2019.9.18 csp-s模拟测试46 反思总结
神志不清:
回去休息(x)继续考试(√)
非常爆炸的一次考试。看错题码完T1回去再看发现自己过于幼稚,T2读完题看着16mb的空间秒出正解然后逻辑出现致命失误100pts->0pts,T3看了一会题直接放弃,10pts最后有时间再说->没时间,下一个。
还有什么好说的呢,略略。
T1:Set
曾经学完鸽巢原理【抽屉原理】以后还想过怎么考这个知识点的问题,最后没想出个所以然,断定一定是看不出来考点的考法。然后我真没看出来…一开始还读错题。T2打了个自以为的正解以后看了一会儿T3翻回来检查这道题,推翻了重新想。
大约想了一个小时?对 非常睿智。之后整个转换了思路才终于想到DP。半小时紧急打个DP然后瞎调一通,居然有50pts。
最后考完听别人一说才恍然大悟,这不是背包吗…
正解则完全不一样,是上面所说的鸽巢原理。余数有一个性质,n的余数不超过n种。对于给出的数列取模做个前缀和,从S0到Sn一共有n+1种余数,那么一定存在余数相同的两个S,把这两个位置中间的一段输出,它们的和一定被n整除。
非常巧妙的一道题。
#include<iostream> #include<cstdio> using namespace std; int n; int s[1000010],v[1000010]; int main() { scanf("%d",&n); v[0]=0; for(int i=1,x;i<=n;i++){ scanf("%d",&x); s[i]=(s[i-1]+x)%n; if(!v[s[i]]&&s[i]!=0)v[s[i]]=i; else{ printf("%d\n",i-v[s[i]]); for(int j=v[s[i]]+1;j<=i;j++)printf("%d ",j); return 0; } } printf("-1"); return 0; }
T2:Read
16mb,读入又很麻烦,显然是一边读入一边处理答案的类型。
看题发现要先求出众数,再加上边读入边处理,自然想到了摩尔投票法。摩尔投票法还是当时准备给大家讲平衡树的时候在一道题里用到过,我引给大家的算法……结果这次四个人里只有我wa了……
wa的原因是处理完众数以后,摩尔投票法剩下的那个次数我直接用来求答案了。实际上最后存下的那个“众数”不一定是真正的众数,有可能恰巧是剩下的数字,并不多于所有数的一半。应该再扫一遍看是否大于(n+1)/2,然后大于的话再减去合适的数量。(n+1)/2和之后的那个数量都可以列出式子来移项得到。
足以说明我当时多么睿智…虽然现在也好不到哪去。
#include<iostream> #include<cstdio> using namespace std; long long X,Y; int sum,cnt,m,k,count[1010],x[1010],y[1010],z[1010]; int main() { scanf("%d%d",&m,&k); for(int i=1;i<=m;i++)scanf("%d",&count[i]); for(int i=1;i<=m;i++)scanf("%d",&x[i]); for(int i=1;i<=m;i++)scanf("%d",&y[i]); for(int i=1;i<=m;i++)scanf("%d",&z[i]); int N=0,S=(1<<k)-1; for(int i=1;i<=m;i++){ X=x[i]; if(X==Y)cnt++; else{ cnt--; if(cnt<0){ cnt=1; Y=X; } } long long lst=x[i]; // printf("%d ",X); for(int j=1;j<count[i];j++){ lst=(lst*y[i]+z[i])&S; X=lst; if(X==Y)cnt++; else{ cnt--; if(cnt<0){ cnt=1; Y=X; } } // printf("%d ",X); } } for(int i=1;i<=m;i++){ N++; X=x[i]; if(X==Y)sum++; long long lst=x[i]; // printf("%d ",X); for(int j=1;j<count[i];j++){ lst=(lst*y[i]+z[i])&S; N++; X=lst; if(X==Y)sum++; // printf("%d ",X); } } if(sum>(N+1)/2)printf("%d",sum*2-N-1); // printf("\n"); else printf("0"); return 0; }
T3:Race
看了一眼题,想了会儿,直接扔了。
最后看正解果然是我的思路盲区…
x2的实质:比当前选手排名高的人任取两个组成的有序数对的个数,数对中的两个数可以重复。
在2m天中同样的数对可能多次出现,那么可以考虑这个数对出现了多少次,进一步转化成每个数对于同一选手的贡献。
对于一位选手,因为所有人的能力值均不相同,其他人一定可以根据能力值二进制第一位和他不同的位数分成至多m组。可以用01trie数记录下所有能力值并存一个size,对于每个运动员都在树上查一次,记录下第一位和他不同的数的个数。每个与他不同的数,排名大于他的次数都是总次数的二分之一,那么一个有序数对就是四分之一即2m-2次*2【因为数对有序所以*2】。
#include<iostream> #include<cstdio> using namespace std; int n,m; long long ans; const long long mod=1000000007; int a[200010],tree[6000010][2],tot=1,sum[6000010][2],f[31]; void ins(int x){ int now=1; for(int i=m-1;i>=0;i--){ if(!tree[now][(x>>i)&1])tree[now][(x>>i)&1]=++tot; sum[now][(x>>i)&1]++; now=tree[now][(x>>i)&1]; } } void work(int x){ int now=1; for(int i=m-1;i>=0;i--){ f[i]=sum[now][((x>>i)&1)^1]; now=tree[now][(x>>i)&1]; } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); ins(a[i]); } for(int i=1;i<=n;i++){ work(a[i]); long long num=0; for(int j=0;j<m;j++){ for(int k=j;k<m;k++){ num=(num+(2ll*f[j]*f[k]%mod*(1<<(m-2))%mod))%mod; } } ans^=num; } printf("%lld",ans); return 0; }
马上考试,祝大家rp++。