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那个折半大异小同.