2019-7-27 考试总结
A. 随 (rand)
这个题考场上连$n^2dp$都没想出来,
忽略"原根"这个东西,没有它也能$AC$,
$n^2$的$dp$其实很好想的,用$dp[i][j]$表示在$i$时间$x$的值为$j$的概率,
那么这个$dp[i][j\times k\% p]+=dp[i-1][j]\times sum[k]\times inv[n] \% mod$,
然后就转移就可以了。
之后就到了倍增。。
直接预处理$g[i][j]$表示$2^i$时间的时候$x$的值为$j$的概率。
那么$g$很好转移,$g[i][j\times k\% p]+=g[i-1][j]\times g[i-1][k] \%mod$,
然后查询$f$就好了。
丑陋的代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #define int long long #define mod 1000000007 #define Maxn 100050 #define Reg register using namespace std; long long n,m,p,inv,l,pow[1050],sum[1050]; long long g[150][1050],lss[5][1050]; long long mi(long long x,long long y,long long p) { long long ans=1,base=x; while(y) { if(y&1) ans=(ans*base)%p; base=(base*base)%p; y>>=1; } return ans; } signed main() { // freopen("text.in","r",stdin); // freopen("text2.out","w",stdout); scanf("%lld%lld%lld",&n,&m,&p); memset(g,0,sizeof(g)); for(Reg int i=1,x;i<=n;++i) { scanf("%lld",&x); x%=p; ++sum[x]; } inv=mi(n,mod-2,mod); pow[0]=1; for(Reg int i=1;i<=50;++i) pow[i]=pow[i-1]*2; //倍增预处理出2次的操作 for(Reg int i=1;i<p;++i) g[0][i]=sum[i]*inv%mod; for(Reg int i=1;i<p;++i) { for(Reg int j=1;j<p;++j) g[1][i*j%p]=(g[1][i*j%p]+g[0][i]*sum[j]%mod*inv%mod)%mod; } //开始求g数组 for(Reg int i=2;i<=50;++i) { for(Reg int j=1;j<p;++j) { for(Reg int k=1;k<p;++k) { if(j*k%p==0) continue; long long lll=(g[i-1][j]*g[i-1][k])%mod,o=j*k%p; g[i][o]=(g[i][o]%mod+lll)%mod; } } } //求f[m][] for(Reg int i=1;i<p;++i) lss[0][i]=(lss[0][i]+sum[i]*inv%mod)%mod; --m; long long k=0,sup=0; for(Reg int i=50;i>=0;--i) { if(pow[i]+k<=m) { k+=pow[i]; ++sup; int cur=sup&1,pre=(sup+1)&1; memset(lss[cur],0,sizeof(lss[cur])); for(Reg int j=1;j<p;++j) for(Reg int k=1;k<p;++k) lss[cur][j*k%p]=(lss[cur][j*k%p]%mod+(lss[pre][j]*g[i][k])%mod)%mod; } } long long ans=0; for(Reg int i=1;i<p;++i) ans=(ans+lss[sup&1][i]*i%mod)%mod; printf("%lld",ans); return 0; }
B. 单(single)
完全想不到
如果$opt=0$,那么进行$2$遍$dfs$,
第一遍求出根节点$1$的$b$值,第二遍由$1$节点递推到其他节点。
对于节点$i$,$b[i]=\sum a[j]\times dis[j][i]$,
那么对于$i$的儿子节点$x$,$x$的儿子节点到$x$的距离肯定比到$i$的距离小$1$,
然后其他节点差不多。
最后时间复杂度$O(n)$。
如果$opt=1$,
首先$b[i]-b[fat(i)]=SUM-2\times sum(i)$-----(*),
其中:
$SUM=\sum \limits_{i=1}^{n} a[i]$,
$sum(i)=\sum a[j]$,其中$j$是$i$的儿子节点。
这个可以感性的理解一下,
对于$i$节点的儿子节点(包括$i$节点),它到$i$节点的距离要比对$fat(i)$的距离少$1$,
所以它对$i$节点的贡献要少$a[j]$,所以要减去$sum(i)$。
而对于不是$i$节点的儿子节点的节点,它肯定相反,贡献要多$a[j]$,所以要加上$SUM-sum(i)$,
最后就是$SUM-2\times sum(i)$了。
然后列出了$n-1$个式子,就是那个(*)式子。
还要列最后一个式子$b[1]=\sum \limits_{i=2}^{n} sum(i)$,这个就是所有节点对根节点的贡献。
好了,列完式子了。。。
然后先把那$n-1$个式子相加:$\sum \limits_{i=2}^{n} b[i]-b[fat(i)]=(n-1)SUM-2\times \sum \limits_{i=2}^{n} sum(i)$,
然后就惊喜地发现这个右边的那一堆$\sum$就是$2\times b[1]$,
然后两边都加上$2\times b[1]$,
最后就是$2\times b[i]+\sum \limits_{i=2}^{n} b[i]-b[fat(i)]=SUM$,
然后就把$SUM$求出来了。
带入那$n-1$个式子可以得到$sum(i)$,不包括根节点$1$,
然后就树上差分了。
最后$a[1]$可以用总的$SUM$直接减去其他的。
丑陋的代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<vector> #define fabs(x) ((x)<0?(-1*(x)):(x)) #define Maxn 100050 #define INF 0x7ffffff #define int long long #define mod 1000000007 #define Reg register using namespace std; int A[Maxn],B[Maxn]; int t,n,m,opt,tot,fir[Maxn],vis[Maxn],size[Maxn],sigA[Maxn]; int sum[Maxn],SUM,S,lss; struct Tu {int st,ed,next;} lian[Maxn*2]; vector<vector<int> >son(Maxn); void add(int x,int y) { lian[++tot].st=x; lian[tot].ed=y; lian[tot].next=fir[x]; fir[x]=tot; return; } void dfs(int x,int deep) //先1遍dfs求所有点对根节点1的贡献 { size[x]=1; B[1]+=A[x]*deep; sigA[x]=A[x]; vis[x]=1; for(Reg int j=fir[x];j;j=lian[j].next) { if(vis[lian[j].ed]) continue; son[x].push_back(lian[j].ed); dfs(lian[j].ed,deep+1); size[x]+=size[lian[j].ed]; sigA[x]+=sigA[lian[j].ed]; } return; } void dfs_re(int x) //第2遍dfs求所有点的A值 { for(Reg int i=0,to;i<son[x].size();++i) { to=son[x][i]; B[to]=B[x]-sigA[to]+sigA[1]-sigA[to]; dfs_re(son[x][i]); } return; } //每一个节点 b[i]-b[fat(i)]=SUM-2sum(i) //b[1]=sigma(sum(2)~sum(n)) //n-1个式子相加: //(n-1)SUM-2sigma(sum(2)~sum(n))=sigma(b[2]-b[fat(2)]~b[n]-b[fat(n)]) // =S //加上2b[1]+S=(n-1)SUM //得到(n-1)SUM,得到SUM=(2b[1]+S)/(n-1) //带入原n-1个式子 //得到2sum(i),得到sum(i) void dfs2(int x) { vis[x]=1; for(Reg int i=fir[x];i;i=lian[i].next) { if(vis[lian[i].ed]) continue; son[x].push_back(lian[i].ed); dfs2(lian[i].ed); S+=B[lian[i].ed]-B[x]; } return; } void dfs2_re(int x) { int sup=0; for(Reg int i=0;i<son[x].size();++i) { int to=son[x][i]; sum[to]=(SUM-(B[to]-B[x]))/2; dfs2_re(to); sup+=sum[to]; } A[x]=sum[x]-sup; if(x!=1) lss+=A[x]; return; } signed main() { // freopen("text.in","r",stdin); scanf("%lld",&t); while(t--) { lss=tot=S=SUM=0; scanf("%lld",&n); for(Reg int i=1;i<=n;++i) { fir[i]=sum[i]=A[i]=vis[i]=B[i]=size[i]=sigA[i]=0; son[i].clear(); } for(Reg int i=1,x,y;i<=n-1;++i) { scanf("%lld%lld",&x,&y); add(x,y); add(y,x); } scanf("%lld",&opt); if(opt==0) { for(Reg int i=1;i<=n;++i) scanf("%lld",&A[i]); dfs(1,0); dfs_re(1); for(Reg int i=1;i<=n;++i) printf("%lld ",B[i]); printf("\n"); } else { for(Reg int i=1;i<=n;++i) { scanf("%lld",&B[i]); SUM+=B[i]; } dfs2(1); SUM=(S+2*B[1])/(n-1); dfs2_re(1); A[1]=SUM-lss; for(Reg int i=1;i<=n;++i) printf("%lld ",A[i]); printf("\n"); } } return 0; }
C. 题(problem)
这个题之前做过第一种情况,所以拿分比较容易,
第一种情况$opt=0$,没有限制,
直接枚举向上走的步数,然后其他三种情况可以表示出来,
这样最后就是$\sum \limits_{a,b,c,d}^{a,b,c,d>=0} C_{n}^{a}C_{n-a}^{b}C_{n-a-b}^{c}$,
直接求就可以了。
第二种情况$opt=1$,只能走$x$轴非负半轴,显然是个卡特兰数。(然而考试时想了半天)
直接卡特兰数求解就可以了,
最后就是$C_n^{n/2}-C_n^{n/2-1}$,
第三种情况$opt=3$,限制只能走坐标轴。
这个比较麻烦,然后考试时输出样例水到$5$分。
正解是$dp$,$F[i]=\Pi F[i-j]Cat(j/2-1)$,(然而蒟蒻并不知道为啥$Cat$要减一)
经过$scp-007$大神的指导,本蒟蒻终于理(yi)解(yin)出这个$dp$式子。
因为卡特兰数的这个栈是可以弹空的。。所以要先移动到$1$,然后再把$1$当作坐标轴继续移动。
对,就这么简单。
第四种情况也很简单,只能走第一象限和坐标轴非负半轴,
这个我考试的时候想到的是一个小容斥,具体看代码吧。
ans=(ans+C(n,a)*C(n-a,b)%mod*C(n-a-b,c)%mod)%mod; ans=(ans-C(n,a)*C(n-a,b)%mod*C(n-a-b,(n-a-b)/2-1)%mod+mod)%mod; ans=(ans-C(n,a+1)*C(n-a-1,b-1)%mod*C(n-a-b,c)%mod+mod)%mod; ans=(ans+C(n,a+1)*C(n-a-1,b-1)%mod*C(n-a-b,(n-a-b)/2-1)%mod)%mod;
丑陋的代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<cstdlib> #include<ctime> #define abs(x) ((x)<0?(-1*(x)):(x)) #define C(x,y) (jc[x]*ny[y]%mod*ny[(x)-(y)]%mod) #define Cat(x) ((C(2*(x),(x))-C(2*(x),(x)-1)+mod)%mod) #define Maxn 200050 #define INF 0x7ffffff #define mod 1000000007 #define Reg register using namespace std; int n,typ,st; long long jc[Maxn],ny[Maxn],ans,dp[Maxn]; long long mi(long long x,long long y,long long p) { long long ans=1,base=x; while(y) { if(y&1) ans=(ans*base)%p; base=(base*base)%p; y>>=1; } return ans; } void dfs(int x,int y,int tim) { if(tim==n) { if(x==0&&y==0) ++ans; return; } if(x-1==0||y==0) dfs(x-1,y,tim+1); if(x==0||y-1==0) dfs(x,y-1,tim+1); if(x+1==0||y==0) dfs(x+1,y,tim+1); if(x==0||y+1==0) dfs(x-1,y,tim+1); } int main() { scanf("%d%d",&n,&typ); ans=0; jc[0]=ny[0]=jc[1]=ny[1]=1; for(Reg int i=2;i<=n*2;++i) { jc[i]=jc[i-1]*i%mod; ny[i]=mi(jc[i],mod-2,mod); } if(typ==0) { for(Reg int i=0,a,b,c,d;i<=n;++i) { a=i,b=a,c=(n-2*a)/2,d=c; if(a<0||b<0||c<0||c<0) break; ans=(ans+C(n,a)*C(n-a,b)%mod*C(n-a-b,c)%mod)%mod; } printf("%lld",ans); } else if(typ==1) { ans=(C(n,n/2)-C(n,n/2-1)+mod)%mod; printf("%lld",ans); } else if(typ==2) { dp[0]=1,dp[2]=4; for(Reg int i=4;i<=n;i+=2) { for(Reg int j=2;j<=i;j+=2) dp[i]=(dp[i]+dp[i-j]*Cat(j/2-1)%mod)%mod; dp[i]=dp[i]*4%mod; } printf("%lld",dp[n]); } else if(typ==3) { for(Reg int i=0,a,b,c,d;i<=n;++i) { a=i,b=a,c=(n-2*a)/2,d=c; if(a<0||b<0||c<0||c<0) break; ans=(ans+C(n,a)*C(n-a,b)%mod*C(n-a-b,c)%mod)%mod; ans=(ans-C(n,a)*C(n-a,b)%mod*C(n-a-b,(n-a-b)/2-1)%mod+mod)%mod; ans=(ans-C(n,a+1)*C(n-a-1,b-1)%mod*C(n-a-b,c)%mod+mod)%mod; ans=(ans+C(n,a+1)*C(n-a-1,b-1)%mod*C(n-a-b,(n-a-b)/2-1)%mod)%mod; } printf("%lld",ans); } return 0; }
总结:
考试一开始先看的$T1$,这。。样例是什么鬼
还有下边的“孙金宁教你学数学”,然后就感觉有点炸
赶紧跳到$T2$,首先第一眼,高斯消元,然后就打了一个$O(n^3)$的暴力,水到$30$分,
之后没多想去看$T3$,看到原题很开心。。$0,1,3$都非常简单,然后是$2$的那个点,
其实数据范围已经给了一些提示。。
然而我一直在想用组合数求解,最后依然没想出来,打了一个$dfs$,输出样例,水到$80$分。
考试还剩$40$多分钟,然后就回头看$T1$,样例模不对,然后一直在想这个样例怎么模,顺便打了一个$n^2$暴力,
直到最后,$wd$大神才公布这个样例不对。。
当时不到$10:20$,最后把那个$n^2$暴力交了上去,水到$10$分。
所以最后$10+30+80=120$。
没什么水平。。