这次考试嗅到了达哥的杀气。。。这是第一次带题的考试总结,毕竟达哥的香还是要上一柱的。。。
首先做一下总结,这次考试T1有判的10分没有拿到,T2有高斯消元板子打错只有40分,扔掉20,然后 T3的最后25分不会,实在没想到三个组合数中间加一个DP。。。推了半天式子也没头绪。。。反正能拿的分基本都拿到了10+40+75=125。。。看起来我还是太辣鸡了,整场考试一半分都拿不了。。
然后是题目,这次提高还是很大的。。。
T1:再次申明一个点期望的乘积不是乘积的期望。。。粘一发百度
我们能接触到的期望DP中可以转移,当期望结果可以相加减的时候才成立,比如买可乐,等概率赠一种龙珠,集齐7颗不同的召唤神龙,问你期望次数,显然次数是加减,如果用定义来求$p(1)*1+p(2)*2+cdots$不用多说,我们用DP来求的话,集齐x颗的期望次数$p(1)*1+p(2)*2+cdots$加在此基础上加某一颗的次数和概率的$frac{7-x}{7}*(p(1)*1+p(2)*2+cdots)$,就能转移到集齐x颗的期望次数,能转移的根源就是乘法分配律,这样就能在最后的状态也保留$p(1)*1+p(2)*2+cdots$的形式,那在这个题中我们搞每一次操作的期望不能通过上一次的期望值乘上这一次的期望值得到,鬼知道你的概率成了几次方。那么遇到这种题怎么办呢?把概率和期望值分开,最后乘一次计算答案,$f(i,j)$表示第i次操作得到j的概率,那么转移显然可以最外层把次数作为阶段,然后把当前状态的j值枚举,再枚举序列中的数,转移稍简单,当然我们发现序列远长于mod,而我们不关注序列,序列中值域又在1~mod-1之间,那么直接把枚举序列的数变成枚举序列的值域,转移一样简单。那么这个题其实到这就是我的范围了,在往后的倍增优化还没接触过,什么原根循环矩阵更是听都没听过。。。然后我们稍看一下,其实这种不算小数的期望一般都是骗人的,还没乘逆元的时候就是方案数,那么我们最后再乘,$f[1][]$这个数组观察一下发现它其实就是序列里数出现的个数,那么这样我们改变一下定义,$f(i,j)$表示i个数相乘的j的概率,其实这种放回的选择,8次1个和1次8个是一样的,因为假设一次可以选重的,那么这m次就可以不用一次一次加过来,思路逐渐清奇。。。。还记得蒙哥马利高效算法不,好吧就是快速幂,它就是从1次1次乘变成1坨1坨乘,那么这个题就可以解决了,和矩阵乘法优化的思路一样,通过减少低效的乘法直接把次数log掉,刚刚那个8次1个等效于1次8个就是满足结合律。
#include<cstring> #include<iostream> #include<cstdio> using namespace std; const int MOD=1e9+7,N=1e5+20,MD=1020; int a[N],f[MD],tmp[MD],g[MD],w[MD]; int rd() { char cc=getchar(); int s=0,w=1; while(cc<'0'||cc>'9') {if(cc=='-')w=-1;cc=getchar();} while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar(); return s*w; } int qpow(int a,int k,int mod) { int ans=1; for(;k;k>>=1,a=1ll*a*a%mod) if(k&1) ans=1ll*ans*a%mod; return ans; } void pow(int mod) { memset(tmp,0,sizeof(tmp)); for(int j=0;j<mod;j++) for(int k=0;k<mod;k++) tmp[j*k%mod]=(tmp[j*k%mod]+1ll*f[j]*f[k]%MOD)%MOD; memcpy(f,tmp,sizeof(tmp)); } int main() { int n=rd(),m=rd(),mod=rd(),p; long long s=0,ans=0,sum; for(int i=1;i<=n;i++) f[rd()]++; int inv_n=qpow(n,m,MOD),k=m,op=1; inv_n=qpow(inv_n,MOD-2,MOD); g[1]=1; for(;k;k>>=1) { if(k&1) { memset(tmp,0,sizeof(tmp)); for(int j=0;j<mod;j++) for(int k=0;k<mod;k++) tmp[j*k%mod]=(tmp[j*k%mod]+1ll*g[j]*f[k]%MOD)%MOD; memcpy(g,tmp,sizeof(tmp)); } pow(mod); } for(int j=0;j<mod;j++)ans=(ans+1ll*j*g[j]%MOD)%MOD; ans=1ll*ans*inv_n%MOD; printf("%lld ",ans); } /* g++ 1.cpp -o 1 ./1 3 5 100 1 2 3 */
T2:这是一道高考数学题,给定数列前缀和和前缀前缀和的关系求解数列递推式。。。。我们先看题,给a求b就是没手也会打。。两次dfs直接搞掉。给b求a???!!!略加思索,元,对不起了。。高斯!消它!然后板子没背过,多一分都不行。然后考虑链上给a求b,显然这种两端的答案要考虑边的贡献,参见本人两个HAOI的题,显然得到$b[i]=s[1]+s[2]+cdots+s[i-1]+p[i+1]+cdots+p[n]$,s,p分别是a的前缀和和后缀和,那么,反过来,给b求a,观察一下求$b[i+1]$的,和$b[i]$做差,就能得到$b[i+1]-b[i]=s[i]-p[i+1]$,????s和p相减,前缀和和后缀和相减???他俩相加我是知道的,就是s[n],那么手代一下 吧,$2*s[i-1]+b[i-1]-b[i]=s[n]$,呀,有戏,高考数学套路,b是s的前缀和级别,多写两项$2*[n-1]+b[n-1]-b[n]=s[n]$,$2*s[1]+b[1]-b[2]=s[n]$,直接$n-1$项相加得到$2(s[1]+s[2]+cdots+s[n-1])+b[1]-b[n]=(n-1)s[n]$
左边那一坨就是$b[n]$,$s[n]$可求,直接递推,然后差掉出a,树上就是看官自己思考的结果啦。。其实也是一个思路,换成dfs序就行了。。。。
#include<iostream> #include<cmath> #include<cstdio> #include<cstring> #define int long long using namespace std; const int N=200020; int a[N],b[N],f[N],fr[N*2],s[N],p[N],ss[N],pp[N],s1,s2,fa[N],n,tt; double d[2020][2020]; struct node{int fr,to,pr;}mo[N*2]; int rd() { char cc=getchar(); int s=0,w=1; while(cc<'0'||cc>'9') {if(cc=='-')w=-1;cc=getchar();} while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar(); return s*w; } void add(int x,int y) { mo[++tt].fr=x; mo[tt].to=y; mo[tt].pr=fr[x]; fr[x]=tt; } void dfs1(int x) { s[x]=a[x]; for(int i=fr[x];i;i=mo[i].pr) { int to=mo[i].to; if(to==fa[x]) continue; fa[to]=x; dfs1(to); s[x]+=s[to]; f[x]+=f[to]+s[to]; } } void dfs2(int x) { for(int i=fr[x];i;i=mo[i].pr) { int to=mo[i].to; if(to==fa[x]) continue; b[to]=b[x]+s[1]-2*s[to]; dfs2(to); } } void dfs3(int x) { for(int i=fr[x];i;i=mo[i].pr) { int to=mo[i].to; if(to==fa[x]) continue; fa[to]=x; s1+=b[to];s2+=b[x]; dfs3(to); } } void dfs4(int x) { for(int i=fr[x];i;i=mo[i].pr) { int to=mo[i].to; if(to==fa[x]) continue; s[to]=(s[1]-b[to]+b[x])/2; dfs4(to); } } void dfs5(int x) { for(int i=fr[x];i;i=mo[i].pr) { int to=mo[i].to; if(to==fa[x]) continue; s[x]-=s[to]; dfs5(to); } } void dfs6(int x,int rt,int fa) { for(int i=fr[x];i;i=mo[i].pr) { int to=mo[i].to; if(to==fa) continue; d[rt][to]=d[rt][x]+1; dfs6(to,rt,x); } } void solve() { for(int i=1;i<=n;i++) d[i][n+1]=b[i]; for(int i=1;i<=n;i++) { int k=i; for(int j=i;j<=n;j++) if(fabs(d[k][i])<fabs(d[j][i])) k=j; for(int j=1;j<=n+1;j++) swap(d[k][j],d[i][j]); double res=d[i][i]; if(fabs(res)<1e-10) continue; for(int j=1;j<=n+1;j++) d[i][j]/=res; for(int j=1;j<=n;j++) if(i!=j) { double s=d[j][i]; for(int k=1;k<=n+1;k++) d[j][k]-=d[i][k]*s; } } } signed main() { int T=rd(); while(T--) { tt=0;bool flag=1; memset(fr,0,sizeof(fr)); memset(mo,0,sizeof(mo)); memset(f,0,sizeof(f)); memset(fa,0,sizeof(fa)); memset(a,0,sizeof(a)); memset(s,0,sizeof(s)); memset(d,0,sizeof(d)); memset(p,0,sizeof(p)); memset(ss,0,sizeof(ss)); memset(pp,0,sizeof(pp)); memset(b,0,sizeof(b)); n=rd(); for(int i=1,x,y;i<n;i++) { x=rd(),y=rd(); add(x,y);add(y,x); if(y!=x+1&&x!=y+1)flag=0; } int jud=rd(); if(flag==0) { if(n<=1000) { if(jud==0) { for(int i=1;i<=n;i++) a[i]=rd(); dfs1(1); b[1]=f[1]; dfs2(1); for(int i=1;i<=n;i++) printf("%d ",b[i]); puts(""); } else { for(int i=1;i<=n;i++) b[i]=rd(); for(int i=1;i<=n;i++) dfs6(i,i,0); solve(); for(int i=1;i<=n;i++) printf("%.0lf ",d[i][n+1]); puts(""); } } else { if(jud==0) { for(int i=1;i<=n;i++) a[i]=rd(); dfs1(1); b[1]=f[1]; dfs2(1); for(int i=1;i<=n;i++) printf("%d ",b[i]); puts(""); } else { s1=0;s2=0; for(int i=1;i<=n;i++) b[i]=rd(); dfs3(1); s[1]=(s1-s2+2*b[1])/(n-1); dfs4(1); dfs5(1); for(int i=1;i<=n;i++) printf("%d ",s[i]); puts(""); } } } else { if(jud==0) { for(int i=1;i<=n;i++) a[i]=rd(); for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i],ss[i]=ss[i-1]+s[i]; for(int i=n;i>=1;i--) p[i]=p[i+1]+a[i],pp[i]=pp[i+1]+p[i]; for(int i=1;i<=n;i++) printf("%d ",ss[i-1]+pp[i+1]); puts(""); } else { for(int i=1;i<=n;i++) b[i]=rd(); s[n]=(b[n]+b[1])/(n-1); for(int i=1;i<n;i++) s[i]=(s[n]+b[i+1]-b[i])/2; for(int i=1;i<=n;i++) printf("%d ",s[i]-s[i-1]); puts(""); } } } } /* g++ 2.cpp -o 2 ./2 2 9 1 2 1 3 2 4 2 5 3 6 3 9 6 7 6 8 0 2 2 2 1 3 2 5 2 7 9 1 2 1 3 2 4 2 5 3 6 3 9 6 7 6 8 1 51 65 41 89 85 49 65 71 53 */
T3:qj测试点好题,考场上最后半小时就在j测试点,j一个25,再一个50,又一个75。。。。然后这就是一个组合数+卡特兰,主要说一下只能在坐标轴上的情况,这是一个DP,这也是那种枚举决策点的,因为不知道j步回来经过几次原点,我先假设不回来,那可以用走到1然后卡特兰求,然后枚举回来的点用DP把他们合并,记着四个坐标轴要乘四,状态定义为走j步回来的方案数,式子就是$f[i]=sum f[i-j]*4*catlan((j-2)/2)$。别的都是比较好推的组合数,不多bb
#include<iostream> #include<cstdio> using namespace std; #define int long long const int mod=1e9+7,N=100020; int fac[N],inv[N],f[N]; int rd() { char cc=getchar(); int s=0,w=1; while(cc<'0'||cc>'9') {if(cc=='-')w=-1;cc=getchar();} while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar(); return s*w; } int qpow(int a,int k) { int ans=1; for(;k;k>>=1,a=1ll*a*a%mod) if(k&1) ans=1ll*ans*a%mod; return ans; } int C(int n,int m) { if(n<m) return 0; return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod; } signed main() { int t=rd(),tp=rd(),ans=0;fac[0]=1; for(int i=1;i<=t;i++) fac[i]=1ll*fac[i-1]*i%mod; inv[t]=qpow(fac[t],mod-2); for(int i=t;i>=1;i--) inv[i-1]=1ll*inv[i]*i%mod; if(tp==0) { for(int k=0;k<=t;k+=2) ans=(ans+1ll*C(t,k)*C(k,k/2)%mod*C(t-k,(t-k)/2)%mod)%mod; } else if(tp==1) { t/=2; ans=(C(2*t,t)-C(2*t,t-1)+mod)%mod; } else if(tp==2) { f[0]=1; for(int i=2;i<=t;i+=2) { for(int j=2;j<=i;j+=2) { f[i]=(f[i]+1ll*f[i-j]*4%mod*(C(j-2,(j-2)/2)-C(j-2,((j-2)/2)-1)+mod)%mod)%mod; } } ans=f[t]; } else if(tp==3) { for(int i=0;i<=t;i+=2) { int k=i/2,p=(t-i)/2; ans=(ans+1ll*C(t,i)%mod*(C(2*p,p)-C(2*p,p-1)+mod)%mod*(C(2*k,k)-C(2*k,k-1)+mod)%mod)%mod; } } printf("%lld ",ans); } /* g++ 3.cpp -o 3 ./3 100 2 */