礼物
情人节就要到了,小Y想要买一份礼物送给自己的女朋友。但是他在买完礼物后发现自己并没有女朋友,所以他决定把礼物送给自己(真可怜)。
这份礼物其实是一个手环。这个手环上一共有\(n\)个珠子,而且现在它们看上去都是一样的。
因为没有人陪着过情人节,小Y觉得特别无聊,于是就一个人宅在家里修炼膜法。最终他习得了一种膜法,可以把他手环上\(n\)个珠子中的\(m\)个变成金色的,这样他的手环就会显得非常高端大气上档次。
但是膜法毕竟都是假的,表面上的光鲜亮丽并不能够长久地存在。因此,手环上被变成金色的最长连续段不能太长,具体地,这个最长长度不能超过小Y心里想的一个阈值\(k\)。
小Y想要知道他在使用膜法后可能产生的不同手环的方案数。两个手环被认为是不同的,当且仅当它们不能仅通过旋转变得一模一样。也就是说,如果两个手环需要通过翻转才能够一模一样,则我们认为这两个手环是不同的。
由于答案可能很大,所以小Y只需要你告诉他答案对\(998244353\)取模后的结果就好了。
对于100%的测试数据,保证\(1\leq T\leq 5\),\(1\leq n\leq 10^6\),\(0\leq k\leq m\leq n\)。
题解
显然是Burnside引理题。枚举转了\(a\)步,那么共有\(\gcd(a,n)\)个循环,每个循环长度为\(\frac{n}{\gcd(a,n)}\)。
若\(m\bmod \frac{n}{\gcd(a,n)}\neq 0\),显然不动点个数为\(0\)。否则可以看做长为\(\gcd(a,n)\)的环上把\(m/\frac{n}{\gcd(a,n)}\)个点染成金色,且最长连续段不能超过\(k\)。
如果\(m/\frac{n}{\gcd(a,n)}=\gcd(a,n)\)的话,说明\(n\)个点都要被染成金色。只需判断\(n\leq k\)即可。
否则金色就不可能一直拼接,那么手环的最长金色长度可以看成长为\(\gcd(a,n)\)的环上的最长金色长度。于是转化成了子问题:长为\(n\)的环上,要染\(m\)个金色,最长金色长度不超过\(k\)的方案数。
将\(n-m\)个灰色点之间的距离看成变量\(x_1,x_2,\dots,x_{n-m}\),那么求的就是方程
的解。每个解的贡献是\(x_1+1\)。
限制\(\leq k\)直接容斥,贡献\(x_1+1\)利用期望的线性性拆开计算,\(x_1\)的贡献利用组合意义。
时间复杂度\(O(\sigma_1(n))\)。
CO int N=1e6+10;
int prime[N],num,phi[N];
vector<int> divs[N];
int fac[N],inv[N],ifac[N];
IN int C(int n,int m){
if(m>n) return 0;
return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
int calc(int n,int m,int k){ // m<n
int ans=0;
for(int i=0;i<=n-m and i*(k+1)<=m;++i){
int sum=mul(C(n-m,i),C(n-i*(k+1)-1,n-m-1));
if(i) sum=add(sum,mul(C(n-m-1,i-1),mul(k+1,C(n-i*(k+1)-1,n-m-1)))); // notice range
if(i) sum=add(sum,mul(C(n-m-1,i-1),C(n-i*(k+1)-1,n-m)));
sum=add(sum,mul(C(n-m-1,i),C(n-i*(k+1)-1,n-m)));
ans=add(ans,i%2==0?sum:mod-sum);
}
return ans;
}
void real_main(){
int n=read<int>(),m=read<int>(),k=read<int>();
int ans=0;
for(int g:divs[n])if(m%(n/g)==0){
if(m/(n/g)==g){
if(n<=k) ans=mul(ans,phi[n/g]);
continue;
}
ans=add(ans,mul(calc(g,m/(n/g),k),phi[n/g]));
}
ans=mul(ans,fpow(n,mod-2));
printf("%d\n",ans);
}
int main(){
freopen("gift.in","r",stdin),freopen("gift.out","w",stdout);
phi[1]=1;
for(int i=2;i<N;++i){
if(!prime[i]){
prime[++num]=i;
phi[i]=i-1;
}
for(int j=1;j<=num and i*prime[j]<N;++j){
prime[i*prime[j]]=1;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*phi[prime[j]];
}
}
for(int i=1;i<N;++i)for(int j=i;j<N;j+=i)
divs[j].push_back(i);
fac[0]=fac[1]=1;
inv[0]=inv[1]=1;
ifac[0]=ifac[1]=1;
for(int i=2;i<N;++i){
fac[i]=mul(fac[i-1],i);
inv[i]=mul(mod-mod/i,inv[mod%i]);
ifac[i]=mul(ifac[i-1],inv[i]);
}
for(int T=read<int>();T--;) real_main();
return 0;
}
周末晚会
Irena和Sirup正准备下个周末的Party。为这个Party,他们刚刚买了一个非常大的圆桌。他们想邀请每个人,但他们现在不知道如何分配座次。Irena说当有超过\(K\)个女孩座位相邻(即这些女孩的座位是连续的,中间没有男孩)的话,她们就会说一整晚的话而不和其他人聊天。
Sirup没有其他选择,只有同意她。然而,作为一名数学家,他很快地痴迷于所有可能方案。
题目说明: \(N\)个人围绕着圆桌坐着,其中一些是男孩,另一些是女孩。你的任务是找出所有合法的方案数,使得不超过\(K\)个女孩座位是连续的。
循环同构会被认为是同一种方案。
对于100%的数据\(N,K\leq 2000\)。
题解
https://www.cnblogs.com/ErkkiErkko/p/10067750.html
考虑如何求在置换“顺时针旋转\(x\)位置”下不动点的数目,可以发现这样的不动点的男女排列一定具有周期性,且\(\gcd(n,x)\)是它的一个周期,这里可以自行画图理解一下。这样我们又再次简化了题目,我们现在只需考虑这一个周期了。
令\(d=\gcd(n,x)\),首先进行如下的分类讨论:
-
\(n \leq k\),这样不存在不合法的方案,返回\(2^d\)。
-
\(n>k\)且\(d \leq k\),这样只要这个周期内不全是女生即是合法方案,返回\(2^d-1\)。
-
\(n>k\)且\(d>k\),DP解决。
怎么DP?显然环上的问题要用序列DP来解决。
\(F(i,j)\)表示考虑到前\(i\)个人,最后\(j\)个人是女生的方案数。
\(G(i,j)\)表示考虑到前\(i\)个人,保证第一个人是男生,最后\(j\)个人是女生的方案数。
显然有转移:
\(G(i,j)\)的转移与\(F(i,j)\)类似。
于是对于长度为i的区间,其合法方案数为\(\sum_{j=1}^{\min(i,k)}F(i,j)-\sum_{j=k+1}^{\min(i-1,2k)}G(i-j,0) \times (k-(j-k)+1)\),这个可以直接预处理出来。
时间复杂度\(O(n^2)\)。
CO int N=2e3+10;
int prime[N],num,phi[N];
int F[N][N],G[N][N];
void real_main(){
int n=read<int>(),m=read<int>();
F[1][0]=F[1][1]=1;
G[1][0]=1;
for(int i=2;i<=n;++i){
F[i][0]=G[i][0]=0;
for(int j=0;j<=min(i-1,m);++j) F[i][0]=add(F[i][0],F[i-1][j]);
for(int j=1;j<=min(i,m);++j) F[i][j]=F[i-1][j-1];
for(int j=0;j<=min(i-2,m);++j) G[i][0]=add(G[i][0],G[i-1][j]);
for(int j=1;j<=min(i-1,m);++j) G[i][j]=G[i-1][j-1];
}
function<int(int)> calc=[&](int i)->int{
if(i<=m) return m<n?add(fpow(2,i),mod-1):fpow(2,i);
int ans=0;
for(int j=0;j<=min(i,m);++j) ans=add(ans,F[i][j]);
for(int j=m+1;j<=min(i-1,2*m);++j) ans=add(ans,mod-mul(G[i-j][0],m-(j-m)+1));
return ans;
};
int ans=0;
for(int i=1;i<=n;++i) ans=add(ans,calc(gcd(n,i)));
ans=mul(ans,fpow(n,mod-2));
printf("%d\n",ans);
}
int main(){
for(int T=read<int>();T--;) real_main();
return 0;
}