6.10 考试修改+总结+颓废记
昨天晚上得到了非常不爽的消息,zcg要去给高一讲课,而我并不能去
虽然什么事情并不能都顺着我的心意来吧,但是这件事情真是让人越想越不痛快
要知道,我从去年就一直期待着给高一讲课呢
所以今天考试非常不开心,一般这个时候我会选择爆零的
但是想了想觉得爆零太难看,就看了看好像第一题可做
在教学楼里颓废了好久然后吃了点东西,用最后的时间码完了第一题
(反正二、三题我没看出来怎么做,所以暴力也不想写了
然后惊讶的是,只有第一题程序的窝rank1了QAQ
先放题解吧
第一题:
首先我们注意到转置的实质是某个数循环右移a位
问题就转化成了给定你一个置换,求循环节个数
但是这个置换的元素个数很多,所以我们可以对他们进行分类统计
即按循环节长度分类,问题就转化成了计数问题
那么我们枚举循环节长度,很容易发现循环节长度一定是a+b的约数
就会有LCM(a+b,k*a)个等价类,每个等价类有两种选择
即2^LCM(a+b,k*a),但是我们会发现这部分方案还包含k的约数
容斥一下就可以了
我的程序实际上有更快的方法,即考虑每部分对答案的贡献系数实际上是约数个数函数
线性筛出约数个数函数可以变快了QAQ
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; const int mod=1000000007; const int maxn=2000010; int xp[maxn],st[maxn],top=0; int vis[maxn],pos[maxn],tot=0,tim=0; int Ans[maxn]; int T,n,m,N,lim,ans; LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);} int pow_mod(int v,int p){ int tmp=1; while(p){ if(p&1)tmp=1LL*tmp*v%mod; v=1LL*v*v%mod;p>>=1; }return tmp; } int main(){ freopen("matrix.in","r",stdin); freopen("matrix.out","w",stdout); scanf("%d",&T); xp[0]=1; for(int i=1;i<=2000000;++i){ xp[i]=(xp[i-1]<<1); if(xp[i]>=mod)xp[i]-=mod; } while(T--){ scanf("%d%d",&n,&m); if(n==0&&m==0){printf("0\n");continue;} N=n+m;N/=gcd(n,N); lim=(int)(sqrt(N));top=0; tim++;tot=0;ans=0; for(int i=1;i<=lim;++i){ if(N%i==0){ st[++top]=i; vis[i]=tim; if(i*i!=N){ int cur=N/i; st[++top]=cur; vis[cur]=tim; } } } sort(st+1,st+top+1); for(int i=1;i<=top;++i)Ans[i]=0; for(int i=1;i<=top;++i){ Ans[i]=Ans[i]+xp[gcd(n+m,1LL*st[i]*n)]; if(Ans[i]>=mod)Ans[i]-=mod; for(int j=i+1;j<=top;++j){ if(st[j]%st[i]==0){ Ans[j]-=Ans[i]; if(Ans[j]<0)Ans[j]+=mod; } } Ans[i]=1LL*Ans[i]*pow_mod(st[i],mod-2)%mod; ans+=Ans[i];if(ans>=mod)ans-=mod; }ans=1LL*xp[n]*xp[m]%mod-ans; if(ans<0)ans+=mod; printf("%d\n",ans); }return 0; }
第二题:
上午完全没有读懂第二题在说啥
题解很神
首先很重要的一点是实际上答案是个定值,所以最小化什么的不用管QAQ
我们注意到我们每一次移动之后所有的数的下标和不变
而下标的平方和+2,那么我们就可以通过下标和来逐步确定最终状态
而且可以利用下标的平方和来统计答案
首先考虑单个添加,不难发现如果原位置没有,则直接添加即可
否则会使得左右区间分裂,且左区间左端点-1,右区间右端点+1
那么很容易统计出现在的下标和,用现在的下标和减去之前的下标和就是分裂点
由于p是递增的,所以用单调栈模拟就可以了
还有一种做法是考虑一次性添加一个位置的点
由上面可以推论,如果这个位置有奇数个点,则区间最终不会分裂
如果有偶数个点,则区间会分裂,且分裂点为当前点
之后我们用单调栈维护区间合并,每次合并区间的时候很容易统计出下标和S以及下标平方和
还可以统计区间中数的个数K
那么设区间的左端点为L
很容易知道(L+L+K-1)*K/2<=S<(L+L+K+1)*K/2
又因为L是正整数,可以通过这个式子直接解出L
但是注意在程序中的除法是向下取整,所以正负数要分类讨论
然后继续可以解出分裂点和右端点R
吐槽一句:感觉考试的时候完全想不到维护平方和来计算答案啊,可是想不到这个就只能QAQ了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> using namespace std; typedef long long LL; int T,n,p,q,top; LL ans; LL S(int x){return 1LL*x*(x+1)*((x<<1)|1)/6;} struct Node{ int L,R,x; Node(int L=0,int R=0,int x=0):L(L),R(R),x(x){} LL val(){ if(L>0)return S(R)-S(L-1)-1LL*x*x; else if(R>=0)return S(R)+S(abs(L))-1LL*x*x; else return S(abs(L))-S(abs(R)-1)-1LL*x*x; } }st[2000010],now; Node operator +(Node A,Node B){ Node tmp; int N=A.R-A.L+B.R-B.L; LL S=1LL*(A.R+A.L)*(A.R-A.L+1)/2+1LL*(B.R+B.L)*(B.R-B.L+1)/2-A.x-B.x; if(2*S-1LL*N*N+N>0)tmp.L=(2*S-1LL*N*N+N)/(2*N); else tmp.L=(2*S-1LL*N*N-N)/(2*N); tmp.R=tmp.L+N; tmp.x=1LL*(tmp.L+tmp.R)*(tmp.R-tmp.L+1)/2-S; ans=ans+(tmp.val()-A.val()-B.val())/2; return tmp; } int main(){ freopen("game.in","r",stdin); freopen("game.out","w",stdout); scanf("%d",&T); while(T--){ scanf("%d",&n);top=0;ans=0; for(int i=1;i<=n;++i){ scanf("%d%d",&p,&q); if(q&1)now=Node(p-q/2,p+q/2+1,p+q/2+1); else now=Node(p-q/2,p+q/2,p); ans=ans+(now.val()-1LL*p*p*q)/2; while(top&&st[top].R>=now.L){ now=st[top]+now; top--; }st[++top]=now; }printf("%lld\n",ans); }return 0; }
第三题:
第三题至今还在卡常数ing
但是没有关系,用了zcg的卡常大法水过去了
最后还是羞耻的去掉了
自己的概率DP太弱,考试的时候虽然懒得写暴力,但是想的时候也只能想出暴力
完全没有想到概率DP的做法,不过貌似想到了我就能看出可以用FFT优化了QAQ
(请叫我熟练的FFT工人,我一定是被zcg带坏了)
设f(i,j)表示i这个点还剩j时间的时候的最小费用
这个状态都是老生常谈,但是因为概率在边上非常难转移
关键是下面,设g(i,j)表示到了i这条边还剩j时间的时候的最小费用
这样的话,不难发现当j<=0的时候f(i,j)=dis(i->n)+fine
否则f(i,j)=min(g(k,j)) (i不等于n)
而对于g(k,j)的转移我们发现实际上把所以这条边用的时间的情况讨论完求sigma就可以了
时间瓶颈在于求g(k,j)
g(k,j)=val(k)+sigma(p(k,t)*f(u,j-t))
不难发现后面是个卷积形式,分治FFT优化一下即可
看完题解就直接麻麻麻,结果一直WA,后来发现有个地方要特判一下是不是等于n QAQ
感觉考场上如果真写这道题目自己药丸啊
#include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> #include<cstring> #include<cmath> #include<vector> using namespace std; const int oo=0x7fffffff/3; const int maxn=102; const double pi=acos(-1.0); int n,m,T,F,u,v,N,len; int dis[maxn][maxn]; int w[maxn]; int h[maxn],cnt=0; double p[52][20010]; double S[52][20010]; double f[52][20010]; double g[52][20010]; struct edge{ int to,next,w; }G[102]; struct cpx{ double r,i; cpx(double r=0,double i=0):r(r),i(i){} }A[100010],B[100010],C[100010]; int rev[100010]; vector<int>V[maxn]; cpx operator +(const cpx &A,const cpx &B){return cpx(A.r+B.r,A.i+B.i);} cpx operator -(const cpx &A,const cpx &B){return cpx(A.r-B.r,A.i-B.i);} cpx operator *(const cpx &A,const cpx &B){return cpx(A.r*B.r-A.i*B.i,A.r*B.i+A.i*B.r);} void add(int x,int y,int z){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt; } void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } void floyd(){ for(int i=1;i<=n;++i)dis[i][i]=0; for(int k=1;k<=n;++k){ for(int i=1;i<=n;++i){ for(int j=1;j<=n;++j){ dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); } } }return; } void Get_S(){ for(int i=1;i<=m;++i){ int v=G[i].to; for(int j=T;j>=1;--j){ S[i][j]=p[i][j]*(dis[v][n]+F)+S[i][j+1]; } }return; } void FFT(cpx *A,int n,int type){ for(int i=0;i<n;++i)C[i]=A[rev[i]]; for(int i=0;i<n;++i)A[i]=C[i]; for(int i=2;i<=n;i<<=1){ cpx wn(cos(2*pi/i),sin(2*pi/i)*type); int mi=(i>>1); for(int j=0;j<n;j+=i){ cpx w(1,0); for(int k=0;k<mi;++k){ cpx x=A[k+j],y=A[k+j+mi]*w; A[k+j]=x+y;A[k+j+mi]=x-y; w=w*wn; } } } if(type==-1){for(int i=0;i<n;++i)A[i].r/=n;} return; } void Solve(int L,int R){ if(L==R){ for(int i=1;i<=m;++i){ g[i][L]+=w[i]; if(G[i].to!=n)g[i][L]+=S[i][L]; else g[i][L]+=S[i][L+1]; } for(int i=1;i<n;++i){ f[i][L]=1e18; for(int j=h[i];j;j=G[j].next){ f[i][L]=min(f[i][L],g[j][L]); } }return; } int mid=(L+R)>>1; Solve(L,mid); for(N=1,len=0;N<(R-L+1);N<<=1,len++);N<<=1,len++; for(int i=0;i<N;++i)rev[i]=rev[i>>1]>>1|((i&1)<<(len-1)); for(int u=1;u<n;++u){ for(int j=0;j<V[u].size();++j){ int v=V[u][j]; for(int i=0;i<N;++i)A[i]=B[i]=cpx(); for(int i=0;i<(N>>1);++i)A[i].r=p[v][i+1]; for(int i=L;i<=mid;++i)B[i-L].r=f[u][i]; FFT(A,N,1);FFT(B,N,1); for(int i=0;i<N;++i)A[i]=A[i]*B[i]; FFT(A,N,-1); for(int i=mid+1;i<=R;++i)g[v][i]+=A[i-L-1].r; } } Solve(mid+1,R); } int main(){ freopen("girls.in","r",stdin); freopen("girls.out","w",stdout); read(n);read(m);read(T);read(F); for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)dis[i][j]=oo; for(int i=1;i<=m;++i){ read(u);read(v);read(w[i]); add(u,v,w[i]); dis[u][v]=w[i]; V[v].push_back(cnt); for(int j=1;j<=T;++j){ read(u); p[i][j]=u*0.00001; } }floyd();Get_S();Solve(1,T); printf("%.10lf\n",f[1][T]); return 0; }
今天考试没什么可以说的
自己心情不太好,没有认真的去对待
不过至今还是对自己rank1的事情表示惊奇
Em 发泄一下心情就好了,以后的考试还是要好好考的QAQ
不过大致想一想,就算自己第二题和第三题写了也不过是30的暴力分
自己还是太弱了QAQ
没有想出正解的原因是第二题没有仔细模拟过程并探究性质
第三题没有想到可以对边做DP
一些坑:概率DP,FFT专项