普通DP——CF1542D Priority Queue

题目传送门: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;
}