bzoj1004: [HNOI2008]Cards [Burnside&Ploya+求逆元]

本文详细阐述了如何运用动态规划解决染色问题,通过分解置换和构建状态背包,求解满足特定颜色限制的等价类数量。文章还介绍了如何求解逆元,以及最终答案的计算方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今晚还有点时间刷完水题以后把1004试着弄了一下,看上去好高端的~~

---------------------------------------------------------------------------------------------------

Burnside引理:C(f)为不动点个数,则可得等价类数目为C(f)的平均值

Ploya定理:置换f可以被分解为m(f)个循环,假设涂k种颜色,C(f)=k^m(f);

综上所述,等价类的个数等于所有置换的f的k^m(f)的平均数

---------------------------------------------------------------------------------------------------

但是首先这道题对于最终的等价类有一定颜色限制,所以不能直接用Ploya定理,那么退而求其次。

同样的我们还是要把置换f拆成m(f)个循环,显然不动点满足每一个循环内点颜色相同。

也就是我们需要找到一种求C(f)的方法,使得最终的等价类满足颜色的限制,颜色只有三种,我们尝试用DP代替。


假设循环内点有v个,我们把每一个循环当做一个体积为v的物品放入状态背包,并且要求最终状态为{Sr,Sb,Sg}

那么我们不难得到动归方程:

    f[i][j][k]=Sigma{f[i-v][j][k]+f[i][j-v][k]+f[i][j][k-v]};

那么我们就得到了满足条件的不动点个数 f[Sr][Sb][Sg] 加入答案


最后一步是求逆,实现起来并没有什么难度,可以采用扩展欧几里得或费马小定理。

p为质数这个性质太棒了!这意味着直接可以得到逆元x=(m+1)^(p-2) mod p,因为a模b的逆元为a^(b-2) mod b


其实如果懂了也没什么难的,只是不知道下次遇到同类的题会不会做QAQ

至于代码的实现,五十行不到,思路清晰的话还是比较短的。

--------------------------------------------------------------------------------------------------

#include<iostream>
#include<cstdio>
#include<cstring>
#define For(i,l,r) for(int (i)=(l);(i)<=(r);(i)++)
#define ForD(i,r,l) for(int (i)=(r);(i)>=(l);(i)--)
using namespace std;
const int N=70;
int sr,sb,sg,m,p,n,ans;
int zh[N],f[N][N][N],fm[N];
bool used[N];
int Qpow(int c,int k){
	int ret=1;
	while(k){
		if (k&1) ret=(ret*c)%p;
		c=(c*c)%p;
		k>>=1;
	}return ret;
}
int main(){
	freopen("1004.in","r",stdin);
	scanf("%d%d%d%d%d",&sr,&sb,&sg,&m,&p);
	n=sr+sb+sg; ans=0;
	For(i,1,m+1){
		memset(f,0,sizeof(f));
		memset(used,0,sizeof(used));
		memset(fm,0,sizeof(fm)); 
		if(i!=m+1) For(j,1,n) scanf("%d",&zh[j]);else For(j,1,n) zh[j]=j;
		For(i,1,n) if(!used[i]){
			int tmp=i;
			used[i]=1;
			fm[++fm[0]]=1;
			while(zh[tmp]!=i){
				tmp=zh[tmp];
				used[tmp]=1;
				fm[fm[0]]++;
			}
		}f[0][0][0]=1;
		For(d,1,fm[0]) ForD(j,sr,0) ForD(k,sb,0) ForD(l,sg,0){
			if(j-fm[d]>=0) f[j][k][l]=(f[j][k][l]+f[j-fm[d]][k][l])%p;
			if(k-fm[d]>=0) f[j][k][l]=(f[j][k][l]+f[j][k-fm[d]][l])%p;
			if(l-fm[d]>=0) f[j][k][l]=(f[j][k][l]+f[j][k][l-fm[d]])%p;
		}ans=(ans+f[sr][sb][sg])%p;
	}ans=(ans*Qpow(m+1,p-2))%p;
	printf("%d\n",ans);
	return 0;
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值