A - XXXXX
题意:给定n个数和x,选取一个最大子数组,使得其和不被x整除。
解:一开始把子数组看成子序列了。。。如果n个数之和不能被x整除,直接输出n;否则从左右两边分别找到第一个不被x整除的数,选较长一段即可。
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define maxx 500005 #define maxn 1005 #define maxm 200005 #define eps 0.00000001 #define inf 0x7fffffff #define mod 998244353 //#define int long long //int n,m; int n,x; int a[maxx]; signed main() { int T; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&x); for(int i=1;i<=n;i++) scanf("%d",&a[i]); int sum=0; for(int i=1;i<=n;i++) sum+=a[i]; if(sum%x!=0){ printf("%d\n",n); continue; } int l=n,r=1; for(int i=1;i<=n;i++){ if(a[i]%x!=0){ l=i; break; } } for(int i=n;i>=1;i--){ if(a[i]%x!=0){ r=i; break; } } int ans=max(n-l,r-1); if(ans==0) printf("-1\n"); else printf("%d\n",ans); } return 0; }
B - A Game with Traps
题意:给定n个点,m个士兵和k个陷阱。每个士兵有一个敏捷值a[i],对于陷阱i,它的位置为l[i],危险值为d[i],如果士兵的a[i]≥d[i],士兵就不会死掉。如果提前到达r[i](r[i]>l[i]),就可以解除这个陷阱。队长需要带领队伍,且不会掉进陷阱。给定时限T,每走一个单位消耗一个时间,求最多带多少人到点n+1.
解:显然要队长先去解除陷阱,然后大部队跟上。如果可以一起走,尽量一起走节约时间。那么就是,一起走,遇到一个陷阱,解除点在r[i],队长跑过去再折回来,再一起走,这样一个过程。但是如果走到一半,又遇到陷阱,队长又要跑一趟,很浪费时间,所以一次要尽量解除多的陷阱;但如果队长跑得太远,时间会被浪费到折返上,显然存在一个分界点。令陷阱所在处和解除处两个端点形成一条线段,重叠的线段可以一起跑掉,不重叠的则会造成中间路程浪费。由于数据不大,直接在数组里差分即可。队长跑完了,考虑抛弃掉一些敏捷值低的人。这个过程可以二分。
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define maxx 400005 #define maxn 1005 #define maxm 200005 #define eps 0.00000001 #define inf 0x7fffffff #define mod 998244353 //#define int long long int m,n,k,t; int a[maxx],vis[maxx]={0}; struct node{ int l,r,d; bool operator< (const node &a){ return l<a.l; } }tr[maxx]; int check(int x){ for(int i=0;i<=n+1;i++) vis[i]=0; int up=a[x]; for(int i=1;i<=k;i++){ if(tr[i].d>up) vis[tr[i].l]++,vis[tr[i].r+1]--; } for(int i=1;i<=n;i++) vis[i]+=vis[i-1]; int res=0; for(int i=1;i<=n;i++) if(vis[i]) res+=2; res+=n+1; if(res>t) return 0; else return 1; } signed main() { scanf("%d%d%d%d",&m,&n,&k,&t); for(int i=1;i<=m;i++) scanf("%d",&a[i]); for(int i=1;i<=k;i++) scanf("%d%d%d",&tr[i].l,&tr[i].r,&tr[i].d); sort(a+1,a+m+1); sort(tr+1,tr+k+1); int l=1,r=m; int ans=0; while(l<=r){ int mid=(l+r)/2; if(check(mid)){ ans=m-mid+1; r=mid-1; } else l=mid+1; } printf("%d\n",ans); return 0; } // 1 g12 g23 g34 g45 g51 // 2 g123 g234 g345 g451 g512 // n-1 g12...n // 1 2 3 4 5 1 2 3 4 5 //
E. Compress Words
题意:给出n个字符串进行合并,如果第i个字符串的前缀和第1-i个处理出来结果的后缀相同,就可以省略。求最后答案。
解:最长不超过1e6,如果在以有中暴力匹配会T,(听说KMP不会),但是每次算当前字符串的hash值再匹配不会。注意当前是从前往后,已有从后往前,方法不一样。
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define maxx 400005 #define maxn 1005 #define maxm 200005 #define eps 0.00000001 #define inf 0x7fffffff #define mod 998244357 #define base 2333 //#define int long long int n; int a[maxx]; string s,ans; signed main() { scanf("%d",&n); cin>>ans; for(int i=2;i<=n;i++){ cin>>s; ll temp=1,l=0,r=0,idx=0; for(int j=0;j<s.length();j++){ l=(l+s[j]*temp)%mod; r=(r*base+ans[ans.size()-1-j])%mod; temp=temp*base%mod; if(l==r){ idx=j+1; } } for(int j=idx;j<s.size();j++){ ans+=s[j]; } } cout<<ans<<"\n"; return 0; }
F - Array Stabilization (GCD version)
题意:给定一个数组,每次生成一个新数组b,令b[i]=gcd(a[i],a[(i+1)%(n+1)]),问至少操作几次后可以使整个数组的值相同。
解:最后的值一定是它们共同的gcd。一开始整了点随机数,发现最大次数没有超过15的,大喜,以为存在未知的定理能让多个数的gcd在一定次数内相同,然后T了。开始正经做题,发现第k次操作能让b[i]=gcd(i,i+1,...,i+k),最多执行n-1次。那这是个区间操作。考虑枚举k,会T;但这个操作具有单调性,第k次操作完成要求,第k+1次也能完成,二分之。
关于那个环一样的gcd当然是开两倍数组啦,然后我空间只开了一倍的还RE了。。。
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define maxx 400005 #define maxn 1005 #define maxm 200005 #define eps 0.00000001 #define inf 0x7fffffff #define mod 998244353 //#define int long long //int n,m; int n; int a[maxx]; int tr[maxx*8]; int gcd(int a,int b){ return b==0?a:gcd(b,a%b); } void build(int l,int r,int now){ if(l==r){ tr[now]=a[l]; return; } int mid=(l+r)/2; build(l,mid,now*2); build(mid+1,r,now*2+1); tr[now]=gcd(tr[now*2],tr[now*2+1]); } int query(int l,int r,int s,int e,int now){ if(s<=l&&r<=e){ return tr[now]; } int mid=(l+r)/2; int res=0; if(s<=mid) res=gcd(res, query(l,mid,s,e,now*2)); if(mid<e) res=gcd(res, query(mid+1,r,s,e,now*2+1)); return res; } int check(int x){ for(int i=1;i<n;i++){ int n1=query(1,2*n,i,i+x,1); int n2=query(1,2*n,i+1,i+x+1,1); if(n1!=n2) return 0; } return 1; } signed main() { int T; scanf("%d",&T); while(T--){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); int flag=0; for(int i=1;i<n;i++){ if(a[i]!=a[i+1]) break; if(i==n-1) flag=1; } if(flag){ printf("0\n"); continue; } for(int i=1;i<=n;i++) a[i+n]=a[i]; build(1,n*2,1); int ans=n-1; int l=1,r=n-1; while(l<=r){ int mid=(l+r)/2; if(check(mid)){ ans=mid; r=mid-1; } else l=mid+1; } printf("%d\n",ans); } return 0; } // 1 g12 g23 g34 g45 g51 // 2 g123 g234 g345 g451 g512 // n-1 g12...n // 1 2 3 4 5 1 2 3 4 5 //
G - Orac and Models
题意:给定一个数组,从中挑选一个上升子序列,要求子序列里的数在原数组中下标从前往后为倍数关系。
解:直接dp
代码:
#include <bits/stdc++.h> using namespace std; #define ll long long #define maxx 500005 #define maxn 1005 #define maxm 200005 #define eps 0.00000001 #define inf 0x7fffffff #define mod 998244353 //#define int long long //int n,m; int n; int a[maxx],dp[maxx]; signed main() { int T; scanf("%d",&T); while(T--){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); dp[1]=1; for(int i=2;i<=n;i++){ dp[i]=1; for(int j=1;j*j<=i;j++){ if(i%j==0){ if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1); if(a[i]>a[i/j]) dp[i]=max(dp[i],dp[i/j]+1); } } } int ans=0; for(int i=1;i<=n;i++) ans=max(ans,dp[i]); printf("%d\n",ans); } return 0; }