[AGC022F] Checkers 题解

题目链接

点击打开链接

题目解法

很妙的题!!!

考虑 x 是无穷大的数,所以可以认为 xi 的系数是单独的一项,不会和 xj(ji) 合并
所以问题转化成了:每个数初始是 xix 可以理解是元),进行题目中的操作,问最后形成的 n 次多项式的个数

BA 连边,这会形成一棵树
考虑 xi 的系数,手画一下可以发现为 c×2depthi,c{1,1}
我们用递推的方式考虑 ci 的值
如果 cfai 是已知的,每个儿子会使 ci×(1),所以 ci=cfai×(1)cntson(i)
所以 cicfai 不同只与 i 儿子的奇偶性有关,也就是说,通过 cicfai 是否相同以及每个节点的深度可以确定一个多项式

考虑一个 dp,令 fi,j 为前若干层共有 i 个点,当前层有 j 个点的儿子数量为奇数
枚举下一层的节点个数 k(kj)
那么不考虑儿子数量的奇偶性,下一层节点中和父亲 c 相同的个数为 t=kj2,需要保证 2|kj
枚举下一层实际和父亲 c 相同的个数 p,自己手画一下图可以发现,下一层中度数为奇数的节点的个数 |tp|
这里给出一个结论:只需要从 fi,j 转移到 fi+k,|tp|,不能转移到 fi+k,|tp|+2w
为什么?第一,根据递推式,我们只关心下一层中实际和父亲相同的点的个数,而不在乎树的形态(就是怎么连,不包括节点深度),而 fi+k,|tp| 可以转移到所有 fi+k,|tp|+2w 的点,当然是取 w=0;第二,luogu 题解区有 hack
所以转移为:fi+k,|tp|fi,j(nik)(kp)
边界为 f1,0=f1,1=n,答案为 fn,0
时间复杂度 O(n4)

点击查看代码
#include <bits/stdc++.h>
#define F(i,x,y) for(int i=(x);i<=(y);i++)
#define DF(i,x,y) for(int i=(x);i>=(y);i--)
#define ms(x,y) memset(x,y,sizeof(x))
#define SZ(x) (int)x.size()-1
#define pb push_back
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
template<typename T> void chkmax(T &x,T y){ x=max(x,y);}
template<typename T> void chkmin(T &x,T y){ x=min(x,y);}
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
const int N=55,P=1e9+7;
int n,f[N][N],C[N][N];
//f[i][j]:有i个点,当前层有j个点儿子个数为奇数的方案数
inline void inc(int &x,int y){ x+=y;if(x>=P) x-=P;}
int main(){
n=read();
C[0][0]=1;
F(i,1,n){ C[i][0]=C[i][i]=1;F(j,1,i-1) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;}
f[1][0]=f[1][1]=n;
F(i,1,n) F(j,0,i) if(f[i][j])
F(k,max(1,j),n-i){//下一层有k个点
if((k-j)&1) continue;
int t=(k-j)/2;//不考虑儿子个数的奇偶性,下一层有t个点和父亲符号相同
F(p,0,k) inc(f[i+k][abs(t-p)],1ll*f[i][j]*C[n-i][k]%P*C[k][p]%P);//下一层实际有p个点和父亲符号相同
}
printf("%d\n",f[n][0]);
return 0;
}

能不能再给力一点
我们拆开转移式:fi,j(nik)(kp)=fi,j(ni)!(nik)!p!(kp)!
接下来的一步比较妙,令 x=p,y=kp
那么 fi+x+y,|yxj|2=fi,j(ni)!×1(nixy)!x!y!
这里重新定义 fi,jfi,j(ni)!
所以 fi+x+y,|yxj|2=fi,j1x!y!

可以发现,x,y 关系不大,考虑拆开 x,y
这里给出转移式:
{gi+y,yj=fi,j1y!fi+x,|jx|2=gi,j1x!
有了转移式,还要知道 x,y 的范围
首先 x+y>0,x+yj,且 x+yni
综合一下 xni,yni,x+j0,2|yx+jx+y>0
前面的限制都好满足,但 x+y>0 不太好满足
我们钦定 x0,y>0,这样会漏掉 x>0,y=0 的情况,暴力转移即可

边界为 f1,0=f1,1=n!,答案为 fn,0
时间复杂度 O(n3)

点击查看代码
#include <bits/stdc++.h>
#define F(i,x,y) for(int i=(x);i<=(y);i++)
#define DF(i,x,y) for(int i=(x);i>=(y);i--)
#define ms(x,y) memset(x,y,sizeof(x))
#define SZ(x) (int)x.size()-1
#define pb push_back
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
template<typename T> void chkmax(T &x,T y){ x=max(x,y);}
template<typename T> void chkmin(T &x,T y){ x=min(x,y);}
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
const int N=55,P=1e9+7;
int n,f[N][N],g[N][N<<1];
int fac[N],ifac[N],C[N][N];
//f[i][j]:有i个点,当前层有j个点儿子个数为奇数的方案数
inline void inc(int &x,int y){ x+=y;if(x>=P) x-=P;}
int qmi(int a,int b){
int res=1;
for(;b;b>>=1){ if(b&1) res=1ll*res*a%P;a=1ll*a*a%P;}
return res;
}
int main(){
n=read();
C[0][0]=1;
F(i,1,n){ C[i][0]=C[i][i]=1;F(j,1,i-1) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;}
fac[0]=1;
F(i,1,n) fac[i]=1ll*fac[i-1]*i%P;
ifac[n]=qmi(fac[n],P-2);
DF(i,n-1,0) ifac[i]=1ll*ifac[i+1]*(i+1)%P;
f[1][0]=f[1][1]=fac[n];
F(i,1,n){
F(j,0,n<<1) if(g[i][j]) F(x,0,n-i) if(x+j-n>=0) if(~(j-n-x)&1) inc(f[i+x][abs((j-n-x)/2)],1ll*g[i][j]*ifac[x]%P);
F(j,0,i){
if(f[i][j]) F(y,1,n-i) inc(g[i+y][y-j+n],1ll*f[i][j]*ifac[y]%P);
//y=0
F(k,max(1,j),n-i) if(~(k-j)&1){
int t=(k-j)/2;
inc(f[i+k][abs(t-k)],1ll*f[i][j]*C[n-i][k]%P*ifac[n-i]%P*fac[n-i-k]%P);
}
}
}
printf("%d\n",f[n][0]);
return 0;
}
posted @   Farmer_D  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示