题目传送门:Priority Queue
这道题目假如有长度为n,那么子序列就有\(2^n\)个,如果按照n是500的数据范围那么肯定是回超时的。既然是算总和,我们只要对于这个序列中的每一个数到底有多少个序列包含了它(我们在这里叫x)。这样就可以算出这个数对答案的贡献。
我们假设\(dp[i][j]\)的意思是在前i 个字符串中选取的数中有j个数比x小(注意如果和x相同大,但是位置比x考前那么也算进去)
然后思考转移方程式:
- 如果当前这个数字就是x,那么\(dp[i][j] = dp[i-1][j]\)(一定是取这个数字,不然没贡献)
- 如果当前这个数字小于x,或者和x相同大但是位置比x考前\(dp[i][j] = dp[i-1][j]+dp[i-1][j-1]\)(前面是不取,后面是取, ps:这里要注意j为0的情况)
- 如果当前这个数字大于x,或者和x相同大但是位置比x靠后\(dp[i][j] = dp[i-1][j]+dp[i-1][j]\)(前面这个是取,后面是不取)
- 如果当前这个数字是符号,那么我们再进行分类:
- 如果当前j为0,那么\(dp[i][0] = dp[i-1][1]+dp[i-1][0]+dp[i-1][j]\)(前两个是取,后面是不取)
- 如果当前j不是0,那么\(dp[i][j] = dp[i-1][j+1]+dp[i-1][j]\)(前面是取,后面是不取)
#include<iostream>
#include<string>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int maxn = 505;
int dp[maxn][maxn];
int num[maxn];
void ini()
{
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
}
int main()
{
int n;
char ope;
ll ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf(" %c", &ope);
if(ope == '-') num[i] = -1;
else scanf("%d", &num[i]);
}
for(int i = 1; i <= n; i++)
{
if(num[i] == -1) continue;
ini();
for(int z = 1; z <= n; z++)
{
if(i == z)
{
for(int g = 0; g <= z; g++)
dp[z][g] = dp[z-1][g];
}
else if(num[z] == -1)
{
if(z < i)
dp[z][0] = (0ll + dp[z-1][0]*2 + dp[z-1][1])%mod;
else
dp[z][0] = (0ll + dp[z-1][0]+dp[z-1][1])%mod;
for(int g = 1; g <= z; g++)
{
dp[z][g] = (0ll + dp[z-1][g+1] + dp[z-1][g])%mod;
}
}
else if(num[z] < num[i] || (num[z] == num[i] && z < i))
{
dp[z][0] = dp[z-1][0];
for(int g = 1; g <= z; g++)
dp[z][g] = (0ll + dp[z-1][g] + dp[z-1][g-1])%mod;
}
else
{
for(int g = 0; g <= z;g++)
dp[z][g] = (2ll*dp[z-1][g])%mod;
}
}
for(int g = 1; g <= n; g++)
dp[n][g] = (dp[n][g] + dp[n][g-1])%mod;
ans = (1ll*dp[n][n]*num[i] + ans)%mod;
}
printf("%lld", ans);
return 0;
}