洛谷 P5131 荷取融合

DP

一个值得思考的 \(DP\) 题。

\(f[i][j]\) 为前 \(i\) 个中选了 \(j\) 次的方案数, \(g[i][j]\) 为前 \(i\) 个中选了 \(j\) 次的所有方案的价值积的和。枚举上一个选取的位置和次数即可.

\(O(n^2k^2)\) \(20\)分 空间也会爆炸

#include<iostream>
#include<cstdio>
#define LL long long
using namespace std;
int n, k;
LL tot, sum;
const int N = 100010, mod = 19260817;
int a[N];
LL f[N][305], g[N][305];
LL ksm(LL a, LL b, LL mod)
{
	LL res = 1;
	for (; b; b >>= 1, a = a * a % mod)
		if (b & 1)res = res * a % mod;
	return res;
}
int main()
{
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);
	f[0][0] = 1; g[0][0] = 1;
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= k; ++j)
			for (int l = 0; l < i; ++l)
				for (int o = 0; o < j; ++o)(f[i][j] += f[l][o]) %= mod;
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= k; ++j)
			for (int l = 0; l < i; ++l)
				for (int o = 0; o < j; ++o)(g[i][j] += g[l][o] * ksm(a[i], j - o, mod)) %= mod;
	for (int i = 1; i <= n; ++i)(tot += f[i][k]) %= mod, (sum += g[i][k]) %= mod;
	cout << sum*ksm(tot, mod - 2, mod) % mod;
	return 0;
}

发现枚举上一个位置并没有什么用,记录一下前缀和就行了。

另外空间也只需要一维就行。

\(O(nk^2)\) \(70\)

#include<iostream>
#include<cstdio>
#define LL long long
using namespace std;
int n, k, now;
LL tot, sum;
const int N = 100010, mod = 19260817;
int a[N];
LL f[305], g[305], F[305], G[305];
LL ksm(LL a, LL b, LL mod)
{
	LL res = 1;
	for (; b; b >>= 1, a = a * a % mod)
		if (b & 1)res = res * a % mod;
	return res;
}
int main()
{
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);
	F[0] = G[0] = 1; now = 1;
	for (int i = 1; i <= n; ++i)
	{
		for (int j = k; j >= 1; --j)
		{
			f[j] = g[j] = 0;
			for (int o = 0; o < j; ++o)(f[j] += F[o]) %= mod;
			(F[j] += f[j]) %= mod;
			for (int o = 0; o < j; ++o)(g[j] += G[o] * ksm(a[i], j - o, mod)) %= mod;
			(G[j] += g[j]) %= mod;
		}
		(tot += f[k]) %= mod; (sum += g[k]) %= mod;
	}
	cout << sum*ksm(tot, mod - 2, mod) % mod;
	return 0;
}

发现循环\(o\)的含义就是类似于前缀和,差分一下就行。(\(g\) 的差分需要将快速幂拆开)

\(O(nk)\) \(80\)

#include<iostream>
#include<cstdio>
#define LL long long
using namespace std;
int n, k, now;
LL tot, sum;
const int N = 100010, mod = 19260817;
int a[N];
LL f[305], g[305], F[305], G[305], S[305];
LL ksm(LL a, LL b, LL mod)
{
	LL res = 1;
	for (; b; b >>= 1, a = a * a % mod)
		if (b & 1)res = res * a % mod;
	return res;
}
int main()
{
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);
	F[0] = G[0] = S[0] = 1; now = 1;
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= k; ++j)f[j] = (f[j - 1] + F[j - 1]) % mod;
		for (int j = 1; j <= k; ++j)(F[j] += f[j]) %= mod;
		for (int j = 1; j <= k; ++j)S[j] = (S[j - 1] + G[j] * ksm(ksm(a[i], mod - 2, mod), j, mod)) % mod;
		for (int j = 1; j <= k; ++j)g[j] = ksm(a[i], j, mod) * S[j - 1] % mod;
		for (int j = 1; j <= k; ++j)(G[j] += g[j]) %= mod;
		(tot += f[k]) %= mod; (sum += g[k]) %= mod;
	}
	cout << sum*ksm(tot, mod - 2, mod) % mod;
	return 0;
}

上面的方法虽然时间复杂度对了,但由于快速幂跑的慢,递推省掉快速幂就行了。

\(O(nk)\) \(100\)

#include<iostream>
#include<cstdio>
#define LL long long
using namespace std;
int n, k;
LL tot, sum;
const int N = 100010, mod = 19260817;
int a[N];
int f[305], g[305], F[305], G[305], S[305];
inline int ksm(int a, LL b)
{
	int res = 1;
	for (; b; b >>= 1, a = (LL)a * a % mod)
		if (b & 1)res = (LL)res * a % mod;
	return res;
}
inline int work(int x) {return x >= mod ? x - mod : x;}
int main()
{
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);
	F[0] = G[0] = S[0] = 1;
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= k; ++j)f[j] = work(f[j - 1] + F[j - 1]);
		for (int j = 1; j <= k; ++j)F[j] = work(F[j] + f[j]);
		for (int j = 1, inv = ksm(a[i], mod - 2), now = 1; j <= k; ++j)now = (LL)now * inv % mod, S[j] = work(S[j - 1] + (LL)G[j] * now % mod);
		for (int j = 1, now = 1; j <= k; ++j)now = (LL)now * a[i] % mod, g[j] = (LL)now * S[j - 1] % mod;
		for (int j = 1; j <= k; ++j)G[j] = work(G[j] + g[j]);
		tot = work(tot + f[k]); sum = work(sum + g[k]);
	}
	cout << sum*ksm(tot, mod - 2) % mod;
	return 0;
}
posted @ 2020-03-20 20:10  wljss  阅读(158)  评论(0编辑  收藏  举报