来自学长的馈赠2
70分rank 28
T1:数论(求逆元)+快速幂求期望(线性性)
T2:图论
T3:数论:catalan数
T4:预设性DPhttps://www.cnblogs.com/TSTYFST/p/16515258.html
T1:
https://tg.hszxoj.com/contest/444/problem/1
求x取值的期望
首先介绍一下我调了3-4个小时的暴力(g大神帮忙成果)
dp[i][j]第i次选数后,x=j的概率
如果直接循环选的是那些数会T,于是合并同类项,如果A[I]==A[J]就开一个值域的数组表示这个val的出现次数
转移就是累加,然后注意/(n)要利用逆元,看清楚n的取值范围
#include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<string> #include<cstdlib> #include<ctime> #include<algorithm> #include<iomanip> #include<bitset> #include<map> #include<queue> #include<deque> #include<vector> #define _f(i,a,b) for(register int i=a;i<=b;++i) #define f_(i,a,b) for(register int i=a;i>=b;--i) #define ll long long #define INF 1000000000 #define chu printf //太大会炸 using namespace std; inline int re() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } const int N=1000+10; const ll MOD=1e9+7; ll f[3000][400]; int n,m,mod; int a[100000+100]; inline ll ny(ll x,int y) { ll ans=1,a=x; while(y) { if(y&1) { ans=ans*a%MOD; } y>>=1; a=a*a%MOD; } return ans; } inline ll qpow(ll x,int y) { ll ans=1,a=x; while(y) { if(y&1) { ans=ans*a%mod; } y>>=1; a=a*a%mod; } return ans; } int main() { n=re(),m=re(),mod=re(); if(mod==2) { chu("1");return 0; } if(n==1) { int xp=re(); chu("%lld",qpow(xp,m)%MOD);return 0; } for(int i=1;i<=n;i++) { int xp=re(); a[xp]++; } f[0][1]=1; for(int i=0;i<=m-1;i++) { for(int j=1;j<mod;j++) { for(int k=1;k<mod;k++) { int can=(j*k%mod); f[i+1][can]=f[i+1][can]+f[i][j]*a[k]; f[i+1][can]%=MOD; } } } ll ans=0; ll Ny=ny(n,MOD-2); Ny=ny(Ny,m); for(int i=1;i<mod;++i) { ans+=f[m][i]*i%MOD; ans%=MOD; } ans=ans*Ny%MOD; chu("%lld",ans); return 0; } /* 2 1000 281 1 2 1 1000000000 107 53 */
T1 概率期望的题目 正解: 其实懂了DP得方法这个也就差不多了
DP的循环本质上就是(cnt1+cnt2+cnt3+...+cntmod-1)^m
这个多项式不能合并(因为cnt1乘以cnt1它的实际意义加和应该加到[cnt1*cnt1%mod]里面)
所以不能直接加再快速幂 然后但是它可以 类似矩阵快速幂,用一个g数组存()本身不断乘方的值 快速幂就可以解决了
#include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<string> #include<cstdlib> #include<ctime> #include<algorithm> #include<iomanip> #include<bitset> #include<map> #include<queue> #include<deque> #include<vector> #define _f(i,a,b) for(register int i=a;i<=b;++i) #define f_(i,a,b) for(register int i=a;i>=b;--i) #define ll long long #define ull unsigned long long #define INF 1000000000 #define chu printf //太大会炸 using namespace std; inline int re() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } const int N=100000+10,MOD=1e9+7; int f[N],g[N],fg[N],n,m,mod; inline int qpow(int a,int b) { int anse=1; while(b) { if(b&1) { anse=(ll)anse*a%MOD; } b>>=1; a=(ll)a*a%MOD; } return anse; } signed main() { //freopen("seq2.in","r",stdin); n=re(),m=re(),mod=re(); _f(i,1,n) { int x=re();g[x]++; } //计算^m,g:a,f:ans f[1]=1; int ha=m; while(m) { if(m&1) {//看情况,fg[1]貌似要变成1 _f(i,1,mod-1) { _f(j,1,mod-1) { int can=i*j%mod; fg[can]=((ll)fg[can]+(ll)f[i]*g[j]%MOD)%MOD; } } _f(i,1,mod-1)f[i]=fg[i],fg[i]=0; } _f(i,1,mod-1) { _f(j,1,mod-1) { int can=i*j%mod; fg[can]=((ll)fg[can]+(ll)g[i]*g[j]%MOD)%MOD; } } _f(i,1,mod-1)g[i]=fg[i],fg[i]=0; m>>=1; } int ans=0; _f(i,1,mod-1) { ans=((ll)ans+(ll)f[i]*i%MOD)%MOD; } chu("%lld",1LL*ans*qpow(qpow(n,ha),MOD-2)%MOD); return 0; }
T2:
(30s)没拿到暴力10分(又是低错,+=写成=)
图论:其实就是树的重心+消元思想
代码里有讲解
#include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<string> #include<cstdlib> #include<ctime> #include<algorithm> #include<iomanip> #include<bitset> #include<map> #include<queue> #include<deque> #include<vector> #define _f(i,a,b) for(register int i=a;i<=b;++i) #define f_(i,a,b) for(register int i=a;i>=b;--i) #define ll long long #define INF 1000000000 #define chu printf //太大会炸 using namespace std; inline int re() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } const int N=100000+10; const ll MOD=1e9+7; struct node { int to,nxt; }e[200000+100]; int tot,head[100000+10]; inline void add(int x,int y) { e[++tot].to=y,e[tot].nxt=head[x],head[x]=tot; } int siz[100000+10],f[100000+10],n; inline void dfs(int x,int fa) { for(int i=head[x];i;i=e[i].nxt) { int to=e[i].to; if(to==fa)continue; dfs(to,x); siz[x]+=siz[to]; f[x]=f[x]+f[to]+siz[to]; } } inline void deal(int x,int fa) { for(int i=head[x];i;i=e[i].nxt) { int to=e[i].to; if(to==fa)continue; f[to]=f[x]+siz[1]-siz[to]-siz[to]; deal(to,x); } head[x]=0; } int xi[100000+10]; inline void dfs_2(int x,int fa) { for(int i=head[x];i;i=e[i].nxt) { int to=e[i].to; if(to==fa)continue; xi[to]++,xi[x]--; dfs_2(to,x); } } inline void dfs_3(int x,int fa) { for(int i=head[x];i;i=e[i].nxt) { int to=e[i].to; if(to==fa)continue; siz[to]=(siz[1]+f[x]-f[to])/2; dfs_3(to,x); } } inline void dfs_4(int x,int fa) { for(int i=head[x];i;i=e[i].nxt) { int to=e[i].to; if(to==fa)continue; siz[x]-=siz[to]; dfs_4(to,x); } } int main() { int T_t=re(); while(T_t--) { n=re(); tot=0; _f(i,1,n-1) { int u=re(),v=re();add(u,v);add(v,u); } int t=re(); if(t==0) { _f(j,1,n)siz[j]=re(),f[j]=0; dfs(1,-1);//求im,先求出im[1],再递推 // chu("dfs\n"); deal(1,-1); _f(op,1,n)chu("%d ",f[op]); chu("\n"); } else//如果给出b,那我只能按照一条链的算 ,1是起点 { _f(j,1,n)f[j]=re(),siz[j]=0,xi[j]=0; dfs_2(1,-1); ll zuo=0; _f(j,1,n)zuo+=(ll)f[j]*xi[j]; zuo+=2*f[1]; zuo/=(n-1); siz[1]=zuo; // chu("siz[:%d\n",siz[1]); dfs_3(1,-1); // _f(i,1,n)chu("siz[:%d\n",siz[i]); dfs_4(1,-1); _f(i,1,n)chu("%d ",siz[i]),head[i]=0; chu("\n"); } } return 0; } /* 发现就算拼劲全力也就40分 3 2 1 2 1 17 31 2 1 2 0 31 17 7 1 4 1 3 3 5 3 7 1 2 2 6 0 25 4 7 21 8 3 6 66 126 98 98 156 194 160 1 7 1 2 27 2 3 3 3 4 2 4 5 1 5 6 4 6 7 8 1 5 1 3 1 2 3 5 3 4 1 45 63 31 41 37 */
T3
给你坐标,只能走n步,有要求的让你走来走去
数论:数列:catalan数列
具体知识看数论
opt=0:
枚举哪些步数向上走,哪些向下走,乘法原理
opt=1:catalan
opt=2
这个比较有意思,我需要保证每次dot改变方向的时候都在原点,不妨设dp[x],走x步到达原点的方案数
怎么转移?
枚举第一次到达原点的方案数,可以保证方案之间一定不会重复(经常使用的技巧)
怎么保证是第一次到达?catalan(j/2-1),留出第一步和最后一步,剩下的还是catalan,就可以了
opt=3
一样
#include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<string> #include<cstdlib> #include<ctime> #include<algorithm> #include<iomanip> #include<bitset> #include<map> #include<queue> #include<deque> #include<vector> #define _f(i,a,b) for(register int i=a;i<=b;++i) #define f_(i,a,b) for(register int i=a;i>=b;--i) #define ll long long #define INF 1000000000 #define chu printf //太大会炸 using namespace std; inline int re() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } const int N=1000+10; const ll MOD=1e9+7; int n,opt; ll ny[100000+100],nys[100000+100],fac[100000+100]; ll f[100000+100]; inline ll C(int x,int y) { return fac[x]*nys[x-y]%MOD*nys[y]%MOD; } inline ll Cat(int x) { return C(x*2,x)*ny[x+1]%MOD; //return (C(2*x,x)-C(2*x,x-1)+MOD)%MOD; } void pre() { ny[0]=nys[0]=fac[0]=ny[1]=nys[1]=fac[1]=1; _f(i,2,100010) { fac[i]=fac[i-1]*i%MOD; ny[i]=(MOD-MOD/i)*ny[MOD%i]%MOD; nys[i]=nys[i-1]*ny[i]%MOD; // chu("ny;%lld(%lld)\n",nys[i],nys[i-1]); } } ll ans; int main() { pre(); n=re(),opt=re(); if(opt==0) { for(int k=0;k<=n;k+=2) { //chu("C:%lld\n",C(k,k/2)); ans+=C(k,k/2)*C((n-k),(n-k)/2)%MOD*C(n,k)%MOD; ans%=MOD; } chu("%lld",ans); } else if(opt==1) { chu("%lld",Cat(n/2));// } else if(opt==2) { f[2]=4;f[0]=1; for(int i=4;i<=n;i+=2)//i步数回到原点的方案数 { for(int j=2;j<=i;j+=2)//j步数第一次回到原点 { f[i]=(f[i]+4*f[i-j]%MOD*Cat(j/2-1)%MOD)%MOD; //这里注意f[0]要初始化成1,因为当全部默认成第一次回到原点(一直朝着一个方向走),也是要算进去的 } } // chu("f[4]:%lld\n",f[4]); chu("%lld",f[n]); } else { for(int k=0;k<=n;k+=2)//k是步数 { ans+=Cat(k/2)*Cat(n/2-k/2)%MOD*C(n,k)%MOD; ans%=MOD; } chu("%lld",ans); } return 0; } /* */
T4:
给你一个排列n,求相邻数取max的加和<=K的方案数
dp[i][j][k]:当前已经安排了i个数(从小到大),有j个空位,max为k的方案数
空位:两两数字之间
预设性DP
表示遇到统计答案时状态转移提前预设出还没进行选择的选项,以方便进行转移
像这道题,如果正常的话肯定是想着
dp[i][j][k]:当前安排的数为i(状态压缩),有j个空位,max为k的方案数
但是一来空间开不下,二来时间也会T
所以预设出我2 2数之间的空位,这样a_b是不会有贡献的,ab的贡献会算进去
关键是转移,我虚设出的空位不一定是在哪个确切的位置,因为它本身就是由排列组合计算出的无数种方案叠加的结果
所以转移也利用的是乘法原理(省略很多冗余计算)
类比:
https://tg.hszxoj.com/contest/398/problem/2 提高过度的中国象棋 只能每行每列放最多两个 f[i][j][K]:表示到i行为止,有j列放了1个子,k行放了0个子的方案数 5种情况枚举就行,你会发现有时候状态压缩能干的事好像预设也可以干 const int N=2000+10,M=320; int mod=9999973; int f[108][108][108]; int n,m; int main() { freopen("chess.in","r",stdin); freopen("chess.out","w",stdout); n=re(),m=re(); if(n<m) { swap(n,m); } f[1][1][m-1]=m;//第一行放了一个 f[1][0][m]=1;//第一行只能j+k==m因为不可能一列有两个 一个也不放 f[1][2][m-2]=m*(m-1)/2; //放了两个 _f(i,2,n) { _f(j,0,m) _f(k,0,m-j) { f[i][j][k]=f[i-1][j][k];//le7* if(j-1>=0)f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j-1][k+1]*(k+1)%mod);//有0的放1 f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j+1][k]*(j+1)%mod);//有1的放1 f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j][k+1]*j*(k+1)%mod);//有0的放1+有1的放1 if(j-2>=0)f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j-2][k+2]*(k+2)*(k+1)/2%mod);//有0的放2 f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j+2][k]*(j+2)*(j+1)/2%mod);//有1的放2 f[i][j][k]%=mod; //chu("f[%d][%d][%d]:%d\n",i,j,k,f[i][j][k]); } } int ans=0; _f(i,0,m) _f(j,0,m-i) { ans+=f[n][i][j]; ans%=mod; } chu("%d",ans); return 0; } f[i][j][k]: 第i行有j列放了1个棋子,k列放了0个棋子的方案数量
const int N=1000+10; const int MOD=998244353; ll f[2][60][2500];//f[i][j][k]:安排到i数,有j个空位(几个没关系,表示几个数之间),maxtot为k的方案数 int main() { int n=re(),m=re(),mx=n*n; int now=0; f[1][0][0]=1; _f(i,2,n) { _f(j,0,50) { _f(k,0,2400)f[now][j][k]=0; } int mx_now=min(i*i,m); _f(j,0,i) { _f(k,0,mx_now+2*i) { if(j>0)f[now][j][k]=(f[now][j][k]+f[now^1][j-1][k]*2)%MOD;//放两边无贡献,多了一个空位 // chu("from:%d %d %d\n",i-1,j-1,k); if(k>i-1)f[now][j][k]=(f[now][j][k]+f[now^1][j][k-i]*2)%MOD;//放两边有1个贡献,没有多空位 //if(j==0) continue; // chu("from:%d %d %d\n",i-1,j,k-i); if(j>0)f[now][j][k]=(f[now][j][k]+f[now^1][j-1][k]*(j-1))%MOD;//放中间无贡献,多一个空位 // chu("from:%d %d %d\n",i-1,j-1,k); if(k>i-1&&j>0)f[now][j][k]=(f[now][j][k]+f[now^1][j][k-i]*j*2)%MOD;//放中间有贡献,没有多空位 // chu("from:%d %d %d\n",i-1,j,k-i); f[now][j][k]=(f[now][j][k]+f[now^1][j+1][k-2*i]*(j+1))%MOD;//放中间有2贡献,少了一个空位 //chu("from:%d %d %d\n",i-1,j+1,k-2*i); //中,有2,-1 // chu("from:%d %d %d\n",i-1,j-1,k); // chu("(%lld)f[%d[%d[%d:%lld\n",f[now^1][j+1][k-2*i],i,j,k,f[now][j][k]); } } now^=1; } ll ans=0;now^=1; _f(i,0,m)ans+=(f[now][0][i]),ans%=MOD; chu("%lld",ans); return 0; }