标签:clu 代码 组合 个数 优化 %s max 错误 一个
讲道理一看到辣么小的\(n\),我就想到了状压dp。
枚举子集?哦,\(O(3^n)\)的复杂度。但是我没有注意到\(n=20\)的时候是过不了的。
接下来我就在想70pts的状压dp要怎么写?
没有清楚定义状态的我连样例2都过不了,只能够手动枚举拿30pts。
所以最简单的月赛也爆炸了
下面的做法是借鉴luogu题解的,不是抄袭。(你信吗?)
设dp[i]
表示从全0转移到i状态的所有歉意值之和。
那么会有
\[dp[i]=\sum{dp[j] + qy[j] \times num[j]}, j \in i and j \neq \emptyset\]
其中num[j]
表示从全0转移到j的方案数,qy[j]
表示当前这个状态的歉意值。
num[j]
自然也是可以递推的,可以这么递推:
\[num[j] = \sum{num[k]}, k \in j and k \neq \emptyset\]
用枚举子集的优化技巧就可以做到\(O(3^n)\)的搞出来了。
不给代码,题解里面是有的。
我的错误之处:没有意识到方案数是对答案有影响的。
使用贡献法,那么每一个歉意值就会对答案产生贡献了。
产生的贡献是多少?是这个状态在所有方案中出现的次数 乘以 这个方案的歉意值。
还能够发现:其实一个状态只跟它的01个数有关,跟她们的顺序是无关的。
所以设一个\(num[i]\)表示填\(i\)个1的方案数,那么就可以递推出来了。
递推式是这样子的:
\[num[i] = \sum_{j=1}^n{num[i-j] \times C_i^j}\]
那么最后的答案就是该方案0的个数 乘以 该方案1的个数 乘以 这个方案的歉意值。
对了:注意取膜!否则你就只有80pts啦。
代码:
#include<cstdio>
#include<cstring>
#define ll long long
const int maxn = 21, maxN = 1048580;
const int MOD = 998244353;
ll dp[maxN];
ll qy[maxN];
ll num[maxn];
ll C[maxn][maxn];
int n, m;
void init()
{
for(int i = 0; i <= 20; i++)
{
C[i][0] = C[i][i] = 1;
}
for(int i = 1; i <= 20; i++)
{
for(int j = 1; j < i; j++)
{
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
}
}
int popcount(char *ch)
{
int len = strlen(ch);
int res = 0;
for(int i = len - 1; i >= 0; i--)
{
if(ch[i] == ‘1‘) res++;
}
return res;
}
int main()
{
init();
scanf("%d%d", &n, &m);
num[0] = 1;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= i; j++)
{
num[i] = (num[i] + C[i][i - j] * num[i - j] % MOD) % MOD;
}
}
ll ans = 0;
for(int i = 1; i <= m; i++)
{
char ch[25]; int x;
scanf("%s%d", ch, &x);
int temp = popcount(ch), len = strlen(ch);
ans = (ans + x * num[temp] % MOD * num[len - temp] % MOD) % MOD;
}
printf("%lld\n", ans);
return 0;
}
标签:clu 代码 组合 个数 优化 %s max 错误 一个
原文地址:https://www.cnblogs.com/Garen-Wang/p/9913101.html