[JZOJ6084]【GDOI2019模拟2019.3.25】礼物【Burnside引理】【计数】

37 篇文章 0 订阅
35 篇文章 0 订阅

Description

你有一个长度为n的有标号环,你需要将其中m个位置染色,要求不能出现长度大于k的连续被染色的段。

求本质不同的环的个数。
两个环本质不同,当且仅当它们不能通过旋转得到另一个。

k ≤ m ≤ n ≤ 1 0 6 k\leq m\leq n\leq 10^6 kmn106

Solution

看到不能循环同构,自然想到用Burnside引理求解

枚举每一种置换,向后转i步,计算不动点个数

根据简单数论知识(套路)可以得出,向后转i步以后整个环分成了 ( n , i ) (n,i) (n,i)组,每一组有 n ( n , i ) n\over (n,i) (n,i)n个元素,组中的染或不染的状态相同。

第p个元素属于第 ( p − 1 )   m o d   ( n , i ) + 1 (p-1)\ mod \ (n,i)+1 (p1) mod (n,i)+1组,也就是说原来的环被分成了 n ( n , i ) n\over (n,i) (n,i)n个子段,每个子段中的元素两两不在同一组。

我们发现每一个子段其实也可以看做一个环(因为它与两边与它完全一样的子段相接)。

并且由于有m的限制,我们还需要满足 ( n , i ) ∣ m (n,i)|m (n,i)m

我们只需要对所有n的约数求解即可。

现在只需要计算如何计算长度为n的带标号环,染m个元素,最长连续染色段不超过k。

一个环不太好搞,我们钦定1号元素是不染的,剩下的就相当于一条链了。
我们可以将链旋转1~n次,可以发现每个不染的元素都有可能是变成1号钦定的元素,因此每种环都被算了恰好n-m次,因此只需要将链的情况乘上 n n − m n\over n-m nmn即可。

考虑如何计算一条链的情况。
我们对于每一个不染的元素i,记一个变量 x i x_i xi表示它与上一个不染的元素之间夹了多少个染了的元素,特别的 x 1 x_1 x1为链末尾的染色端长。

方案数相当于方程组 ∑ i = 1 n − m x i = m \sum\limits_{i=1}^{n-m}x_i=m i=1nmxi=m
0 ≤ x i ≤ k 0\leq x_i \leq k 0xik
的解的组数。

接下来就是套路了,采用容斥,枚举多少个变量必须超出上界限制,就给这些变量加上k+1,然后随便选。

那么 A n s = ∑ i = 0 n − m ( − 1 ) i ( n − m i ) ( m − i ( k + 1 ) + n − m − 1 n − m − 1 ) Ans=\sum\limits_{i=0}^{n-m}(-1)^i{n-m\choose i}{m-i(k+1)+n-m-1\choose n-m-1} Ans=i=0nm(1)i(inm)(nm1mi(k+1)+nm1)

这样计算一个是线性的,因此总的时间复杂度就是 O ( σ ( n ) ) O(\sigma(n)) O(σ(n))的,其中 σ ( n ) \sigma(n) σ(n)为n的约数和。

Code

#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 1000005
#define mo 998244353
#define LL long long
using namespace std;
int t,n,m,l;
LL ct[N],js[N],ny[N];
bool bz[N];
int gcd(int x,int y)
{
	return (y)?gcd(y,x%y):x;
}
LL ksm(LL k,LL n)
{
	LL s=1;
	for(;n;n>>=1,k=k*k%mo) if(n&1) s=s*k%mo;
	return s;
}

LL C(int n,int m)
{
	return ((n<m)?0:js[n]*ny[m]%mo*ny[n-m]%mo);
}
LL calc(int n,int m)
{
	if(m==0) return 1;
	if(m==n-1)
	{
		if(l>=n-1) return n;
		else return 0; 
	} 
	LL s=0,v=1;
	fo(i,0,n-m)
	{
		if(n-1-i*(l+1)<n-m-1) break; 
		s=(s+v*C(n-m,i)*C(n-1-i*(l+1),n-m-1)%mo+mo)%mo;
		v=-v;
	}
	s=s*(LL)n%mo*ny[n-m]%mo*js[n-m-1]%mo;
	return s;
}
int main()
{
	cin>>t;
	js[0]=1;
	fo(i,1,N-5) js[i]=js[i-1]*(LL)i%mo;
	ny[N-5]=ksm(js[N-5],mo-2);
	fod(i,N-6,0) ny[i]=ny[i+1]*(LL)(i+1)%mo;
	while(t--)
	{
		cin>>n>>m>>l;
		memset(ct,0,sizeof(ct));
		memset(bz,0,sizeof(bz));
		LL s=0;
		fo(i,1,n)
		{
			int c=gcd(n,i),w=n/c;
			if(m%w==0)
			{
				int p=m/w;
				if(p==c) s+=(l>=n);
				else s=(s+((bz[c])?ct[c]:ct[c]=calc(c,p)))%mo;
				bz[c]=1;
			}
		}
		printf("%lld\n",s*ksm(n,mo-2)%mo);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值