BZOJ1547 周末晚会题解(置换群+数论+计数DP)

题目:BZOJ1547.
题目大意:给定一个长度为 n n n的环,要求每个位置染成 0 0 0 1 1 1,其中连续的 0 0 0数量不超过 m m m,求方案数.循环同构算一种方案但翻转同构不算.
数据组数 T ≤ 50 T\leq 50 T50 1 ≤ n , m ≤ 2000 1\leq n,m\leq 2000 1n,m2000,答案对 1 0 8 + 7 10^8+7 108+7取模.

首先看到了循环同构算一种方案就可以想到用Burnside引理转化成统计 n n n个置换中的每个循环染同色的方案数.

考虑对于一个转了 a a a次的循环,根据一些数论知识,我们知道总共会有 g c d ( n , a ) gcd(n,a) gcd(n,a)个循环,显然这些循环会形成一些长度为 n g c d ( n , a ) \frac{n}{gcd(n,a)} gcd(n,a)n的循环节,每个循环节内部的所有位置互不相干.

问题变成了如何统计一个长度为 g c d ( n , a ) gcd(n,a) gcd(n,a)的环中没有超过 m m m个连续的 0 0 0的染色方案.显然若 m + 1 ≥ g c d ( n , a ) m+1\geq gcd(n,a) m+1gcd(n,a)时答案就是 2 g c d ( n , a ) − [ m + 1 ≤ n ] 2^{gcd(n,a)}-[m+1\leq n] 2gcd(n,a)[m+1n],现在考虑 m ≤ g c d ( n , a ) m\leq gcd(n,a) mgcd(n,a)的情况.

发现此时环并不好解决但是链好办,所以先统计链的方案数,再减去在链上没有超过 m m m 0 0 0但首尾相连后就有的方案数即可.

为了实现这一点,考虑一个DP.设 f [ i ] [ j ] f[i][j] f[i][j]表示长度为 i i i的链上满足连续的 0 0 0不超过 m m m个且倒数第 j + 1 j+1 j+1个染为 1 1 1最后 j j j个都染为 0 0 0的方案数,容易得到状态转移方程:
f [ i ] [ j ] = { ∑ k = 0 m f [ i − 1 ] [ k ] j = 0 f [ i − 1 ] [ j − 1 ] j > 0 f[i][j]= \left\{\begin{matrix} \sum_{k=0}^{m}f[i-1][k]&j=0\\ f[i-1][j-1]&j>0 \end{matrix}\right. f[i][j]={k=0mf[i1][k]f[i1][j1]j=0j>0

链的方案数显然为 ∑ i = 0 m f [ g c d ( n , a ) ] [ i ] \sum_{i=0}^{m}f[gcd(n,a)][i] i=0mf[gcd(n,a)][i],然后考虑计算链上没有超过 m m m个连续的 0 0 0但首尾相连后有的方案数.

考虑枚举开头连续 0 0 0的数量 i i i,再枚举结尾连续 0 0 0的数量,就可以得到方案数为:
∑ i = 1 min ⁡ ( g c d ( n , a ) − 1 , m ) ∑ j = m + 1 − i m f [ g c d ( n , a ) ] [ j ] \sum_{i=1}^{\min(gcd(n,a)-1,m)}\sum_{j=m+1-i}^{m}f[gcd(n,a)][j] i=1min(gcd(n,a)1,m)j=m+1imf[gcd(n,a)][j]

前缀和一下就可以省去一个 ∑ \sum ,时间复杂度降为 O ( m ) O(m) O(m).

总时间复杂度 O ( T n m ) O(Tnm) O(Tnm).

代码如下:

#include<bits/stdc++.h>
using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=2000,mod=100000007;

int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
int mul(int a,int b){return (LL)a*b%mod;}
void sadd(int &a,int b){a=add(a,b);}
void ssub(int &a,int b){a=sub(a,b);}
void smul(int &a,int b){a=mul(a,b);}
int Power(int a,int k){int res=1;for (;k;k>>=1,smul(a,a)) if (k&1) smul(res,a);return res;}
int Get_inv(int a){return Power(a,mod-2);}
int gcd(int a,int b){return b?gcd(b,a%b):a;}

int n,m;
int dp[N+9][N+9];

void Get_dp(){
  dp[0][0]=1;
  for (int i=1;i<=n;++i){
    dp[i][0]=dp[i][m+1]=0;
  	for (int j=0;j<=m;++j){
  	  sadd(dp[i][0],dp[i-1][j]);
      if (j>0) dp[i][j]=dp[i-1][j-1];
	}
  }
  for (int i=1;i<=n;++i)
    for (int j=m;j>=0;--j) sadd(dp[i][j],dp[i][j+1]);
}

int Solve(int len){
  if (len<=m+1) return sub(Power(2,len),m+1<=n);
  int res=dp[len][0];
  for (int i=1;i<len&&i<=m;++i) ssub(res,dp[len-i-1][m+1-i]);
  return res;
}

int ans;

Abigail into(){
  scanf("%d%d",&n,&m);
}

Abigail work(){
  Get_dp();
  ans=0;
  for (int i=0;i<n;++i) sadd(ans,Solve(gcd(n,i)));
}

Abigail outo(){
  printf("%d\n",mul(ans,Get_inv(n)));
}

int main(){
  int T;
  scanf("%d",&T);
  while (T--){
    into();
    work();
    outo();
  }
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值