天天看点

CF1561D1 Up the Strip (simplified version) 题解

容易想到 f i f_i fi​ 表示走到 i i i 的方案数和转移方程(一开始我这个蒟蒻居然还觉着是刷表呢

f x = ( ∑ i = x + 1 n f i ) + ( ∑ { f y ∣ x = ⌊ y k ⌋ } ) f_x=(\sum_{i=x+1}^nf_i)+(\sum\{f_y | x=\lfloor \frac{y}{k} \rfloor\}) fx​=(i=x+1∑n​fi​)+(∑{fy​∣x=⌊ky​⌋})

第一项很好求,维护一个后缀和,难点主要是在第二项

我们可以枚举 k k k ,这时我们的 k k k 固定下来了,显然 y ∈ [ x k , x k + k ) y\in [xk,xk+k) y∈[xk,xk+k) 这样余数会被下取整抹掉,这样的话我们可以用后缀和维护这一段的和,于是我们的状态数是 O ( n ) O(n) O(n) 的,转移是枚举一个 k k k ,是 O ( log ⁡ 2 n ) O(\log_2 n) O(log2​n) 的,总的复杂度就是 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2​n)

(考试的时候还推出一个没什么用的结论,就是 [ x k , x k + k ) [xk,xk+k) [xk,xk+k) 之间的数不会因为第二个操作而互相影响,因为 2 x y ≥ x k + k 2xy \ge xk+k 2xy≥xk+k 以后可能用得到吧)

代码:

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define pii pair<int, int>
#define mem(a, x) memset(a, x, sizeof(a))
#define rps(i, b, e) for(int i=(b);i<=(e);i++)
#define prs(i, e, b) for(int i=(e);i>=(b);i--)
#define rp(i, e) rps(i, 1, e)
#define pr(i, e) prs(i, e, 1)
#define rp0(i, e) rps(i, 0, (e)-1)
#define pr0(i, e) prs(i, (e)-1, 0)
#define rpg(i, x) for(int i=head[x];i;i=e[i].nxt)
#define opf(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
using namespace std;
const int NR=2e5+10;
typedef long long LL;
LL n, p, f[NR], s[NR];
int main()
{
	cin>>n>>p;
	f[n]=s[n]=1;
	pr(i, n-1) {
		f[i]=s[i+1];
		for(LL j=2;i*j<=n;j++) {
			f[i]=(f[i]+(s[i*j]+p-s[min(i*j+j, n+1)]))%p; 
		}
		s[i]=(s[i+1]+f[i])%p;
	}
	cout<<f[1];
	return 0;
}
           

后记:这题本来考场上推出来了,以为复杂度是 O ( n n ) O(n\sqrt n) O(nn

​) 现在才知道有调和级数这个东西,所以没敢提交 D2 ,后悔啊,要是内会提交了我的 rating 就起飞了

继续阅读