LYK loves 整数拆分 (dp好题)

题面

LYK 最近在研究整数拆分问题。他认为一个数字可以被拆分成若干正整数之和。例如当
n=5 时。有以下几种拆分方式。
5=1+1+1+1+1
5=1+1+1+2
5=1+1+3
5=1+2+2
5=1+4
5=2+3
5=5
对于其中一种拆分方式,所产生的价值为其中每一对数的 gcd 之和。
例如5=1+2+2,它所产生的价值为 gcd(1,2)+gcd(1,2)+gcd(2,2)=4。对于5=5这种拆分方式,
产生的价值为 0。
当然 LYK 也有部分不喜欢的数,它想知道对于所有拆分方式中没有它不喜欢的数的拆分
中,产生的价值之和对 1000000007 取模后的结果是多少。

输入格式

若干组数据,不超过 5 组。
对于每组数据,第一行一个数 n。
接下来一行第一个数 m,表示有 m 个 LYK 不喜欢的数字,紧接着 m 个数,表示 LYK 不
喜欢的数字。

输出格式

输出若干行表示答案。

数据范围

n<=2000

1500 ms,128 mb

题解

n <= 2000,算是比较可喜的数据范围。

于是这题的核心就在于设计一个好的 dp,找到一种好的枚举方法,能够在 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn) 范围内过掉。

我直接说了,我们最后是可以枚举数对算 gcd 的贡献的,因此我们先求 n 的拆分方案数,设 f [ i ] f[i] f[i] 表示 LYK 是否不喜欢 i 。

d p [ i ] [ j ] dp[i][j] dp[i][j] 为把 i 进行拆分,拆出来最大的一个数小于等于 j 的方案数(第二维纯粹是方便转移),那么有如下方程:
d p [ i ] [ j ] = ∑ k = 1 , f [ k ] = 0 j d p [ i − k ] [ k ] dp[i][j]=\sum_{k=1,f[k]=0}^{j}dp[i-k][k] dp[i][j]=k=1,f[k]=0jdp[ik][k]

正着不好做,我们反着来,那么 dp[i][j](f[j]=0)就对 dp[i+j][j~n] 都有一份贡献,相当于后面一段都要加,我们打个标记,用前缀和优化可以做到 O ( n 2 ) O(n^2) O(n2)

然后我们枚举数对 ( i , j ) (i,j) (i,j) 求它们的贡献,这时的枚举方法,或者说定义方法就比较巧了。

首先 i = j i=j i=j 的情况就不用多说了吧,枚举拆分方案内从左到右第 x 个 i ,然后对答案贡献为 ( d p [ n − x i ] [ n ] ∗ i ) ∗ ( x − 1 ) (dp[n-xi][n]*i)*(x-1) (dp[nxi][n]i)(x1),即与它左边的每一个都形成一对。

考虑拆分方案中 ( i , j ) (i,j) (i,j) 可能不止一个,我们就在此基础上枚举个 ( x , y ) (x,y) (x,y),表示拆分方案中从左往右数第 x 个 i 和第 y 个 j 形成的数对,那么一种 “ ( i , j ) , ( x , y ) (i,j),(x,y) (i,j),(x,y)” 对答案的贡献就为 d p [ n − x i − y j ] [ n ] ∗ g c d ( i , j ) dp[n-xi-yj][n]*gcd(i,j) dp[nxiyj][n]gcd(i,j),这样是不会算重的。

然后我们是不是要这样枚举呢?

for(int i = 1;i <= n;i ++) {
    
    
	for(int j = i + 1;j <= n;j ++) {
    
    
		for(int x = 1;x * i <= n;x ++) {
    
    
			for(int y = 1;y * j + x * i <= n;y ++) {
    
    
				ans += ...
			}
		}
	}
}

根据对埃筛的复杂度分析我们可以判断,这样的复杂度是 O ( n 2 ln ⁡ 2 n ) O(n^2\ln^2n) O(n2ln2n) 的,多了个 ln ⁡ \ln ln ,过不了,那其实我们还可以根据 j 的不同对 d p [ . . . ] [ n ] dp[...][n] dp[...][n] 求出 j 种前缀和,这样就只用枚举 x ,不用枚举 y ,我们用前缀和优化又省了一个 log。

CODE

#include<map>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 2005
#define LL long long
#define ULL unsigned long long
#define DB double
#define ENDL putchar('\n')
#define eps 1e-5
#pragma GCC optimize(2)
LL read() {
    
    
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {
    
    if(s == '-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {
    
    x=x*10+(s-'0');s = getchar();}
	return f * x;
}
const int MOD = 1000000007;
int n,m,i,j,s,o,k;
bool f[MAXN];
int dp[MAXN][MAXN],tag[MAXN][MAXN];
int sm[MAXN][MAXN];
int gcd(int a,int b) {
    
    return b == 0 ? a:gcd(b,a % b);}
int main() {
    
    
	freopen("zscf.in","r",stdin);
	freopen("zscf.out","w",stdout);
	while(scanf("%d",&n) == 1) {
    
    
		m = read();
		memset(f,0,sizeof(f));
		memset(dp,0,sizeof(dp));
		memset(sm,0,sizeof(sm));
		memset(tag,0,sizeof(tag));
		for(int i = 1;i <= m;i ++) s = read(),f[s] = 1;
		for(int i = 1;i <= n;i ++) {
    
    
			dp[0][i] = 1;
			if(!f[i]) (tag[i][i] += 1) %= MOD;
		}
		for(int i = 1;i <= n;i ++) {
    
    
			int ss = 0;
			for(int j = 1;j <= n;j ++) {
    
    
				(ss += tag[i][j]) %= MOD;
				dp[i][j] = ss;
				if(!f[j] && i+j <= n) (tag[i+j][j] += dp[i][j]) %= MOD;
			}
		}
		for(int i = 1;i <= n;i ++) {
    
    
			for(int j = 0;j <= n;j ++) {
    
    
				sm[i][j] = dp[j][n];
				if(j >= i) (sm[i][j] += sm[i][j-i]) %= MOD;
			}
		}
		int ans = 0;
		for(int i = 1;i <= n;i ++) {
    
    
			if(f[i]) continue;
			for(int j = i+i,k = 1;j <= n;j += i,k ++) {
    
    
				(ans += dp[n-j][n] *1ll* k % MOD *1ll* i % MOD) %= MOD;
			}
			for(int j = i+1;j <= n;j ++) {
    
    
				if(f[j]) continue;
				int gc = gcd(i,j);
				for(int k = i;k <= n && k+j <= n;k += i) {
    
    
					(ans += sm[j][n-k-j] *1ll* gc % MOD) %= MOD;
				}
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43960414/article/details/114443875
lyk