【Educational Codeforces Round 88 (Rated for Div. 2) E】Modular Stability
题目链接
题目大意
你给一个整数n一个整数k
让你找这么一个数组a[1],a[2],...,a[k]
其中1<=a[1]<a[2]<....<a[k]<=n
使得对于任意一个非负整数x,让它按照 任意顺序 依次去和这个数组的每个元素取模(x和第1个元素取模后,结果再和第2个元素取模...)。
得到的取模的结果都要相同。
请你求出符合要求的数组a的个数。
题解
最后选的序列,一定是每个数字都是最小的那个数字a[1]的倍数。
为啥?
假设,存在某个数字a[i],这个a[i]不是a[1]的倍数。
那么我们看两个取模的顺序[a[1],a[2],...a[k]]和[a[i],a[1],a[2],...a[i-1],a[i+1],...a[k]]
我们取 x=a[i] ,先让x对第一个顺序的进行取模。
会发现,因为x%a[1]!=0,所以x对a[1]取余之后,设新的x为ans1,则0<ans1<a[1]<a[2]...a[k]
这意味着什么呢? 实际上这就表明,ans1再和后面的a[2..k]进行取模,结果还是ans1。(因为和一个比自己大的数字取模,还是本身嘛)
那x和第二个顺序的进行取模会怎么样呢?发现x=a[i]和a[i]先取模,变成0了!再取模也没用,肯定 都是0。
所以,只要数组中有不是a[1]的倍数的,那么把它放在最开头和x=a[i]进行取模,那个取模的顺序结果 ans2肯定等于0。
而ans1经过上面的分析,随便取x=某个不是a[1]倍数的数字,和a[1]进行取模操作结果为ans1,ans1都会小于a[2],a[3]..a[k]从而最后 取余结果就是ans1(不等于0)。
所以,取的k-1个数字肯定都是a[1]的倍数。
然后再正面说下为啥都是a[1]的倍数就行,因为你和k*a、a取模的话(不管什么样的顺序),最后的结果肯定是和a直接取模相同的(这个不想证啦。。感觉很显然对吧。。)。
而a[1]的倍数有n/a[1]个,去掉a[1]还有 \(n/a[1] - 1\) 个, 从中选出k-1个, 则累加的序列个数就为 \(C^{k-1}_{n/a[1]-1}\) 个。
枚举a[1]等于1..n-k+1然后累加组合数就好。
组合数要用到乘法逆元,预处理一下n!和1/n!对MOD取余的结果就好。
总结
这题关键点在于猜到最后结论,一定每个数都是a[1]的倍数这点, 猜到这一点, 然后试试发现。
不管怎么样取模,最后都和直接与a[1]取模的结果相同。问题就解决啦。
题解
#include<bits/stdc++.h>
#define ll long long
#define rei(x) scanf("%d",&x)
#define rel(x) scanf("%I64d",&x)
#define rep1(i,a,b) for (int i = a;i <= b;i++)
#define rep2(i,a,b) for (int i = a;i >= b;i--)
using namespace std;
const ll MOD = 998244353;
const int N = 5e5;
int n,k;
ll fac[N+10],rfac[N+10];
/*
n!/((n-m)!*m!)
*/
ll combinations(int n,int m){
return fac[n]*rfac[n-m]%MOD*rfac[m]%MOD;
}
ll _pow(ll x,ll y){
ll ans = 1;
while (y>0){
if (y&1){
ans = ans * x%MOD;
}
x = (x*x)%MOD;
y = y/2;
}
return ans;
}
int main(){
#ifdef LOCAL_DEFINE
freopen("D:\\rush.txt","r",stdin);
#endif
ios::sync_with_stdio(0),cin.tie(0);
fac[0] = 1;fac[1] = 1;
rep1(i,1,N) fac[i] = fac[i-1]*i%MOD;
rfac[N] = _pow(fac[N],MOD-2);
rep2(i,N-1,0) rfac[i] = rfac[i+1]*(i+1)%MOD;
cin >> n >> k;
ll ans = 0;
rep1(i,1,n){
int tmp = n/i;
tmp--;
if (tmp<k-1) break;
ans = ans + combinations(tmp,k-1);
ans%=MOD;
}
cout<<ans<<endl;
return 0;
}