【WC模拟】B君的宴请

Description

求在n个点的圆环中选出k个不相邻的点的方案数。
如果两个方案能够通过旋转或对称重合则视为同一种。
n,k<=10^6

Solution

首先我们强制一个选,然后只记录两两选择点之间的数的个数。
那么原问题就转化成了把n-k划分成k个正整数的方案数,允许旋转和对称。
burnside引理直接上。
可以发现旋转之后再对称可以等同于以另一条对称轴对称。
同理对称之后再旋转也是一样的。
那么我们的置换集合的大小就为2*k,k种旋转,k种对称。
旋转就是经典问题了,枚举旋转i位,每一组相同的数的个数为k/gcd(i,k),那么我们可以直接把总和除以个数,就变成每个数只有一个的填数方案。
直接组合数计算就好了。
对称分两种情况讨论。
如果k为奇数那么对称一定经过一个选择的点,枚举被对称轴经过的那一段没选的点的个数,组合数直接算就好了。
k为偶数同理,不过有两段都不经过选择的点和两段都经过选择的点两种情况。
然后最后算出来的不动的方案数直接/2k就是答案。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int N=1e6+5,mo=1e9+7;
int n,k,fact[N],inv[N];
int gcd(int x,int y) {
    return y?gcd(y,x%y):x;
}
int mi(int x,int y) {
    int z=1;
    for(;y;y/=2,x=(ll)x*x%mo) 
        if (y&1) z=(ll)z*x%mo;
    return z;
}
int c(int m,int n) {
    if (n>m||m<0||n<0) return 0;
    return (ll)fact[m]*inv[n]%mo*inv[m-n]%mo;
}
int main() {
    freopen("round.in","r",stdin);
    freopen("round.out","w",stdout);
    scanf("%d%d",&n,&k);n-=k;
    if (k<=1) {
        printf("1\n");
        return 0;
    }
    if (k==2) {
        printf("%d\n",n/2);
        return 0;
    }
    fact[0]=inv[0]=inv[n]=1;
    fo(i,1,n) fact[i]=(ll)fact[i-1]*i%mo;
    inv[n]=mi(fact[n],mo-2);
    fd(i,n-1,1) inv[i]=(ll)inv[i+1]*(i+1)%mo;
    int ans=c(n-1,k-1);
    fo(i,1,k-1) {
        int len=k/gcd(i,k);
        if (!(n%len)) (ans+=c(n/len-1,k/len-1))%=mo;
    }
    if (k&1) {
        int res=0;
        fo(i,1,n-k+1) 
            if (!((n-i)&1)) (res+=c((n-i)/2-1,k/2-1)%mo)%=mo;
        (ans+=(ll)res*k%mo)%=mo; 
    } else {
        int res=0;
        if (!(n&1)) res=c(n/2-1,k/2-1);
        fo(i,2,n-k+2)
            if (!((n-i)&1)) (res+=(ll)c((n-i)/2-1,k/2-2)*(i-1)%mo)%=mo;
        (ans+=(ll)res*(k/2)%mo)%=mo; 
    }
    printf("%lld\n",(ll)ans*mi(k*2,mo-2)%mo);
    return 0;
}

你可能感兴趣的:(组合数,群论,WC模拟,B君的宴请,Burnside引理)