蓝桥杯 PREV-30 波动数列(0/1背包)

题目链接:

PREV-30 波动数列

思路:

设这个数列首项为 A A ,由题意可知总和一定可以表示成 n A + x a y b ( x + y = n ( n 1 ) 2 ) nA+xa-yb(x+y=\frac{n(n-1)}{2}) 的形式,其中 x x + a +a 操作的次数和, y y b -b 操作的次数和;
那么我们有 n A = s x a + y b ( x + y = n ( n 1 ) 2 & 0 x , y n ( n 1 ) 2 ) nA=s-xa+yb\quad(x+y=\frac{n(n-1)}{2}\&0\leq x,y\leq\frac{n(n-1)}{2})
而我们知道当且仅当右式可以被 n n 整除时 A A 有解,因此我们需要枚举每一组 ( x , y ) (x,y) ,带进右式检查此时 A A 是否有解,如果有解,那么我们的答案应该加上 x , y x,y 为当前值时、数列的 + a , b +a,-b 分布情况的种数;
|------------------------------------------------------------------------------------------------------------|
此时的问题只剩下:对于给定的 ( n , x ) (n,x) ,数列的可能种数( y y 可以通过 n ( n 1 ) 2 x \frac{n(n-1)}{2}-x 计算得出);
对于每一项,它只有 + a +a 或者不 + a +a ,因此是0/1背包的模型,我们设 d p [ i ] [ j ] ( 0 i < n ) dp[i][j](0\leq i<n) 为:长度为 n n 的数列,第 0 0 项到第 i i 项的 + a +a 情况对最后 + a +a 总和的影响数;
我们知道第 i i 项如果 + a +a ,那么后面的所有项都会受到一次 + a +a 的影响,所以第 i i 如果是 + a +a 操作,那么对整体的贡献就是 n i n-i + a +a (注意 i i 0 0 开始取),因此 d p [ i ] [ j ] + = d p [ i 1 ] [ j ( n i ) ] dp[i][j]+=dp[i-1][j-(n-i)] ;如果第 i i 项不执行 + a +a ,那么它贡献为 j j 时的种数就和前一项贡献为 j j 的种数是一样的;
综上: d p [ i ] [ j ] = d p [ i 1 ] [ j ] + d p [ i 1 ] [ j n + i ] dp[i][j] = dp[i-1][j] + dp[i-1][j - n + i]
而这种二维dp数组可以通过倒着求而变成一维滚动数组以节省大量空间;

代码:

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const ll mod = 100000007;
ll n, s, a, b;
ll dp[1000000];

int main() {
#ifdef MyTest
	freopen("Sakura.txt", "r", stdin);
#endif
	cin >> n >> s >> a >> b;
	dp[0] = 1;
	for(int i = 1; i < n; ++i)
		for(int j = n * i - (1 + i) * i / 2; j >= n - i; --j) {
			dp[j] = (dp[j] + dp[j - n + i]) % mod;
		}
	ll ans = 0, tot = n * (n - 1) >> 1;
	for(ll i = 0; i <= tot; ++i)
		if((s - i * a + (tot - i) * b) % n == 0) {
			ans = (ans + dp[i]) % mod;	
		}
	cout << ans;
	return 0;
}
发布了356 篇原创文章 · 获赞 12 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_45228537/article/details/104392819