一、题目描述:
给定一个序列 $A$,$A$ 的每一个元素形如 $+x$ 和 $-$,其中 $x$ 为一个整数($1\le x< 998244353$)。
对于 $A$ 的一个子序列 $S$,按如下方式计算 $S$ 的权值:
你需要依次遍历 $S$ 中的元素,并且按照序列维护一个小根堆 $T$。
特别的,若 $T$ 中没有数,那么就不进行删除操作。
在遍历完 $S$ 中的元素后,将小根堆 $T$ 中所有数的和 $sum$ 算出来。
$sum$ 即为 $S$ 的权值。
求 $A$ 中所有子序列 $S$ 的权值和。对 $998244353$ 取模。
数据范围:$1\le n\le 500$。
二、解题思路:
掌握一个算贡献的方法:$\sum_A\sum_{x\in A}=\sum_xx\times \sum_A[x\in A]$
于是我们可以很容易的想到单独对于每个 $+x$ 统计贡献。
然后这个是非常容易计数 $dp$ 的,设目前我们枚举的位置为 $p$。值为 $val$。
$f_{i,j}$ 表示目前枚举到序列的前 $i$ 个元素,小根堆中有 $j$ 个数字小于 $val$ 的子序列个数。
转移很好转移,注意 $i$ 与 $p$ 的相对位置来进行讨论。
相同的数字大小关系定一个标准就行了,比如下标小的就视为更小。
单次 $dp$ 时间复杂度 $O(n^2)$,总时间复杂度 $O(n^3)$。
三、完整代码:
1 #include<iostream> 2 #define N 510 3 #define mod 998244353 4 #define ll long long 5 #define rep(i,l,r) for(ll i=l;i<=r;i++) 6 using namespace std; 7 ll n,ans; 8 ll f[N][N]; 9 ll op[N],a[N]; 10 void add(ll &u,ll v){ 11 u+=v; 12 if(u>=mod) u%=mod; 13 } 14 ll calc(ll p){ 15 ll val=a[p]; f[0][0]=1; 16 rep(i,1,n) rep(j,0,i) f[i][j]=0; 17 rep(i,1,n){ 18 rep(j,0,i) f[i][j]=f[i-1][j]; 19 if(i<p){ 20 if(!op[i]){ 21 rep(j,0,i) add(f[i][j],f[i-1][j+1]); 22 add(f[i][0],f[i-1][0]); 23 }else{ 24 if(a[i]>val){ 25 rep(j,0,i) add(f[i][j],f[i-1][j]); 26 }else{ 27 rep(j,1,i) add(f[i][j],f[i-1][j-1]); 28 } 29 } 30 }else if(i>p){ 31 if(!op[i]){ 32 rep(j,0,i) add(f[i][j],f[i-1][j+1]); 33 }else{ 34 if(a[i]>=val){ 35 rep(j,0,i) add(f[i][j],f[i-1][j]); 36 }else{ 37 rep(j,1,i) add(f[i][j],f[i-1][j-1]); 38 } 39 } 40 } 41 } 42 ll res=0; 43 rep(i,0,n) add(res,f[n][i]); 44 return res; 45 } 46 signed main(){ 47 ios::sync_with_stdio(false); 48 cin.tie(0); cout.tie(0); 49 cin>>n; 50 rep(i,1,n){ 51 char x; cin>>x; 52 if(x=='+') op[i]=1,cin>>a[i]; 53 } 54 rep(i,1,n) if(op[i]) add(ans,calc(i)*a[i]); 55 cout<<ans<<'\n'; 56 return 0; 57 }
四、写题心得:
复竞了,感觉手好生。只有写点简单题找一下感觉了。
不过这个题还是很有意思啊!拜谢 $lsy$ 大佬。省选加油!