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