省选模拟赛记录
Posted TS_Hugh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了省选模拟赛记录相关的知识,希望对你有一定的参考价值。
LOG 模拟赛
第一次见尼玛这么给数据范围的……
开考有点困,迷迷糊糊看完了三道题,真的是像老吕说的那样,一道都不会……
思考T1,感觉有点感觉,但是太困了,就先码了暴力,发现打表可以50分,于是就大力打了一波表……转身T3,码出25分的O(n^2)算法,然后不会了……去了T2,码出35分的O(n^2)bfs,然后不会了……+
考试还有1h+,我又看了一遍三道题,发现并没有什么新的思路,于是决定去想T1,继续考试一开始的思路——我发现每加一位,一定是在原合法方案的基础上加的.想到这里,我就迅速码出,发现需要写尼玛高精度,然而考试还剩下不到10min……我估了一下我现在这题的分——70分,于是决定不打高精度了……
最后70+35+25=130……
后来发现我的T1做法基本就是正解了,只要再写上一个漂亮的高精度就可以A掉了……而正解呢,只不过是继续把性质推了一下然后把我的刷表法变成了填表法……
T3正解是,在我的思路的基础之上,利用题目给出的随机树的条件,把我每次都要重新处理的东西记录了下来,这样像动态点分治一样每次查询加修改就可以了……
思维笔记:I.信息共用以减少处理次数 II.利用随机
算法笔记:I.随机树(我们假设其为有根树)树高期望log(实际上都快到根号级别了) II.随机树(我们假设其为有根树)中,每个点的子树树高与其在整棵树中的深度的乘积的加和的期望是n^1.5的
T2好神啊……
雀巢咖啡系列模拟赛 XLI
异化多肽(T1):
看到这个东西还有那个模数就像要去ntt一发,然后开始推式子.
推啊推,推啊推,推出来一个等比数列求和的式子,很开心,感觉自己要A题了.
然后快速码出快速幂ntt+多项式求逆,调完之后,极限数据5s!!!感觉药丸,然后一波卡常,3s左右,就没再管……
最后90,T了一个点,发现一个很蠢蛋的问题——我凑,我快速幂ntt个蛋啊,mdzz,我要快速幂ntt求的那个多项式在mod x^(n+1)意义下是0啊!!!!感觉自己蠢到家了……
所以这题只不过是一个比较裸的多项式求逆,我为什么要快速幂ntt呢?应该就是我多项式这儿太菜了,当然我觉得也有考试的时候脑子不太正常的原因,还有我的极限思想应该是不太好.
推出这道题的多项式求逆还可以直接从生成函数的实际意义和递推式的角度.
#pragma GCC optimize("O3") #include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=100010; const int P=1005060097; inline int Pow(int x,int y){ int ret=1; while(y){ if(y&1)ret=(LL)ret*x%P; x=(LL)x*x%P,y>>=1; }return ret; } int A[N<<2],ai[N<<2],rev[N<<2],inv[N<<2],len; int n,m; inline void ntt(int *C,int opt){ register int i,j,k,w;int wn,temp; for(i=1;i<len;++i)if(rev[i]>i)std::swap(C[i],C[rev[i]]); for(k=2;k<=len;k<<=1){ wn=Pow(5,(P-1)/k); if(opt==-1)wn=Pow(wn,P-2); for(i=0;i<len;i+=k){ w=1; for(j=0;j<(k>>1);++j,w=(LL)w*wn%P){ temp=(LL)w*C[i+j+(k>>1)]%P; C[i+j+(k>>1)]=(C[i+j]-temp+P)%P; C[i+j]=(C[i+j]+temp)%P; } } } } inline void get_inv(int *a,int *b,int cd){ if(cd==1){b[0]=Pow(a[0],P-2);return;} get_inv(a,b,cd>>1),len=cd<<1; int i,ni=Pow(len,P-2); for(i=1;i<len;++i)rev[i]=(rev[i>>1]>>1)|((i&1)?(len>>1):0); memcpy(A,a,cd<<2),memset(A+cd,0,cd<<2); ntt(A,1),ntt(b,1); for(i=0;i<len;++i)b[i]=(2-(LL)b[i]*A[i]%P+P)%P*(LL)b[i]%P; ntt(b,-1); for(i=0;i<cd;++i)b[i]=(LL)b[i]*ni%P; memset(b+cd,0,cd<<2); } int main(){ scanf("%d%d",&n,&m);int i,x; for(i=1;i<=m;++i) scanf("%d",&x),++ai[x]; ai[0]=P-1,len=1; while(len<=n)len<<=1; get_inv(ai,inv,len); printf("%d\\n",(P-inv[n])%P); return 0; }
编码病毒(T2):
读懂题之后,哇塞,时限有4s,完全可以O(n^2)再来一个1/32的常数用bitset水之啊,然后15min搞好……
最后100,发现正解是比bitset还要水的水fft,就是一个变了一下形的bzoj2194,但是bitset比这玩意好写到不知道哪里去了……
#include <cstdio> #include <bitset> #include <cstring> #include <algorithm> const int N=100010; std::bitset<N> test,data,temp; int n,m,cost[N],cnt[(1<<22)+10]; char s[N]; int main(){ scanf("%d%d",&m,&n); int full=(1<<m)-1,i,j,ans=0x3f3f3f3f; memset(cost,0x3f,sizeof(cost)),cost[0]=0; for(i=1;i<=full;++i){ cnt[i]=cnt[i-((-i)&i)]+1; cost[i%n]=std::min(cost[i%n],cnt[i]+1); cost[((-i)%n+n)%n]=std::min(cost[((-i)%n+n)%n],cnt[i]); } for(scanf("%s",s),i=0;i<n;++i)data[i]=s[i]==\'1\'; for(scanf("%s",s),i=0;i<n;++i)test[i]=s[i]==\'1\'; for(i=0;i<n;++i){ if(i!=0)j=data[0],data>>=1,data[n-1]=j; temp=data^test; j=temp.count(),j=std::min(j,n-j+1); ans=std::min(ans,j+cost[i]); } printf("%d\\n",ans); return 0; }
#include <cmath> #include <cstdio> #include <cstring> #include <complex> #include <algorithm> typedef double db; typedef std::complex<db> cd; const int N=300010; const db Pi=std::acos(-1.); cd A[N<<2],B[N<<2]; int test[N<<2],data[N<<2],ci[N<<2],rev[N<<2],len; int n,m,cost[N],cnt[(1<<22)+10]; char s[N]; inline void fft(cd *C,int opt){ register int i,j,k;cd temp; for(i=1;i<len;++i) if(rev[i]>i) std::swap(C[rev[i]],C[i]); for(k=2;k<=len;k<<=1){ cd wn(std::cos(2*opt*Pi/k),std::sin(2*opt*Pi/k)); for(i=0;i<len;i+=k){ cd w(1.,0.); for(j=0;j<(k>>1);++j,w*=wn){ temp=w*C[i+j+(k>>1)]; C[i+j+(k>>1)]=C[i+j]-temp; C[i+j]+=temp; } } } } inline void mul(int *a,int *b,int *c,int n){ len=1;while(len<n)len<<=1;int i; for(i=0;i<len;++i){ rev[i]=(rev[i>>1]>>1)|((i&1)?(len>>1):0); A[i]=a[i],B[i]=b[i]; } fft(A,1),fft(B,1); for(i=0;i<len;++i)A[i]*=B[i]; fft(A,-1); db inv=1./len; for(i=0;i<len;++i)c[i]=round(A[i].real()*inv); } int main(){ scanf("%d%d",&m,&n); int full=(1<<m)-1,i,j,ans=0x3f3f3f3f; memset(cost,0x3f,sizeof(cost)),cost[0]=0; for(i=1;i<=full;++i){ cnt[i]=cnt[i-((-i)&i)]+1; cost[i%n]=std::min(cost[i%n],cnt[i]+1); cost[((-i)%n+n)%n]=std::min(cost[((-i)%n+n)%n],cnt[i]); } for(scanf("%s",s),i=0;i<n;++i)data[i]=data[i+n]=s[i]==\'1\'?1:-1; for(scanf("%s",s),i=0;i<n;++i)test[n-i-1]=s[i]==\'1\'?1:-1; mul(test,data,ci,n+n+n); for(i=0;i<n;++i){ j=(ci[i+n-1]+n)>>1; ans=std::min(ans,cost[i]+std::min(j+1,n-j)); } printf("%d\\n",ans); return 0; }
数论函数簇(T3):
考场上从看这道题到最后,我对这道题的解法基本没有变——暴力模拟题意……
最后20分……
看到题解发现这题一道神题……
首先,你在暴力模拟题意的时候,会发现,满足a*a=a(mod n),a*b=0(mod n)的a和b就是满足条件的a和b,然后继续观察,发现满足a*a=a(mod n)的a会对答案贡献gcd(a,n).
然后,我们会发现我们的瓶颈在于找到满足a*a=a(mod n)的a,我们可以把这个式子写成a*(a-1)=0(mod n),发现gcd(a,a-1)=1,这个时候我们可以改变对a要满足的条件的表述——因为a与a-1互质,所以gcd(a,n)与gcd(a-1,n)互质,所以若a满足条件,当且仅当gcd(a,n)与gcd(a-1,n)的乘积为n,也就是说a与a-1中含有不相交且加起来是完整的n的n的两部分.
接着,我们设q为gcd(a-1,n),p为gcd(a,n),并且先假设1<=a<=n,那么a-1从0一直到n-1让q取到了所有的n的因数,我们先把gcd(q,n/q)!=1的q全部舍去,那么剩下了几种q,对于每种q,若想找到一个存在其对应的p的a,就是找到一个含有其对应的p的a模q余1,也就是找到一个k,使得kp%q=1,而k的取值为[1,q],所以对于每一种合法q,都会找到一个互不相同的a,从而做出其对应的p的贡献.也就是说,如果我们放在p的角度上观察,就是对于每一个p,只要gcd(p,n/p)=1,就会做出p的贡献.至此,我们找到了一个新的计算R值的方法:R[n]=∑(d|n)[gcd(d,n/d)=1]*d.那么R[n]就相当于n的所有满足另一半与自己互质的约数和,利用这个我们显然可以用欧拉筛法O(n)得到所有的R,这样我们就得到了50分.
接下来,我们利用我们刚刚提到的式子进行一波反演,大力推一波式子,最后推出来的式子是要1到n^0.5枚举一个值并且每次对n除这个值的平方向下取整得到的数进行除法分块,据说这样的复杂度是O(n^0.5logn),反正可以过……
这道题对于数论推导能力,数论里许多性质的掌握以及化式子的能力考察得很好,是一道很好的题.
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=1000000,P=1005060097,inv2=502530049; #define mod(a) ((a)<P?(a):(a)%P) char mu[N+10]; int prime[N+10],len; bool isnot[N+10]; inline LL calc(LL lim){ LL ret=0; for(register LL i=1,last=1;i<=lim;i=last+1){ last=lim/(lim/i); ret=(ret+mod(i+last)*mod(last-i+1)%P*inv2%P*mod(lim/i)%P)%P; } return ret; } int main(){ register int i,j,k; mu[1]=1; for(i=2;i<=N;++i){ if(!isnot[i])prime[++len]=i,mu[i]=-1; for(j=1;prime[j]*i<=N;++j){ isnot[prime[j]*i]=true; if(i%prime[j]==0){mu[prime[j]*i]=0;break;} mu[prime[j]*i]=-mu[i]; } } LL n;int ans=0; scanf("%lld",&n); for(k=1;(LL)k*k<=n;++k) ans=(ans+mu[k]*k*calc(n/((LL)k*k))%P+P)%P; ans=(ans-(n%P)*((n+1)%P)%P*inv2%P+P)%P; printf("%d\\n",ans); return 0; }
No.11省选模拟测试
T1:
有一小点点小思想,就是一个简单的树状数组,就是用差分模拟一下模意义下的区间加和.
T2:
满脑子dp……
又是一道网络流思想的贪心,我好像一碰到什么和网络流有关的东西就不会……
只要把费用流建图想清楚,然后用线段树或者堆或者直接sort模拟一下就可以了……
网络流急需加强……我的贪心也好弱啊……
T3:
我写了一个题解上说的60分做法,然而利用矩阵里大部分都是空的,加一下矩阵乘法的优化就过了,还挺快.这种做法就是在原矩阵中加入记录前缀和的部分,在图的模型中就是引入黑洞点.
正解是利用折半的方法求等比数列,这个思想很不错,十分值得积累与借鉴.
这个折半和fft那个折半大异小同.
No.12省选模拟测试
T1:
类似PA round1里的清新简单小贪心.
T2:
我没有梦想……
先根据期望的线性性推出递推式x[i]=x[i+1]+x[i-1]-x[i],然后40分到手……
我到这儿就结束了……
但是实际上加了优化的矩阵乘法是完全可以跑出70分的,而且我考试的时候发现的全局循环节也可以拿到70分,然而我一个都没打,成了一条没有梦想的咸鱼……
正解是利用差分,将问题转化为置换的幂,然后利用轮换O(n)求出.
这道题对于期望线性性以及差分思想的考察很厉害,而且他对于置换群的考察也比较巧妙.
这个差分真的好厉害,我觉得这个递推式与差分的关系需要积累与借鉴,我感觉这个题里x[1],x[n]不变在一定程度上也有一些提示吧.
T3:
原题.
No.14省选模拟测试
这次考试的题都很不错诶……
一开考,看了一遍题.T1,博弈论,凑……T2,容斥计数???T3,数据结构题,好亲切啊,从他开始吧,想了半天,树套树、分块都不行,后来想到了一种set维护的莫队,码了出来,卡了卡常数,感觉50+没问题,就去想T2了.当时不知道为什么突然一阵困意来袭,状态急剧下降,出去上了一趟厕所,回来继续想T2的容斥,感觉麻烦得要死.突然级部召唤我,我去了一趟,没人……回来继续想,感觉好像会用到矩阵树……突然级部又召唤我,我又去了一趟,在那里填了个表,然后迅速回来,发现本来不足时的省选模拟测试被级部占掉了半个多小时……回来快速看了T1,大概想了一个好像并不对的做法,码了出来,竟然过了大样例,交之……然后去码T2的暴力,火速码完两个部分分,加起来30,还剩下不到15min,于是开始想下一个部分分,还是想容斥,还是觉得很麻烦,最后也没想出来……
最后80+30+20=130
T1,莫名奇妙地80,这个东西的思路就是状态转移.有两种方法,第一种是从已知状态向后逆转移,十分清真好理解,第二种是对于每一种状态去搜索其能到达的所有状态从而转移,这种方法十分玄学,而且还容易错,我现在也不是很懂他那个谜一样的环的处理办法……
我的博弈论好弱啊……
#include <cstdio> #include <cstring> #include <algorithm> #define read(a) (scanf("%d",&a)) const int N=7010; int n,st[2][N],degree[2][N],f[2][N],q[N<<1],front,back; int main(){ read(n); register int i; for(int id=0;id<2;++id){ read(st[id][0]); for(i=1;i<=st[id][0];++i) read(st[id][i]),st[id][i]=n-st[id][i]; for(i=1;i<=n;++i) degree[id][i]=st[id][0]; } f[0][1]=f[1][1]=-1; q[back++]=1,q[back++]=-1; register int x,y,u,v; while(front!=back){ x=q[front++]; if(x<0)y=-x,x=1,u=0; else y=x,x=0,u=1; if(f[x][y]>0) for(i=1;i<=st[u][0];++i){ v=(y+st[u][i]-1)%n+1; if(f[u][v])continue; if(!(--degree[u][v])) f[u][v]=-1,q[back++]=u?-v:v; } else for(i=1;i<=st[u][0];++i){ v=(y+st[u][i]-1)%n+1; if(f[u][v])continue; f[u][v]=1,q[back++]=u?-v:v; } } for(int id=0;id<2;++id){ for(i=2;i<=n;++i){ if(f[id][i]==1)printf("Win "); else if(f[id][i]==-1)printf("Lose "); else printf("Loop "); } puts(""); } return 0; }
T2,我没想出来的那个部分分完全可以用补集转化加矩阵树解决……正解好神啊,矩阵树加生成函数.结合基尔霍夫矩阵的那个结论,我们求出来的主子式行列式就是其所有生成树的边权之积的和.那么如果把原树边的值设为1,把非原树边的值设为x,那么我们的主子式行列式是一个多项式,其前k+1项的和,就是我们的答案.而直接这样求不是很好,那么我们就带入n个值求出对应的答案,利用拉格朗日插值或者高斯消元解方程组来得到多项式系数.
补集转化还真是妙啊……
矩阵树和多项式都是我理解不够深刻的知识点,像利用矩阵树计数,利用生成函数计数,都是我比较弱的地方,拉格朗日插值还是我现学的(现背结论的)……
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=85; const int P=1000000007; inline int Pow(int x,int y){ int ret=1; while(y){ if(y&1)ret=(LL)ret*x%P; x=(LL)x*x%P,y>>=1; } return ret; } inline int Inv(int x){ return Pow(x,P-2); } int n,cao,v[N][N],g[N],co[N],ans[N]; struct Matrix{ int a[N][N]; inline void clear(){ memset(a,0,sizeof(a)); } inline void add(int x,int y,int z){ a[x][x]+=z,a[y][y]+=z; a[x][y]-=z,a[y][x]-=z; } inline int kir(){ int ret=1,b,inv; register int i,j,k; for(i=1;i<n;++i) for(j=1;j<n;++j) a[i][j]=(a[i][j]%P+P)%P; for(i=1;i<n;++i){ if(a[i][i]==0){ for(j=i+1;j<n;++j) if(a[j][i]){ for(k=i;k<n;++k) std::swap(a[i][k],a[j][k]); break; } ret=(P-ret)%P; } inv=Inv(a[i][i]); for(j=i+1;j<n;++j){ b=(LL)a[j][i]*inv%P; for(k=i;k<n;++k) a[j][k]=(a[j][k]-(LL)b*a[i][k]%P+P)%P; } ret=(LL)ret*a[i][i]%P; } return ret; } }Kir; int main(){ scanf("%d%d",&n,&cao); register int i,j,k; for(i=1;i<n;++i){ scanf("%d",&j); v[i][j]=v[j][i]=1; } for(i=0;i<n;++i){ if(i==0){ co[i]=1; continue; } Kir.clear(); for(j=0;j<n;++j) for(k=j+1;k<n;++k) Kir.add(j,k,v[j][k]?1:i); co[i]=Kir.kir(); } int b,c,l; for(i=0;i<n;++i){ memset(g,0,sizeof(g)); b=co[i],l=1,g[0]=1; for(j=0;j<n;++j){ if(j==i)continue; b=(LL)b*Inv((i-j+P)%P)%P; c=(P-j)%P; for(k=l;k>0;--k) g[k]=((LL)g[k]*c%P+g[k-1])%P; g[0]=(LL)g[0]*c%P; ++l; } for(j=0;j<n;++j) ans[j]=(ans[j]+(LL)g[j]*b)%P; } int answer=0; for(i=0;i<=cao;++i) answer=(answer+ans[i])%P; printf("%d\\n",answer); return 0; }
T3,这题真好,只不过我的那30分呢,明明本机可以50的啊……以后还是把本机测试速度当作参考吧,实际复杂度才是最能说明问题的,毕竟评测环境不同,速度也不同嘛.在我的做法里把我用set搞的东西全部用链表处理出来就可以70了,再稍微卡卡常数(似乎还有分块排序这种卡常操作)就可以A掉了……利用KD_tree,把询问看作点,每个点看作对一个矩形的操作,应该就能50分,但是如果把点按照从大到小的顺序进行操作,再加上一些打标记的技巧,会更加得稳……正解的话,好神啊……我感觉这题在前两个部分分里加入权值随机的条件就有一些暗示……我们从点对的角度观察性质,或者说从一个点的贡献的角度,就会发现对于一个点i,大于它的j,可能和他做出贡献的j的a一定是单调的,而且对于值大于他的和值小于他的j,单调性不同.只是利用这个性质话,可以在随机权值中get到50分,因为据说随机权值下,对于一个点,他后面的点单调起来的个数是logw的.我们继续观察,同时强化我们的式子,就可以得到题解的最终的做法,十分好打.
这才叫数据结构题啊……
感觉用链表预处理前驱后继以代替平衡树是一种十分优秀的做法,很值得积累与借鉴……
还有这个KD_tree的姿势也是很牛逼……
正解就更不用说了,对着数据结构题推式子,利用权值,再加上利用约束的单调性等等……真是厉害啊……
#include <set> #include <cstdio> #include <cstring> #include <algorithm> char xB[(1<<15)+10],*xS,*xT; #define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++) template <typename _t> inline void read(_t &x){ register char ch=gtc;bool ud=false; for(x=0;ch<\'0\'||ch>\'9\';ch=gtc)if(ch==\'-\')ud=true; for(;ch>=\'0\'&&ch<=\'9\';x=(x<<1)+(x<<3)+ch-\'0\',ch=gtc); if(ud)x=-x; } typedef long long LL; typedef std::multiset<int> msi; typedef std::multiset<int>::iterator mit; const int L=150; const int C=100010; const int N=100010; const int M=1000010; const int oo=1000000000; const int Inf=0x7f7f7f7f; msi st; mit re[L<<1]; int len; int n,m,ans[M],a[N],pos[N],cnt,min; struct Q{ int l,r,id; }q[M]; inline bool comp(Q aa,Q bb){ return pos[aa.l]<pos[bb.l]||(pos[aa.l]==pos[bb.l]&&aa.r<bb.r); } inline mit ins(int key){ mit ret,tmp,it; ret=tmp=it=st.insert(key); ++tmp; min=std::min(min,std::abs(key-*tmp)); --it; min=std::min(min,std::abs(key-*it)); return ret; } inline int force_query(int l,int r){ if(r-l+1<=50){ register int i,j,ret=Inf; for(i=l;i<=r;++i) for(j=i+1;j<=r;++j) ret=std::min(ret,std::abs(a[i]-a[j])); return ret; } min=Inf,st.clear(); st.insert(Inf),st.insert(oo-Inf); for(register int i=l;i<=r;++i) ins(a[i]); return min; } i以上是关于省选模拟赛记录的主要内容,如果未能解决你的问题,请参考以下文章