dp 专练

A. CF1810G

发现脑子不太够用,考虑先解决小情况。

\(dp_{i,j}\) 表示从后往前枚举到第 \(i\) 个位置,且后缀中前缀和最大为 \(j\),那么期望是多少,转移为:

\[p_i\times dp_{i,j}\to dp_{i-1,j+1} \]

\[(1-p_i)\times dp_{i,j}\to dp_{i-1,\max(j-1,0)} \]

由于是倒着枚举的,所以只能先枚举 \(k\),再进行 \(O(n^2)\)\(dp\),过于浪费。考虑反转 \(dp\)

有趣的是,\(dp\) 不仅可以记录历史信息,还可以用以规划未来。我们将 \(f_{i,j}\) 设为从前往后枚举,\(i\) 以后的位置钦定前缀和最大值为 \(j\) 的情况,转移方程为:

\[f_{i,j}=p_i\times f_{i-1,j+1}+(1-p_i)\times f_{i-1,\max(j-1,0)} \]

为了方便计算答案,我们设 \(dp_{0,i}=h_i\),则有 \(ans_k=dp_{k,0}\)

时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
const int N=5005,p=1e9+7;
int t,n,pc[N],f[N][N];
inline int qpow(int x,int y){
	int re=1;
	while(y){
		if(y&1) re=1ll*re*x%p;
		x=1ll*x*x%p,y>>=1;
	}
	return re;
}
inline void solve(){
	cin>>n;
	for(int i=1,x,y;i<=n;i++)
		cin>>x>>y,pc[i]=1ll*x*qpow(y,p-2)%p;
	for(int i=0;i<=n+1;i++)
		for(int j=0;j<=n+1;j++) f[i][j]=0;
	for(int i=0;i<=n;i++) cin>>f[0][i];
	for(int i=1;i<=n;i++){
		for(int j=0;j<=n;j++)
			f[i][j]=(1ll*pc[i]*f[i-1][j+1]+(p-pc[i]+1ll)*f[i-1][max(j-1,0)])%p;
		cout<<f[i][0]<<" ";
	}
	cout<<"\n";
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>t;
	while(t--) solve();
	return 0;
}

B. AGC061C

考虑不合法情况就是对于一个 \(i\),我们选择了 \(b_i\),且 \((a_i,b_i)\) 中没有选一个。我们考虑容斥解决。

\(f_i\) 表示枚举了前 \(i\) 个数该怎么放。假如不考虑限制,转移显然为 \(f_i=2f_{i-1}\)。考虑要减去什么。

对于一个 \(i\),假如它不合法,那么就会导致所有与它有交的区间选择是确定的。所以找到 \(p=\operatorname{arcmax}_{j=1}^n [b_j<a_i]\times j\)\(q=\operatorname{arcmax}_{j=1}^n[a_j<b_i]\times j\),则 \(dp_q\) 会减去 \(dp_p\),双指针即可。

时间复杂度 \(O(n)\)

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,p=998244353;
int n,a[N],b[N],f[N];
vector<int>g[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i]>>b[i];
	f[0]=1;
	for(int i=1,l=0,r=0;i<=n;i++){
		while(l<n&&b[l+1]<a[i]) l++;
		while(r<n&&a[r+1]<b[i]) r++;
		g[r].push_back(l);
		f[i]=f[i-1]*2%p;
		for(int j:g[i])
			f[i]=(f[i]-f[j]+p)%p;
	}
	cout<<f[n];
	return 0;
}

E. ABC290Ex

猫狗大舞台,有命你就来。

赌博题。先赌猫狗分别先升后降,再赌猫狗左右各一半,最后再赌猫大的一半放左,狗大的一半放右一定最优。剩下的就是简单模拟了。

时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=305;
int n,m,a[N],b[N],numl,numr,ans;
struct num{
	int x,op;
}anm[N<<1];
inline int cmp(num x,num y){
	return x.x<y.x;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=m;i++) cin>>b[i];
	sort(a+1,a+n+1),sort(b+1,b+m+1);
	if(n&1){
		for(int i=1;i<=m;i++) ans+=b[i];
		ans+=(m&1)*a[n--];
	}
	if(m&1){
		for(int i=1;i<=n;i++) ans+=a[i];
		m--;
	}
	for(int i=1;i<=n;i++) anm[i]={a[i],0};
	for(int i=1;i<=m;i++) anm[i+n]={b[i],1};
	sort(anm+1,anm+n+m+1,cmp);
	for(int i=n+m,j=0,k=0;i;i--){
		if(!anm[i].op){
			if(numl<n/2)
				ans+=(2*max(m/2,k)-m)*anm[i].x,numl++;
			else ans+=2*min(m/2,k)*anm[i].x;
			j++;
		}
		else{
			if(numr<m/2)
				ans+=(2*max(n/2,j)-n)*anm[i].x,numr++;
			else ans+=2*min(n/2,j)*anm[i].x;
			k++;
		}
	}
	cout<<ans;
	return 0;
}
posted @ 2025-12-20 15:17  white_tiger  阅读(2)  评论(0)    收藏  举报