洛谷 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;
}