(3/6) rating: 1761 -> 1801 (+40)
A. Desorting
题意
给定一个序列 a1,a2,…,an。每一次操作,你可以选定一个下标 i,给 a1,a2,…,ai 加上 1,给 ai+1,ai+2,…,an 减上 1。
求要使这个序列变成非有序的,至少需要进行几次操作?
解法
不难发现,序列是非有序的等价于存在下标 i,使 ai>ai+1。因此,我们只需要找到原序列中差最小的两个元素,并将这两个元素弄成无序的即可。
可以发现,操作次数为 ⌊2ai+1−ai⌋+1,特判一下原来就有序的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   | #include<bits/stdc++.h> using namespace std;
  using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define begend(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__) 
  int read(){     int f=1,x=0;char c=getchar();     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}     while(isdigit(c)){x=x*10+c-'0';c=getchar();}     return x*f; }
  const int N = 505; int a[N]; void solve(int kase){     int n=read();     for(int i=0;i<n;i++){         a[i]=read();     }
      int ans = INT_MAX;     for(int i=1;i<n;i++){         ans = min(ans,a[i]-a[i-1]);     }
      if(ans<0)puts("0");     else printf("%d\n",(ans)/2+1); }
  int main(){     int T=read(),TT=1;     while(T--){         solve(TT++);     }     return 0; }
 
  | 
 
B. Fibonaccharsis
题意
我们称如下数列为类斐波那契数列:
- 数列只包含非负整数,并且单调不下降;
 
- 对于 n>2,an=an−1+an−2。
 
给定 n (1≤n≤2⋅105) 和 k (3≤k≤109),求出有多少个类斐波那契数列,使得第 k 项为 n。
解法 1
考虑枚举第一项和第二项。不难发现,第一项的范围是 0∼n,对于第二项,可以进行二分答案找出一个合法的第二项。
这样做的复杂度理论上是 O(nlogn⋅k) 的,但是因为斐波那契数列的增长速度是一个等比数列,所以不需要加到 k 项就会超过 n,最多只需要加 O(logn) 项。因此,复杂度为 O(nlog2n),足够通过本题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
   | #include<bits/stdc++.h> using namespace std;
  using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define begend(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__) 
  int read(){     int f=1,x=0;char c=getchar();     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}     while(isdigit(c)){x=x*10+c-'0';c=getchar();}     return x*f; }
  void solve(int kase){     ll n=read(),k=read();
      auto check = [n,k](ll a,ll b){         if(a==0 && b==0)return -1;         ll c;         for(int i=3;i<=k;i++){             c = a+b;             if(c>n)return 0;             a=b;             b=c;         }         if(c==n)return -2;         return -1;     };  
      ll ans = 0;
      for(ll a=0;a<=n;a++){         if(a!=0 && check(a,a)==0)break;
          ll l=a,r=n;         while(l<r){             auto mid=(l+r+1)>>1;
              if(check(a,mid)<0)l=mid;             else r=mid-1;         }         if(check(a,l)==-2)ans++;     }
      printf("%lld\n",ans); }
  int main(){     int T=read(),TT=1;     while(T--){         solve(TT++);     }     return 0; }
 
  | 
 
解法 2(更快)
设第一项为 x,第二项为 y,观察这个数列的前几项:
x,y,x+y,x+2y,2x+3y,3x+5y,5x+8y,8x+13y,…
你发现了规律了吗?设斐波那契数列 {1,1,2,3,5,8,…} 为 F,则 ai=Fi−2x+Fi−1y。
因此,ak=Fk−2x+Fk−1y。这样的复杂度理论是 O(n+k) 的(因为要计算前 n 项斐波那契数列,尽管可以加速到 O(logk)),但是当 k≥42 的时候,此时已经有 F40>108 了,不论如何都不存在合法的解。因此总复杂度可以做到 O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
   | #include<bits/stdc++.h> using namespace std;
  using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define begend(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__) 
  int read(){     int f=1,x=0;char c=getchar();     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}     while(isdigit(c)){x=x*10+c-'0';c=getchar();}     return x*f; }
  const int N = 40+5; ll f[N];
  void solve(int kase){     ll n=read(),k=read();
      if(k>=42){         puts("0");         return;     }          ll ans = 0;     for(ll a=0;a<=n;a++){         ll b = n-a*f[k-2];         if(b%f[k-1]==0){             b/=f[k-1];             if(b>=a)ans++;         }     }     printf("%lld\n",ans); }
  int main(){     f[1]=f[2]=1;     for(int i=3;i<N;i++){         f[i]=f[i-1]+f[i-2];     }
      int T=read(),TT=1;     while(T--){         solve(TT++);     }     return 0; }
 
  | 
 
解法 3(最快)
事实上,在上面的算法基础上,可以继续优化,使本题的复杂度达到 O(logn)。
注意到我们要找的是 Fk−2x+Fk−1y=n (0≤x≤y) 的整数解。
事实上,斐波那契数列任意相邻两项互质,即 ∀i≥2,gcd(Fi,Fi−1)=1(证明略,因为这是信息竞赛,记住就行了)。那么这个方程总存在整数解(裴蜀定理)。
我们可以用 拓展欧几里得算法 找出其的一组整数解 (x0,y0)。则其所有解就可以表示为 (x0+kFk−1,y0−kFk−2)。我们只要找出所有合法的 k 即可,利用 0≤x≤y 的条件 O(1) 求出 k 的范围。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
   | #include<bits/stdc++.h> using namespace std;
  using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define begend(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__) 
  int read(){     int f=1,x=0;char c=getchar();     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}     while(isdigit(c)){x=x*10+c-'0';c=getchar();}     return x*f; }
  const int N = 40+5; ll f[N];
  void exgcd(ll a,ll b,ll& x,ll& y){     if(b==0){x=1,y=0;return;}     exgcd(b,a%b,y,x);     y-=(a/b)*x; }
  void solve(int kase){     ll n=read(),k=read();
      if(k>=42){         puts("0");         return;     }          ll x,y;     exgcd(f[k-2],f[k-1],x,y);     x*=n,y*=n;
                ll mink = ceil(1.0*-x/(f[k-1]));
                     ll maxk = floor(1.0*(y-x)/(f[k-2]+f[k-1]));
      if(maxk<mink)puts("0");     else printf("%lld\n",maxk-mink+1); }
  int main(){     f[1]=f[2]=1;     for(int i=3;i<N;i++){         f[i]=f[i-1]+f[i-2];     }
      int T=read(),TT=1;     while(T--){         solve(TT++);     }     return 0; }
 
  | 
 
C. Ntarsis’ Set
题意
给定正整数集 S。每次操作同时删除其中第 a1,a2,…,an 小的数。
求进行 k (1≤k≤2⋅105) 次操作或,集合中最小的数是几?
解法
不妨考虑反向进行这个操作。一开始集合中只有一个数。
一次操作,我们往 a1−1,a2−2,…,an−n 的位置上插入一个数,进行 k 次。则最小的数下标增加量就是小于其下标的数的数量。因为这个增加量是单调不下降的,因此可以做到复杂度为 O(n+k)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
   | #include<bits/stdc++.h> using namespace std;
  using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define begend(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__) 
  int read(){     int f=1,x=0;char c=getchar();     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}     while(isdigit(c)){x=x*10+c-'0';c=getchar();}     return x*f; } template <class T> T read(){     T f=1,x=0;char c=getchar();     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}     while(isdigit(c)){x=x*10+c-'0';c=getchar();}     return x*f; } template <class T> void write(T x){     if(x<0)putchar('-'),x=-x;     if(x>9)write(x/10);     putchar(x%10+'0'); }
  const int N = 2e5+5; int a[N];
 
  void solve(int kase){          int n=read(),k=read();
      for(int i=0;i<n;i++)a[i]=read()-i-1;
      if(a[0]!=0){         puts("1");         return;     }          ll ans = 1, delta = 0;     for(int i=1;i<=k;i++){         if(ans>a[n-1])ans+=n;         else{             while(ans>a[delta])delta++;             ans+=delta;         }     }     printf("%lld\n",ans); }
  int main(){     int T=read(),TT=1;     while(T--){         solve(TT++);     }     return 0; }
 
  | 
 
D. Imbalanced Arrays
题意
给定序列 a1,a2,…,an,要求求出一个满足下列条件的序列 bn:
- −n≤bi≤n,bi=0;
 
- 不存在 (i,j),使 bi+bj=0;
 
- ∀1≤i≤n,恰有 ai 个 j,使得 bi+bj>0(i,j 不一定不同)。
 
判断这样的序列是否存在,如果存在,给出一个合法解。
解法
我们观察到一个事实,如果存在合法解,一定存在一组解,使得所有数的绝对值各不相同。(想一想,为什么?)
我们继续考虑,现在这个序列的绝对值就分别为 1∼n。考虑绝对值为 n 的那个数,其的 ai 要么为 0(对应为 −n),要么为 n(对应为 n)。如果不存在 0 和 n,则无解。
则我们就成功确定了其中的一个数,对于剩下的数,我们可以同样递归处理长度为 n−1 的数组。
将数组排序后,可以 O(n) 处理所有的数。总复杂度 O(nlogn)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
   | #include<bits/stdc++.h> using namespace std;
  using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__) 
  int read(){     int f=1,x=0;char c=getchar();     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}     while(isdigit(c)){x=x*10+c-'0';c=getchar();}     return x*f; } template <class T> T read(){     T f=1,x=0;char c=getchar();     while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}     while(isdigit(c)){x=x*10+c-'0';c=getchar();}     return x*f; } template <class T> void write(T x){     if(x<0)putchar('-'),x=-x;     if(x>9)write(x/10);     putchar(x%10+'0'); }
  const int N = 1e5+5; int ans[N];
  void solve(int kase){     int n=read();     vector<pii> a;     for(int i=0;i<n;i++){         a.push_back({read(),i});     }     sort(all(a));
      int l=0,r=n-1;     while(l<=r){         if(a[l].first==n-1-r){             ans[a[l].second]=-(r-l+1);             l++;         }         else if((a[r].first==n-l)){             ans[a[r].second]=r-l+1;             r--;         }         else{             puts("NO");             return;         }     }     puts("YES");     for(int i=0;i<n;i++){         printf("%d ",ans[i]);     }     puts(""); }
  int main(){     int T=read(),TT=1;     while(T--){         solve(TT++);     }     return 0; }
 
  |