【WC模拟】B君的宴请

来源:互联网 发布:中央电视台直播软件 编辑:程序博客网 时间:2024/05/02 09:42

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;}
0 0
原创粉丝点击