CodeCraft-20 (Div. 2)
A. Grade Allocation
题意:t(200)组数据,n(1e3)个数字,每次可以随意调整数字,只要保证所有数都是非负整数且不大于m,并且平均数不变,问第一个数字最大多大。
思路:每次把a2~an的数字加到a1上,超过m就停止。其实就是所有数之和与m取min就是最终答案。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=1e3+10; 17 using namespace std; 18 int T,n,m; 19 int a[N]; 20 int main(){ 21 // freopen("in.txt","r",stdin); 22 rd(T); 23 while(T--){ 24 rd(n);rd(m); 25 for(int i=1;i<=n;i++)rd(a[i]); 26 for(int i=2;i<=n;i++){ 27 if(a[1]+a[i] <= m){ 28 a[1]+=a[i];a[i]=0; 29 } 30 else { 31 a[1]=m;break; 32 } 33 } 34 printf("%d\n",a[1]); 35 } 36 return 0; 37 } 38 /**/
B. String Modification
题意:给一个长n(5000)的串,选择一个整数k(1~n),从左至右依次将长为k的连续子串翻转,直到超出边界为止,问最终字符串字典序最小时k最小为多少。
思路:模拟几次看一下,我们可以发现,第k个字符一定到了第一个,因为只有第一次操作会涉及它,同理第k+1个字符只有第二次操作涉及,到了第二个,一直到第n个字符。而对于1~k-1这一串,我们发现每次操作都涉及他们,于是他们每次操作都被翻转一次,只要知道翻转了奇数次还是偶数次即可知道最终的样子。于是枚举每个k算出来最终字符串,依次比较即可得到答案。我在考试用了sort...因为我弱智了,完全没有必要,白多了一个log。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=5e3+10; 17 using namespace std; 18 int T; 19 int n; 20 char s[N]; 21 struct ghb{ 22 int k; 23 char s[N]; 24 bool operator < (const ghb &o)const{ 25 for(int i=0;i<n;i++) 26 if(s[i] != o.s[i])return s[i] < o.s[i]; 27 return k < o.k; 28 } 29 }f[N]; 30 int main(){ 31 // freopen("in.txt","r",stdin); 32 rd(T); 33 while(T--){ 34 rd(n);scanf("%s",s); 35 for(int k=1;k<=n;k++){ 36 f[k].k=k; 37 for(int i=0;i+k-1 < n;i++)f[k].s[i]=s[i+k-1]; 38 if(k%2 == n%2){ 39 for(int i=n-k+1;i<n;i++)f[k].s[i]=s[n-i-1]; 40 } 41 else { 42 for(int i=n-k+1;i<n;i++)f[k].s[i]=s[i-n+k-1]; 43 } 44 } 45 sort(f+1,f+n+1); 46 for(int i=0;i<n;i++)putchar(f[1].s[i]);puts(""); 47 printf("%d\n",f[1].k); 48 } 49 return 0; 50 } 51 /*二分+hash似乎可以优化字符串比较字典序的过程*/
反思:二分+hash可以优化字符串比较过程,一般是n^2预处理,nlog^2sort,而本题预处理只需要n,可以优化至nlog,但空间并不能优化。
C. Primitive Primes
题意:给两个长为n,m(1e6)的多项式,保证每个多项式系数(1e9)为正且所有系数gcd为1,两个式子卷积,给出素数p,求出卷积后的式子中系数模p非0的任意一位的指数。(题目保证这一位一定存在)
思路:首先想到ff和ntt直接套板子,然而并不现实,fft的话数值太大会有精度损失无法准确判断,ntt直接在模数选取这里就不合法。不过好像有任意模数ntt?那个我不会。因为答案一定存在,考虑构造一组合法的解,我们对所有系数模p,然后分别找到两个多项式最低的非0位ij,可以发现i+j就是最终答案之一,因为它的构成中只有ij这一项非0,其他全为0,所以它自身也非0。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=1e6+10; 17 using namespace std; 18 int n,m,p; 19 int a[N],b[N]; 20 int main(){ 21 // freopen("in.txt","r",stdin); 22 rd(n);rd(m);rd(p); 23 for(int i=0;i<n;i++)rd(a[i]),a[i]%=p; 24 for(int i=0;i<m;i++)rd(b[i]),b[i]%=p; 25 int x,y; 26 for(int i=0;i<n;i++) 27 if(a[i]){x=i;break;} 28 for(int i=0;i<m;i++) 29 if(b[i]){y=i;break;} 30 printf("%d\n",x+y); 31 return 0; 32 } 33 /**/
反思:知道了fft和ntt的局限性。题解中提到,这道题以另一种方式证明了本原多项式(系数gcd为1的多项式)的乘积仍为本原多项式,原多项式系数gcd为1,所以不存在一个素数整除所有系数,于是对于任意一个素数都可以找到一个非0位,于是可以找到卷积后的式子中的非0位。于是也找不到一个素数整除卷积后的所有系数,于是卷积后的式子系数gcd也为1。
D. Nash Matrix
题意:n(1e3)*n的格子,每个格子上写着LRDUX之一,分别表示左右下上停几个命令,从一个格子开始,按命令执行下去,如果停在了某个格子,那么将这个格子的坐标记录在起始格子上,如果永远停不下来,那么起始格子记录的便为(-1,-1),现在给出所有格子记录的值,问能否构造出一组合法的解,如果能就输出方案之一。:
思路:先考虑(-1,-1)的格子,我们可以发现,只要不存在孤立的(-1,-1)点,我们一定可以构造出合法方案,即两个点连成环,其他点连向两个点其中一个。再考虑那些一定是X的点,也就是那些记录的值为自身的格子,发现只要是那些记录值和其相同且联通的格子,一定可以通过构造到达它,而那些不连通的格子,一定不可能到达它。所以可以类似于连通分量的方法求出一个个联通的"块",进而判断合法与否。构造时只需要记录当前点是从哪里走来的,原路返回即可。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=1e3+10; 17 using namespace std; 18 int n; 19 struct Point{ 20 int x,y,bel; 21 bool operator == (const Point o)const{ 22 return x==o.x && y==o.y; 23 } 24 }a[N][N]; 25 int siz[N*N],cnt_bel; 26 int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1}; 27 int dfs(int x,int y,int z){ 28 siz[a[x][y].bel=z]++; 29 for(int i=0;i<4;i++){ 30 int tx=x+dx[i],ty=y+dy[i]; 31 if(a[tx][ty].bel)continue; 32 if(tx < 1 || tx > n || ty < 1 || ty > n)continue; 33 if(a[tx][ty].x != a[x][y].x || a[tx][ty].y != a[x][y].y)continue; 34 dfs(tx,ty,z); 35 } 36 } 37 char ans[N][N]; 38 bool flg[N][N]; 39 void work1(int x,int y,int z){ 40 flg[x][y]=1; 41 if(z ==-1)ans[x][y]='X'; 42 if(z == 0)ans[x][y]='U'; 43 if(z == 1)ans[x][y]='D'; 44 if(z == 2)ans[x][y]='L'; 45 if(z == 3)ans[x][y]='R'; 46 for(int i=0;i<4;i++)if(!flg[x+dx[i]][y+dy[i]] && a[x+dx[i]][y+dy[i]] == a[x][y])work1(x+dx[i],y+dy[i],i); 47 } 48 void work2(int x,int y,int z){ 49 flg[x][y]=1; 50 if(z == -1){ 51 for(int i=0;i<4;i++) 52 if(a[x+dx[i]][y+dy[i]] == a[x][y]){ 53 if(i == 0)ans[x][y]='D'; 54 if(i == 1)ans[x][y]='U'; 55 if(i == 2)ans[x][y]='R'; 56 if(i == 3)ans[x][y]='L'; 57 work2(x+dx[i],y+dy[i],i); 58 break; 59 } 60 } 61 else { 62 if(z == 0)ans[x][y]='U'; 63 if(z == 1)ans[x][y]='D'; 64 if(z == 2)ans[x][y]='L'; 65 if(z == 3)ans[x][y]='R'; 66 for(int i=0;i<4;i++)if(!flg[x+dx[i]][y+dy[i]] && a[x+dx[i]][y+dy[i]] == a[x][y])work2(x+dx[i],y+dy[i],i); 67 } 68 } 69 int main(){ 70 // freopen("in.txt","r",stdin); 71 rd(n); 72 for(int i=1;i<=n;i++) 73 for(int j=1;j<=n;j++) 74 rd(a[i][j].x),rd(a[i][j].y); 75 for(int i=1;i<=n;i++) 76 for(int j=1;j<=n;j++){ 77 if(a[i][j].bel)continue; 78 if(a[i][j].x == -1) 79 dfs(i,j,++cnt_bel); 80 else if(a[i][j].x == i && a[i][j].y == j) 81 dfs(i,j,++cnt_bel); 82 } 83 bool Flg=0; 84 for(int i=1;i<=n;i++) 85 for(int j=1;j<=n;j++) 86 if(a[i][j].x == -1 && siz[a[i][j].bel] == 1){ 87 Flg=1;break; 88 } 89 else if(!a[i][j].bel){Flg=1;break;} 90 if(Flg)printf("INVALID\n"); 91 else { 92 printf("VALID\n"); 93 for(int i=1;i<=n;i++) 94 for(int j=1;j<=n;j++) 95 if(a[i][j].x==i && a[i][j].y==j)work1(i,j,-1); 96 else if(a[i][j].x == -1 && !flg[i][j])work2(i,j,-1); 97 for(int i=1;i<=n;i++)printf("%s\n",ans[i]+1); 98 } 99 return 0; 100 } 101 /**/
反思:这种记录来自哪里来构造方案的思路需要记住。
E. Team Building
题意:n(1e5)个人,从中选出p(7)个人当运动员,k(1~n-p)个人当观众,每个人当观众有一个权值ai,运动员p种,第i个人当第j个运动员有一个权值sij,问如何分配使得最终权值和最大。
思路:p很小,容易想到状压dp,考虑f[i][j]表示前i个人,运动员选举状态为j时的最大权值。然而对于观众的选择却很难确定,不选可能会错过,选了又没办法记录。所以考虑按ai从大到小对所有人进行排序,再次进行相同的dp,而当前枚举的人,如果不选为运动员,且观众没选够,那么一定要选他当观众,因为后面的观众ai都比他要小。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=1e5+10; 17 using namespace std; 18 int n,p,k; 19 struct Per{ 20 int a,s[8]; 21 bool operator < (const Per &o)const{ 22 return a > o.a; 23 } 24 }f[N]; 25 LL dp[N][1<<7]; 26 int bit[8],siz[1<<7]; 27 int main(){ 28 // freopen("in.txt","r",stdin); 29 rd(n);rd(p);rd(k); 30 for(int i=1;i<=n;i++)rd(f[i].a); 31 for(int i=1;i<=n;i++)for(int j=1;j<=p;j++)rd(f[i].s[j]); 32 sort(f+1,f+n+1);int t=1<<p; 33 for(int i=1;i<=7;i++)bit[i]=(1<<i-1); 34 for(int i=1;i< t;i++)siz[i]=siz[i>>1] + (i&1); 35 memset(dp,-1,sizeof(dp));dp[0][0]=0; 36 for(int i=0;i<n;i++){ 37 for(int j=0;j<t;j++){ 38 if(dp[i][j] == -1)continue; 39 dp[i+1][j]=max(dp[i+1][j],dp[i][j]); 40 if(i-siz[j] < k)dp[i+1][j]=max(dp[i+1][j],dp[i][j]+f[i+1].a); 41 for(int x=1;x<=p;x++){ 42 if(bit[x] & j)continue; 43 dp[i+1][j|bit[x]]=max(dp[i+1][j|bit[x]],dp[i][j]+f[i+1].s[x]); 44 } 45 } 46 } 47 printf("%lld\n",dp[n][t-1]); 48 return 0; 49 } 50 /**/
反思:这种思路很值得学习,通过排序使得某个权值单调,进而可以进行贪心,使得原本dp没办法记录的东西通过贪心可以做到。
F. Battalion Strength
题意:n(3e5)个数字,任取一个子集,按ai排序,记录ans=ak1*ak2+ak2*ak3+...,如果子集大小小于2,ans=0 。q(3e5)次询问,每次询问修改一个值,并要求输出ans的期望。(对1e9+7取模)
思路:这种数据范围直接考虑数据结构。选取每个子集的概率相同,所以求期望本质上就是求出所有方案ans的总和并除以2^n。我们先对原序列排序,然后考虑ai<aj对答案的贡献,可以发现,ai~aj之间的数字不能取,两边的数字任意取,所以ai*aj共计算2^(i-1) * 2^(n-j)次。于是便成了对所有i<j,求ai*aj*2^(n-j+i-1)之和。我们考虑是否可以通过两个小区间得到大区间的ans,发现小区间中只要记录sl=所有ai*2^(i-1)之和和sr=所有ai*2^(n-i)之和,以及小区间的元素个数siz,和小区间的ans,我们就可以得到大区间的ans。具体操作见代码中的转移,很容易理解,但打字不方便。既然可以在O1的时间内进行区间的合并,我们便可以建造一颗线段树,进行单点修改并维护答案。需要注意我们必须离线来做这个问题,因为我们要维护ai的单调,所以需要按照ai的值建立权值线段树,那么就必须离散化。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=3e5+10; 17 const int mod=1e9+7; 18 using namespace std; 19 int n,p[N]; 20 int Q; 21 struct Query{ 22 int id,val; 23 }q[N]; 24 vector<pair<int,int> >S; 25 int pos[N<<1],pw[N]; 26 int invn; 27 int fast_pow(int x,int y){ 28 int ans=1; 29 while(y){ 30 if(y&1)ans=1ll*ans*x%mod; 31 x=1ll*x*x%mod;y>>=1; 32 } 33 return ans; 34 } 35 int get_id(pair<int,int> x){ 36 return lower_bound(S.begin(),S.end(),x)-S.begin(); 37 } 38 struct Node{ 39 int ans,siz,sl,sr; 40 }f[N<<3]; 41 #define ls (o<<1) 42 #define rs (o<<1|1) 43 #define mid (l+r>>1) 44 void modify(int x,int y,int l=1,int r=n+Q,int o=1){//注意元素的顺序 45 if(l == r){ 46 f[o].sl=f[o].sr=y;f[o].ans=0;f[o].siz=(y!=0); 47 } 48 else { 49 if(x <= mid)modify(x,y,l,mid,ls); 50 else modify(x,y,mid+1,r,rs); 51 f[o].ans=(1ll*f[ls].sl*f[rs].sr%mod+(1ll*f[ls].ans*pw[f[rs].siz]%mod+1ll*f[rs].ans*pw[f[ls].siz]%mod)%mod)%mod; 52 f[o].siz=f[ls].siz+f[rs].siz; 53 f[o].sl=(1ll*pw[f[ls].siz]*f[rs].sl%mod+f[ls].sl)%mod; 54 f[o].sr=(1ll*pw[f[rs].siz]*f[ls].sr%mod+f[rs].sr)%mod; 55 } 56 } 57 vector<pair<int,int> >b; 58 int main(){ 59 // freopen("in.txt","r",stdin); 60 rd(n);for(int i=1;i<=n;i++)rd(p[i]),b.push_back(make_pair(p[i],i)),S.push_back(b[i-1]); 61 rd(Q);for(int i=1;i<=Q;i++)rd(q[i].id),rd(q[i].val),S.push_back(make_pair(q[i].val,i+n));//保证每个元素都不相同 62 pw[0]=1;for(int i=1;i<=n;i++)pw[i]=pw[i-1]*2%mod; 63 invn=fast_pow(pw[n],mod-2);sort(S.begin(),S.end()); 64 for(int i=1;i<=n;i++)modify(get_id(b[i-1])+1,p[i]);//lowerbound得到的数字是从0开始的 65 // int x=1; 66 // cout<<f[x].siz<<" "<<f[x].ans<<" "<<f[x].sl<<" "<<f[x].sr<<endl; 67 printf("%d\n",1ll*f[1].ans*invn%mod); 68 for(int i=1;i<=Q;i++){ 69 modify(get_id(b[q[i].id-1])+1,0); 70 b[q[i].id-1]=make_pair(q[i].val,i+n); 71 modify(get_id(b[q[i].id-1])+1,q[i].val); 72 printf("%d\n",1ll*f[1].ans*invn%mod); 73 } 74 return 0; 75 } 76 /**/
反思:我这道题写了好多遍,都是在写完之后才发现思路有漏洞的,浪费了大量时间,破坏了心态。所以所有题,一定要想清楚了,再写,一边写对。这次代码和思路是借鉴榜一大佬的,很简洁很易懂。回顾了一些知识,比如向函数传入的元素可以设定初始值,但一定要注意设定初始值的这一部分放到最后,具体还没有研究机理,研究清楚之前还是少用为好。以及lowerbound出来的指针减去begin之后是从0开始的。还对离散化有了更深的理解。其实我们可以通过log的复杂度来取得id和离散化后的值之间的联系,用map或者二分(通过第二个关键字保证每个元素都不相同进而可以通过二分找到每个元素对应的位置)这种操作即可,因为我们可能只需要调用n次这个值,使得复杂度并不是*log而是+log。离散化的过程用pair即可,不一定要新建结构体。另外对于有两种类型,比如ai本身和询问的x需要同时离散化时,我们不一定非要if特判一堆来区分开进行代回操作,而是可以直接建立一个pos数组,对x这种操作的id定义为i+n,进而在需要x对应离散化后的值时直接调用pos[i+n]即可,省去了很多if(i>n)之类的回代过程会出现的判断。