8.23<2>题解
由于考试第二天就滚回文化课了,所以一直在翘自习改考试题,改到昨天考试之前才刚刚改完,以后就是半集训了,博客可能会经常性咕咕咕,有空会填坑
T1
看这道题的随机生成,其实有点懵,就直接甩到了最后,没来得及看大样例,也没想到可以找规律,于是乎打了20分暴力就滚粗了,实施上如果打开了大样例的话,应该会发现一些猫腻,会发现答案是一个等差数列,而公差就是所有的$a_i$和$k$的$gcd$的最小值,就结束了,证明没怎么看,来自出题人的证明
1 //每次+最小gcd加到k 2 #include<iostream> 3 #include<cstdio> 4 using namespace std; 5 int n,mod,minn=1000100,ans; 6 int pd[1001000]; 7 int gcd(int a,int b) 8 { 9 return b==0?a:gcd(b,a%b); 10 } 11 int main() 12 { 13 scanf("%d%d",&n,&mod); 14 for(int i=1;i<=n;++i) 15 { 16 int x; scanf("%d",&x); 17 pd[x%mod]=1; 18 } 19 for(int i=0;i<=mod;++i) 20 if(pd[i]) minn=min(minn,gcd(mod,i)); 21 printf("%d\n0 ",mod/minn); 22 int ls=minn; 23 while(ls<mod) {printf("%d ",ls); ls+=minn;} 24 puts(""); 25 return 0; 26 }
T2
考场上想到了dp转移,也打出来了,想到了二维树状数组维护最值,但是由于觉得二维树状数组只能维护前缀最大值,所以并没有打,但是正解中的转换坐标的思路,真的非常好
我把一个矩阵直接压成了一个数列,只是会存一下他们的坐标,所以二维的dp也同时被我压成了一维,由于他需要$a$单调递增,所以对于当前点只会由$a[j]=a[i]-1$的$j$转移而来,那么此时$dp[i]=max(dp[j]+b[i]+|x[i]-x[j]|+|y[i]-y[j]|)$,之所以需要二维树状数组,是由于横纵坐标分别去绝对值对应了4种情况,那我们把原坐标$(i,j)$,变成$(i+j,i-j)$,就可以直接维护这四种情况,如果绝对值去的不正确那么答案只会更差,所以我们只需要维护对于每个点的四种情况,直接$O(1)$查询最大值即可,而我们需要维护的就是所有$a[i]-1$的点的$dp[j]-x$ $dp[j]-y$ $dp[j]+x$ $dp[j]+y$,$x$和$y$均为修改后的坐标,可以用数组维护,也可以只开四个变量,就可以做到$O(1)$查询,最坏情况下,总复杂度就是$O(n^2)$
记得按照a排序
1 #include<algorithm> 2 #include<iostream> 3 #include<cstdio> 4 #define re register 5 #define ll long long 6 #define maxn 2010 7 using namespace std; 8 struct node{ 9 int a,b,x,y; 10 }tz[maxn*maxn]; 11 struct tr{ 12 int zuo,you; 13 ll w; 14 }tre1[maxn*4],tre2[maxn*4]; 15 int n,m,js,flag=1; 16 ll max1,max2,max3,max4,ls1,ls2,ls3,ls4,ans; 17 ll dp[maxn*maxn]; 18 int aa[maxn][maxn],bb[maxn][maxn]; 19 inline int read() 20 { 21 re int e=0; re char ch=getchar(); 22 while(ch<'0'||ch>'9') ch=getchar(); 23 while(ch>='0'&&ch<='9') {e=(e<<3)+(e<<1)+(ch^48); ch=getchar();} 24 return e; 25 } 26 bool cmp(const node &a,const node &b) 27 { 28 return a.a<b.a; 29 } 30 int main() 31 { 32 n=read(); m=read(); 33 for(re int i=1;i<=n;++i) 34 for(re int j=1;j<=m;++j) aa[i][j]=read(); 35 for(re int i=1;i<=n;++i) 36 for(re int j=1;j<=m;++j) bb[i][j]=read(); 37 for(re int i=1;i<=n;++i) 38 for(re int j=1;j<=m;++j) 39 { 40 if(aa[i][j]==0) continue; 41 tz[++js].a=aa[i][j]; tz[js].b=bb[i][j]; 42 tz[js].x=i+j; tz[js].y=i-j; 43 } 44 sort(tz+1,tz+js+1,cmp); 45 re int qd=1; dp[1]=1ll*tz[1].b; ans=dp[1]; 46 max1=dp[1]+tz[1].x; max2=dp[1]-tz[1].x; 47 max3=dp[1]+tz[1].y; max4=dp[1]-tz[1].y; 48 for(re int i=2;i<=js;++i) 49 { 50 if(tz[i].a!=tz[i-1].a) break; 51 qd=i; dp[i]=1ll*tz[i].b; 52 max1=max(max1,dp[i]+tz[i].x); max2=max(max2,dp[i]-tz[i].x); 53 max3=max(max3,dp[i]+tz[i].y); max4=max(max4,dp[i]-tz[i].y); 54 ans=max(ans,dp[i]); 55 } 56 qd++; 57 for(re int i=qd;i<=js;++i) 58 { 59 if(tz[i].a!=tz[i-1].a) 60 { 61 ls1=max1; ls2=max2; ls3=max3; ls4=max4; 62 max1=0; max2=0; max3=0; max4=0; 63 } 64 ll lss1=max(ls1-tz[i].x,ls2+tz[i].x); 65 ll lss2=max(ls3-tz[i].y,ls4+tz[i].y); 66 dp[i]=max(lss1,lss2)+tz[i].b; 67 max1=max(max1,dp[i]+tz[i].x); max2=max(max2,dp[i]-tz[i].x); 68 max3=max(max3,dp[i]+tz[i].y); max4=max(max4,dp[i]-tz[i].y); 69 ans=max(ans,dp[i]); 70 } 71 printf("%lld\n",ans); 72 return 0; 73 }
T3
预处理类似于8.22的T2,找到以这个值作为最大值的最大区间,在区间中算贡献
$ans1$其实还是比较简单的,由于我们已经确定了最大值管辖的区间,那对于异或,我们可以按位考虑贡献,对于最大值左侧如果第$j$为上为1,那就要求右区间的这一位必须是0,才能作出贡献,那么我们可以前缀和$f[i][j]$统计前$i$个数中第$j$位上为1的有几个数,左右乘起来计算贡献即可
$ans2$正解给出的方法是启发式合并$01trie$,我不会,于是就去学了可持久化$01trie$,看懂了的话其实挺好理解的,下面给出模板
1 struct trie{ 2 int cnt; 3 int ch[maxn][2],sum[maxn]; 4 int insert(int x,int val) 5 { 6 int tmp,y; tmp=y=++cnt; 7 for(int i=27;i>=0;i--) 8 { 9 ch[y][0]=ch[x][0]; ch[y][1]=ch[x][1]; 10 int t=val&mi[i]; t>>=i; 11 x=ch[x][t]; ch[y][t]=++cnt; y=ch[y][t]; 12 sum[y]=sum[x]+1; 13 } 14 return tmp; 15 } 16 }trie;
我并没有把询问放出来,因为询问其实挺看题的,可以自己yy,$root[i]$记录的是历史版本,对于这道题来说就是一个数代表一个历史版本,$sum[i]$记录的是有多少个数经过了当前结点,其他的就是继承以及新插入了
拿这道题就变为了查找在当前区间中,左右区间异或大于最大值的两个端点,借用启发式合并的思维,我们枚举较小的区间中的每一个数,去查另一个区间中有几个数和它异或起来大于最大值,我们在$trie$上按位查找,如果异或上这一位已经大于最大值就加上这一位上的$sum$,如果小于就直接停了,否则按位查询下去即可
最后统计答案就可以了
1 #include<iostream> 2 #include<cstdio> 3 #include<stack> 4 #define int long long 5 #define maxn 5001000 6 #define mod 1000000007 7 #define inf 10000000 8 using namespace std; 9 int n,opt,ans1,ans2; 10 int a[maxn],l[maxn],r[maxn],root[maxn],mi[28]; 11 int f[maxn][28]; 12 stack <int> s; 13 struct trie{ 14 int cnt; 15 int ch[maxn][2],sum[maxn]; 16 int insert(int x,int val) 17 { 18 int tmp,y; tmp=y=++cnt; 19 for(int i=27;i>=0;i--) 20 { 21 ch[y][0]=ch[x][0]; ch[y][1]=ch[x][1]; 22 int t=val&mi[i]; t>>=i; 23 x=ch[x][t]; ch[y][t]=++cnt; y=ch[y][t]; 24 sum[y]=sum[x]+1; 25 } 26 return tmp; 27 } 28 //被查询点,当前在查询第几层,比较的a[i],某一个端点a[j] 29 int quary(int fa,int pos,int base,int cs,int tot) 30 { 31 if(pos<0) return 0; 32 int lsan=0; 33 int s1=(((cs>>pos)&1)^0)*mi[pos],s2=(((cs>>pos)&1)^1)*mi[pos]; 34 int dq=(base>>pos)&1; 35 int yh0=((cs>>pos)&1)^0,yh1=((cs>>pos)&1)^1; 36 if(dq==0) 37 { 38 if(yh0==0) 39 { 40 lsan+=sum[ch[fa][1]]; 41 lsan+=quary(ch[fa][0],pos-1,base,cs,tot+s1); 42 } 43 else 44 { 45 lsan+=sum[ch[fa][0]]; 46 lsan+=quary(ch[fa][1],pos-1,base,cs,tot+s2); 47 } 48 } 49 else 50 { 51 if(yh0==0) lsan+=quary(ch[fa][1],pos-1,base,cs,tot+s2); 52 else lsan+=quary(ch[fa][0],pos-1,base,cs,tot+s1); 53 } 54 return lsan; 55 } 56 }trie; 57 void work1() 58 { 59 for(int i=1;i<=n;++i) 60 { 61 for(int j=0;j<=27;++j) 62 { 63 if(((a[i]>>j)&1)==1) f[i][j]=f[i-1][j]+1; 64 else f[i][j]=f[i-1][j]; 65 } 66 } 67 for(int i=1;i<=n;++i) 68 { 69 int ls=1ll*0; 70 for(int j=0;j<=27;++j) 71 { 72 int ls1=(f[i][j]-f[l[i]-1][j])%mod; 73 int ls2=(f[r[i]][j]-f[i-1][j])%mod; 74 int len1=(i-l[i]+1)%mod,len2=(r[i]-i+1)%mod; 75 ls=(ls+(mi[j]%mod*(ls1*((len2-ls2+mod)%mod))%mod)%mod)%mod; 76 ls=(ls+(mi[j]%mod*(((len1-ls1+mod)%mod)*ls2)%mod)%mod)%mod; 77 ls=ls%mod; 78 } 79 ans1=(ans1+(ls*a[i]%mod)%mod)%mod; 80 } 81 printf("%lld\n",ans1); 82 } 83 void work2()//查询有几个数,就有几倍的a[i] 84 { 85 for(int i=1;i<=n;++i) root[i]=trie.insert(root[i-1],a[i]); 86 for(int i=1;i<=n;++i) 87 { 88 if(i-l[i]+1<r[i]-i+1) 89 { 90 for(int j=l[i];j<=i;++j) 91 { 92 int ls1=trie.quary(root[r[i]],27,a[i],a[j],0); 93 int ls2=trie.quary(root[i-1],27,a[i],a[j],0); 94 ans2=(ans2+(((ls1-ls2)%mod)*a[i])%mod)%mod; 95 } 96 } 97 else 98 { 99 for(int j=i;j<=r[i];++j) 100 { 101 int ls1=trie.quary(root[i],27,a[i],a[j],0); 102 int ls2=trie.quary(root[l[i]-1],27,a[i],a[j],0); 103 ans2=(ans2+(((ls1-ls2)%mod)*a[i])%mod)%mod; 104 } 105 } 106 } 107 printf("%lld\n",ans2); 108 } 109 main() 110 { 111 // freopen("english6.in","r",stdin); 112 scanf("%lld%lld",&n,&opt); mi[0]=1; 113 for(int i=1;i<=27;++i) mi[i]=mi[i-1]*2; 114 for(int i=1;i<=n;++i) scanf("%lld",&a[i]); 115 a[0]=inf; a[n+1]=inf; 116 s.push(0); 117 for(int i=1;i<=n;++i) 118 { 119 while(a[s.top()]<a[i]) s.pop(); 120 l[i]=s.top()+1; s.push(i); 121 } 122 while(s.size()) s.pop(); 123 s.push(n+1); 124 for(int i=n;i>=1;--i) 125 { 126 while(a[s.top()]<=a[i]) s.pop(); 127 r[i]=s.top()-1; s.push(i); 128 } 129 if(opt==1) work1(); 130 else if(opt==2) work2(); 131 else {work1(); work2();} 132 return 0; 133 }