[CF1528B]Kavi on Pairing Duty
[CF1528B]Kavi on Pairing Duty
壹、题目描述 ¶
有 \(2n\) 个点在一条直线上,对于第 \(i\) 个点,满足 \(x_i=i\),你需要把他们两两配对,为了方便叙述,我们定义一个匹配 \(i\) 左边的点为 \(l_i\),右边的点为 \(r_i\). 一种配对方案是好的当且仅当对于任意两个配对 \(i,j\),满足下列条件中的一个:
- 两个匹配的长度一样,即 \(r_i-l_i+1=r_j-l_j+1\);
- 一个匹配完全包含另外一个匹配,即 \(l_i<l_j<r_j<r_i\;∨\;l_j<l_i<r_i<r_j\);
给定 \(n\le 10^6\),求好的匹配的方案数。
贰、题解 ¶
我们将配对看成连线,就像原题中给的图一样。
考虑定义 \(f_i\) 为有 \(2i\) 个点时的好的匹配方案数,显然答案为 \(f_n\). 问题来到如何求 \(f_i\).
定义 \(x\) 为 \(1\) 号点所匹配的点,那么,对于所有 \(p\in[x+1,2n]\) 都有 \(p\) 所属配对的长度都为 \(\lang 1,x\rang\) 配对的长度,即 \(x\).
证明十分简单,因为 \(p\) 已经在 \([1,x]\) 之外了,所以 \(p\) 所属的配对不可能与 \(\lang 1,x\rang\) 满足第二个条件,那么就只有可能满足第一个条件了。
现在,我们对于 \(x\) 分类进行讨论:
- 当 \(x\le n\),那么所有 \([x+1,2n]\) 中的点的长度都得是 \(x\),而 \((n,2n]\) 显然是 \([x+1,2n]\) 的子集,这说明所有的配对长度都得是一样的,这只有当 \(x\mid n\) 时满足,故这种情况的方案数为 \(D(n)\),即 \(n\) 的因数个数;
- 当 \(x>n\) 时,对于所有 \(i\in[x+1,2n]\) 的配对,都只有可能是 \(i-x+1\),因为需要满足长度相等,且配对的点在 \([1,2n]\) 之间,也就是说,对于所有 \([x,2n]\) 的点和 \([1,2n-x]\) 配对了,剩下的就是 \([2n-x+1,x-1]\) 的点自由配对,一共有 \(2x-2n-2\) 个点,也就是 \(f_{x-n-1}\),又 \(x\in [n+1,2n]\),那么有 \(x-n-1\in [0,n-1]\),这种情况就是 \(\sum_{i=0}^{n-1}f_i\),而 \(f_0=0\),我们可以进一步化简为 \(\sum_{i=1}^{n-1}f_i\);
综上,构成 \(f_i\) 的有两个部分:
- \(D(i)\),即 \(i\) 的因数个数;
- \(\sum_{k=1}^{i-1}f_k\);
计算 \(D(i)\) 可以使用埃氏筛法,时间复杂度 \(\mathcal O(n\log n)\);
叁、参考代码 ¶
#include<cstdio>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
// #define NDEBUG
#include<cassert>
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
const int mod=998244353;
const int maxn=1e6;
int f[maxn+5], n;
signed main(){
n=readin(1);
rep(i, 1, n) for(int j=i; j<=n; j+=i) ++f[j];
int tmp=0;
rep(i, 1, n){
f[i]=(f[i]+tmp)%mod;
tmp=(tmp+f[i])%mod;
}
writc(f[n], '\n');
return 0;
}
肆、关键 の 步骤 ¶
挖掘特性是十分重要的。
这道题比较关键的地方在于当 \(1\) 与某个点进行匹配之后,有一些点所属的配对就也被固定了。
一般地,对于任意 \(l,r\) 的配对,在 \([1,l-1]\) 与 \([r+1,2n]\) 的点都不可能与它们满足包含关系了,而当 \(l=1\) 或者 \(r=2n\) 时,这两个区间实际上就只有一个有定义了。所以实际上我们找的时这个特性的特殊情况,特性的特性?