省选集训 12 - DP 专题

[CF1810G] The Maximum Prefix

从后往前求最大前缀和是容易的,加一个数 \(a_i\),最大前缀和 \(s\leftarrow \max(0,s+a_i)\)

所以可以从前往后定义 \(f_{i,j}\) 表示加到 \(i\),最大前缀和为 \(j\) 的概率。

当初值设为 \(f_{l+1,0}=1\) 时,长度 \(l\) 的答案即为 \(\sum_{i=0}^{l} f_{1,i}\times h_i\)

然后发现对于不同的长度,除初值外转移都是一样的,所以可以反推答案贡献。

\(g_{1,i}=h_i\),则有转移 \(g_{i,j}=g_{i-1,j+1}\times p_i+g_{i-1,\max(j-1,0)}\times (1-p_i)\)\(g_{l+1,0}\) 即为长度 \(l\) 的答案。

另一种方法,我们如果令最大前缀和为 \(x\),去算最大前缀和为 \(x\) 的贡献也是容易的。

\(f_{i,j,0/1}\) 表示枚举到 \(i\),与目标差值为 \(j\),是否达到过目标的答案,发现对于所有 \(x\) 转移是相同的。

所以令 \(f_{0,j,0/1}=h_j\),就可以一并得出所有的答案了,代码写的是第一种方法。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5005,mod=1000000007;
int t,n,p[N],h[N],dp[N][N];
int quick_pow(int x,int y,int res=1){
	for(;y;x=x*x%mod,y>>=1)  if(y&1)  res=res*x%mod;
	return res;
}
void solve(){
	cin>>n;
	for(int i=1,x,y;i<=n;i++){
		cin>>x>>y;
		p[i]=x*quick_pow(y,mod-2)%mod;
	}
	for(int i=0;i<=n;i++)  cin>>dp[1][i];
	for(int i=2;i<=n+1;i++)
		for(int j=0;j<=n-i+1;j++)
			dp[i][j]=(dp[i-1][j+1]*p[i-1]+dp[i-1][max(j-1,0ll)]*(mod+1-p[i-1]))%mod;
	for(int i=1;i<=n;i++)  cout<<dp[i+1][0]<<(i==n?"\n":" ");
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>t;while(t--)  solve();
}

[AGC061C] First Come First Serve

去重之后理论只有两种选择:选 \(a_i\),或者在 \((a_i,b_i)\) 中有数被选择时选 \(b_i\)

考虑容斥,三种情况:选 \(a_i\) 和选 \(b_i\) 系数为 \(1\),选 \(b_i\)\((a_i,b_i)\) 中没数被选择时系数为 \(-1\)

\(dp_i\) 表示选完 \(a/b_{1,2,\cdots,i}\) 的答案,则系数为 \(1\) 的总贡献为 \(2\times dp_{i-1}\),系数 \(-1\) 的贡献双指针维护即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 500005
#define int long long
vector<int> v[N];
const int mod=998244353;
int n,a[N],b[N],l[N],r[N],dp[N];
signed main(){
	scanf("%lld",&n),dp[0]=1;
	for(int i=1;i<=n;i++)  scanf("%lld%lld",a+i,b+i);
	for(int i=1;i<=n;i++){
		l[i]=l[i-1];
		while(l[i]<n&&b[l[i]+1]<a[i])  l[i]++;
	}
	for(int i=1;i<=n;i++){
		r[i]=r[i-1];
		while(r[i]<n&&a[r[i]+1]<b[i])  r[i]++;
	}
	for(int i=1;i<=n;i++)  v[r[i]].push_back(l[i]);
	for(int i=1;i<=n;i++){
		dp[i]=dp[i-1]*2%mod;
		for(auto x:v[i])  dp[i]=(dp[i]-dp[x]+mod)%mod;
	}
	printf("%lld\n",dp[n]);
}

[ABC290Ex] Bow Meow Optimization

首先一定有序列左边的 \(\lfloor\frac{n}{2}\rfloor+\lfloor\frac{m}{2}\rfloor\) 只动物一定有 \(\lfloor\frac{n}{2}\rfloor\) 只狗和 \(\lfloor\frac{m}{2}\rfloor\) 只猫,右边同理。

如果 \(n\)\(m\) 是奇数,就把权值最大的那只放中间。

正确性显然,如果左边多一只猫,将左半边最右边的猫和右半边最左边的狗交换位置一定不劣。

然后题目就变成将 \(n-(n\&1)\) 个红色的球和 \(m-(m\&1)\) 个蓝色的球填进两个长度相同的序列。

若序列中某个球前面有 \(x\) 个与之异色的球,则权值为 \(a_i/b_i\times [2x(+1)]\),是否 \(+1\)\(n\)\(m\) 的奇偶性决定。

然后我们将两种颜色的球混在一起按权值从大到小排序。

因为权值大的一定尽量往前放,所以令 \(f_{i,j,k}\) 表示前 \(i\) 个球在第一个序列放 \(j\) 的红球和 \(k\) 个蓝球,转移容易。

现在时间复杂度为 \(O(n^3)\),已经可以通过,但是仍然可以进行优化。

给出结论:将红色的前 \(\lfloor\frac{n}{2}\rfloor\) 个放在第一个序列,蓝色的前 \(\lfloor\frac{m}{2}\rfloor\) 个放在第二个序列,一定最优。

证明:考虑权值只有 \(0\)\(1\) 的情况,如果红球有个 \(0\) 在左半边,有个 \(1\) 在右半边,交换显然不劣。

对于每个 \(x\),将大于 \(x\) 的数看成 \(1\),小于 \(x\) 的数看成 \(0\)

原序列答案一定大于等于所有 \(x\) 的答案相加,但是对于每个 \(x\) 都有同样的策略,所以等号成立,即结论成立。

然后就可以直接排序后类归并计算结果了,复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 305
#define int long long
int n,m,ans,sua,sub,al,ar,bl,br,a[N],b[N];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>n>>m;
	for(int i=1;i<=n;i++)  cin>>a[i],sua+=a[i];
	for(int i=1;i<=m;i++)  cin>>b[i],sub+=b[i];
	sort(a+1,a+n+1),sort(b+1,b+m+1);
	ans+=(n&1)*sub,n-=(n&1),ans+=(m&1)*sua,m-=(m&1);
	for(int k=1,i=n,j=m;k<=n+m;k++){
		if(i&&a[i]>b[j]){
			if(al<n/2)  ans+=a[i--]*bl*2,al++;
			else  ans+=a[i--]*br*2,ar++;
		}
		else{
			if(br<m/2)  ans+=b[j--]*ar*2,br++;
			else  ans+=b[j--]*al*2,bl++;
		}
	}
	cout<<ans<<"\n";
}
posted @ 2026-01-12 22:14  tkdqmx  阅读(2)  评论(0)    收藏  举报