[JZOJ4800]周末晚会

本文介绍了一个计数问题的解决方案,该问题要求在循环同构条件下,计算不超过特定数量的女孩连续坐在一起的合法排列数目。文章利用Burnside引理进行分析,并详细解释了如何通过数学方法统计不动点个数,最终给出了一种有效的算法实现。

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

题目大意

n 个人围绕着圆桌坐着,其中一些是男孩,另一些是女孩。你的任务是找出所有合法的方案数,使得不超过k个女孩座位是连续的。
循环同构会被认为是同一种方案。
一个测试点 T 个数据。

1n,k2000,1T20


题目分析

处理如循环同构的等价类计数问题, Burnsides 引理无疑是一个强有力的方法。

  • Burnsides 引理
  • n=gGC(g)|G| ,其中, G 是在一个目标集上的置换群,C(g)表示目标集在置换 g 的作用下的不动点个数。

没有学过Burnsides引理的读者可能会觉得这个很抽象,而且引理本身的证明需要较多的群论知识,我自己也不会,如果有兴趣可以看看这篇博文或者去查阅其它相关资料。
如何统计不动点呢?参考[SDOI2013]项链一题,我们考虑旋转 i (0i<n),将整个数组变成 gcd(i,n) 个相邻的互不影响的部分。将性别看成颜色,那么同部分必须同色,这就是一个不动点。考察一个不动点是否合法,我们只需要考察前 gcd(i,n) 个位置是否满足不存在大于 k 的相邻1(定义 1 为女性),注意头尾相接也不可以有。
这个怎么求呢?我们先不考虑头尾相接的情况,令fi表示长度为 i 0/1串,不存在相邻 k 个以上1的方案数。使用总情况(在前面的合法串基础上填 0/1 )减去不合法(由于前面串一定合法,因此不合法一定是最后面出现了 k+1 1 ,同时我们还要在这些1前面放一个 0 来限制它,因此是fik2)情况得到:

fi=2fi1fik2

注意当 f1 要设置为 1 ,原因就是一些简单的边界情况,读者自行思考吧。这个你不怕内存位置出错可以像Werkeytom_FTD一样作死直接设置,但最好还是特判一下。后面的计算也会有用到f1的情况,就不再累赘。
计算出这个以后,我们就要加上两端相连的情况了。枚举右端的连续 1 的个数r(1rk),那么左端连续 1 的个数l至少为 k+1r ,最多为 k ,为了方便后面的计算,我们不枚举l,枚举 l 相对与l的下界的增量 Δl(0Δl<r) 。为了保证其余位置不与头尾的 1 相接,我们依然在头尾1的靠内一端放一个 0 来限制,因此得到不合法情况总数:
r=1kΔl=0r1fdkΔl3

可以发现后面一个求和可以预处理前缀和来解决。
这样就完了?当然不是,我们来考虑一些特殊情况:

  1. nk ,这个时候前 gcd(i,n) 个位置填什么都可以,因此贡献应当是 2gcd(i,n)
  2. gcd(i,n)k<n ,这个时候全部填 1 相对于gcd(i,n)的长度是合法的,但是相对于整个序列是不合法的,因此贡献要减 1
  3. 其余情况正常处理。

总而言之,细节还是挺多的。需要思路清晰才能切出来。
时间复杂度O(n+Tn2)。Werkeytom_FTD貌似使用了一些奇技淫巧特殊的预处理技巧,程序跑得飞快。


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>

using namespace std;

const int P=100000007;
const int N=2000;

int f[N+50],sum[N];
int R,ans,T,n,k;

int gcd(int x,int y){return !y?x:gcd(y,x%y);}

int quick_power(int x,int y)
{
    int ret=1;
    for (;y;y>>=1,x=1ll*x*x%P) if (y&1) ret=1ll*ret*x%P;
    return ret;
}

void pre()
{
    f[0]=1;
    for (int i=1;i<=n;i++) f[i]=((f[i-1]<<1)-(i-k-2>=0?f[i-k-2]:(!(i-k-1)?1:0))+P)%P;
    sum[0]=1;
    for (int i=1;i<=n;i++) sum[i]=(sum[i-1]+f[i])%P;
}

int g(int d,int r)
{
    int ret=((d-k-3>=0?sum[d-k-3]:0)-(d-k-r-2>=0?sum[d-k-r-3]:0)+P)%P;
    if (d-k>=2&&d-k-2<r) (ret+=1)%=P;
    return ret;
}

int calc(int d)
{
    if (k>=n) return quick_power(2,d);
    int ret=0;
    for (int r=1;r<=k;r++) (ret+=g(d,r))%=P;
    ret=(f[d]-ret+P)%P;
    if (k>=d) (ret+=P-1)%=P;
    return ret;
}

void solve()
{
    ans=0;
    for (int l=0;l<n;l++) (ans+=calc(gcd(l,n)))%=P;
    R=quick_power(n,P-2),ans=1ll*ans*R%P;
}

int main()
{
    freopen("party.in","r",stdin),freopen("party.out","w",stdout);
    for (scanf("%d",&T);T--;)
    {
        scanf("%d%d",&n,&k);
        pre(),solve();
        printf("%d\n",ans);
    }
    fclose(stdin),fclose(stdout);
    return 0;
}
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值