CF1542D Priority Queue 题解 dp

原题链接

阅读提示

由于 dp 转移式分类情况较多,每个类别本人都单独写出了转移式,写代码时要整合一下。

思路

我们要算的是 ∑ A A ⊂ S ∑ i i ∈ T i \sum_{A}^{A\subset S}\sum_{i}^{i\in T}i AASiiTi S S S 是包含所有操作的集合, T T T 是做完选中的操作 A A A 后的可重集合)。这种表达方式十分抽象,但是这两个求和符号给了我们两种考虑的角度。

以 A 集合(操作)为主

由于每个操作可以选中也可以不选中,所以容易想到以 d p i dp_i dpi 来表示考虑到第 i i i 个操作, f ( A ) f(A) f(A) 的和(答案)。删除操作代表着无论到哪个状态都必须知道目前的最小值,这也就意味着每做一个操作都需要记录,因为不知道什么时候可能会用到当前加入的数。这样 d p i dp_i dpi 就需要增加很多维度,几乎算是要把整个 T T T 集合记录在里面,不可行。

以 T 集合(数字)为主

谁说这两个 ∑ \sum 不能调换一下顺序?我们先枚举出现过的每个数,计算这个数会在做完多少种操作序列 A A A 后存在于 T T T 集合,对答案进行贡献。这样有了针对的对象,某个数 a i a_i ai,就不用记录所有数了,只要保证 a i a_i ai 被加入了并且没被删掉。 d p j , k dp_{j,k} dpj,k 表示考虑到第 j j j 个操作, a i a_i ai 目前排在从小到大第 k k k 名,有多少种选择操作的方案。我们先对所有状态进行分类:

还没到第 i i i 个操作( j < i j<i j<i), a i a_i ai 未被加入。

此时无论加减都无所谓,不影响 a i a_i ai,为了方便 a i a_i ai 后来加入, k k k 表示 a i a_i ai 如果现在加入,会排在第几名。

  1. 删除操作
    如果删除了一个数那么 k k k 就会减 1 1 1。要是 k = 1 k=1 k=1 了,还能删吗?当然能, a i a_i ai 都没加入,删了, a i a_i ai 的预估排名还是 1 1 1 k = 1 k=1 k=1 也代表着开始删 > a i >a_i >ai 的数或者集合已经空了。转移式中 d p j − 1 , k dp_{j-1,k} dpj1,k 表示不选中这个操作,对排名无影响。 d p j , k = { d p j − 1 , k + d p j − 1 , k k = 1 d p j − 1 , k + d p j − 1 , k + 1 k > 1 \\ dp_{j,k} = \begin{cases} dp_{j-1,k}+dp_{j-1,k} & k=1 \\ dp_{j-1,k}+dp_{j-1,k+1} & k>1 \\ \end{cases} dpj,k={dpj1,k+dpj1,kdpj1,k+dpj1,k+1k=1k>1
  2. 加入操作
    如果加入了一个比 a i a_i ai 小的数 k k k 就会加 1 1 1,加入了一个比 a i a_i ai 大的 k k k 就没有变化。那如果加入了一个相等的呢?此时需要确定一种顺序,比如说按照下标,如果数值相等,下标越小,排得越靠前。如果不确定顺序,每次有相等的数,做删除操作时都尽量不删 a i a_i ai(每次的规则都在变),会重复算,答案变大。(转移式与 j > i j>i j>i 的加入操作一起写)
现在是第 i i i 个操作( j = i j=i j=i),必须选中,将 a i a_i ai 加入。

没什么好说的, d p j , k = d p j − 1 , k dp_{j,k}=dp_{j-1,k} dpj,k=dpj1,k

过了第 i i i 个操作( j > i j>i j>i), a i a_i ai 已经加入了。
  1. 删除操作
    此时如果 k = 1 k=1 k=1 可就不能删了(只能从 k + 1 k+1 k+1 k k k k + 1 > 1 k+1>1 k+1>1),不然 a i a_i ai 就出去了。
    d p j , k = d p j − 1 , k + d p j − 1 , k + 1 dp_{j,k} = dp_{j-1,k}+dp_{j-1,k+1} dpj,k=dpj1,k+dpj1,k+1
  2. 加入操作
    j < i j<i j<i 的情况相同,把转移式合在一起写了。 d p j , k = { d p j − 1 , k + d p j − 1 , k − 1 a j < a i 或 ( a j = a i 且 j < i ) d p j − 1 , k + d p j − 1 , k a j > a i 或 ( a j = a i 且 j > i ) \\ dp_{j,k} = \begin{cases} dp_{j-1,k}+dp_{j-1,k-1} & a_j<a_i 或 (a_j=a_i 且 j<i) \\ dp_{j-1,k}+dp_{j-1,k} & a_j>a_i 或 (a_j=a_i 且 j>i) \\ \end{cases} dpj,k={dpj1,k+dpj1,k1dpj1,k+dpj1,kaj<ai(aj=aij<i)aj>ai(aj=aij>i)

初始状态和答案

d p 0 , 1 = 1 dp_{0,1}=1 dp0,1=1:初始状态。没有做任何操作, a i a_i ai 如果加入就排在第一个。
∑ k = 1 k ≤ n d p n , k \\ \sum_{k=1}^{k\le n}dp_{n,k} k=1kndpn,k:总方案数。只要 a i a_i ai 还在里面,不论排名。
∑ k = 1 k ≤ n d p n , k × a i \\ \sum_{k=1}^{k\le n}dp_{n,k}\times a_i k=1kndpn,k×ai a i a_i ai 对答案的贡献。

代码

经验与教训:

  1. k k k 的最大值是 j + 1 j+1 j+1,因为在 a i a_i ai 还没加入的时候,做了 j j j 次添加操作,而 a i a_i ai 恰好比它们都大,加入就会排在第 j + 1 j+1 j+1 个。
  2. j < i j<i j<i 的时候,无论如何都可以删除,并且排名不变。上面转移的时候讲了,一开始本人没发现,错得很诡异。
#include<bits/stdc++.h>
using namespace std;
#define mod 998244353
int n,a[505],op[505];
char c;
long long dp[505][505],res,ans;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>c;
		op[i]=(c=='+'?1:0);
		if(op[i]) cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		if(!a[i]) continue;
		memset(dp,0,sizeof(dp));
		dp[0][1]=1;res=0;
		for(int j=1;j<=n;j++)
			for(int k=1;k<=j+1;k++){
				dp[j][k]=dp[j-1][k];
				if(i!=j&&op[j]==1){
					if(a[j]<a[i]||a[j]==a[i]&&j<i)
						dp[j][k]=(dp[j][k]+dp[j-1][k-1])%mod;
					else dp[j][k]=(dp[j][k]+dp[j-1][k])%mod;
				}
				else if(i!=j&&op[j]==0){
					dp[j][k]=(dp[j][k]+dp[j-1][k+1])%mod;
					if(k==1&&j<i) dp[j][k]=(dp[j][k]+dp[j-1][k])%mod;
				}
			}
		for(int j=1;j<=n;j++) res=(res+dp[n][j])%mod;
		ans=(ans+res*a[i]%mod)%mod;
	}
	cout<<ans<<endl;
	return 0;
}

此题代码调了很久,遂写文纪念,结果文章也写了很久,这分类讨论情况是真的多。[捂脸]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值