动态规划笔记

连续段DP

P5599 [CEOI 2016] kangaroo

#include<bits/stdc++.h>
#define int long long
#define mod 1000000007
#define N 2005
using namespace std;
int dp[N][N];
signed main(){
	int n,s,t;
	scanf("%lld%lld%lld",&n,&s,&t);
	dp[1][1]=1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i==s||i==t)dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod;
			else dp[i][j]=(dp[i-1][j-1]*(j-(int)(i>s)-(int)(i>t))+dp[i-1][j+1]*j)%mod;
		}
	}
	printf("%lld\n",dp[n][1]);
	return 0;
}

将问题转化为一个排列,然后从小往大插入数,状态为当前形成j个连续段的方案数,根据题目要求进行转移

四边形不等式优化

P10861 [HBCPC2024] MACARON Likes Happy Endings

对于solve函数维护一个求解区间和一个决策区间,对求解区间进行分治,然后遍历决策区间求出mid的决策点继续分治

#include<bits/stdc++.h>
#define N 100005
#define M (1<<20)+10
using namespace std;
int n,m,d,x[N],L=1,R=0,cntl[M],cntr[M];
long long now=0,dp[N][25];
void deL(int u){
	now-=cntr[x[u-1]^d];
	cntl[x[u-1]]--;
	cntr[x[u]]--;
	return;
}
void adL(int u){
	cntr[x[u]]++;
	now+=cntr[x[u-1]^d];
	cntl[x[u-1]]++;
	return;
}
void deR(int u){
	now-=cntl[x[u]^d];
	cntl[x[u-1]]--;
	cntr[x[u]]--;
	return;
}
void adR(int u){
	cntl[x[u-1]]++;
	now+=cntl[x[u]^d];
	cntr[x[u]]++;
	return;
}
long long query(int l,int r){
	while(L<l)deL(L++);
	while(L>l)adL(--L);
	while(R>r)deR(R--);
	while(R<r)adR(++R);
	return now;
}
void solve(int l,int r,int fl,int fr,int id){
	if(l>r||fl>fr)return;
	int mid=(l+r)/2,maxn=0;
	dp[mid][id]=1e12;
	for(int i=fl;i<=fr;i++){
		long long v=dp[i-1][id-1]+query(i,mid);
		if(v<dp[mid][id]){
			dp[mid][id]=v;
			maxn=i;
		}
	}
	solve(l,mid-1,fl,maxn,id);
	solve(mid+1,r,maxn,fr,id);
	return;
}
signed main(){
	scanf("%d%d%d",&n,&m,&d);
	for(int i=1;i<=n;i++)scanf("%d",&x[i]);
	for(int i=1;i<=n;i++)x[i]=(x[i-1]^x[i]);
	for(int i=1;i<=n;i++)dp[i][0]=1e12;
	for(int i=1;i<=m;i++)solve(1,n,1,n,i);
	long long ans=1e12;
	for(int i=1;i<=m;i++)ans=min(ans,dp[n][i]);
	printf("%lld\n",ans);
	return 0;
}

概率DP

P3232 [HNOI2013] 游走

在没有明显顺序时,通过处理出各个点期望之间的关系式,通过高斯消元跑出答案。

#include<bits/stdc++.h>
#define N 505
#define M 125005
using namespace std;
struct Ty{
	int u,v;
	double w;
	bool operator <(const Ty &a)const{return w>a.w;}
}w[M];
vector<int>y[N];
double x[N][N],dis[N],deg[N];
signed main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&w[i].u,&w[i].v);
		y[w[i].u].push_back(w[i].v);
		y[w[i].v].push_back(w[i].u);
		deg[w[i].u]+=1.0;
		deg[w[i].v]+=1.0;
	}
	for(int i=1;i<n;i++){
		for(int j=0;j<y[i].size();j++)x[i][y[i][j]]+=1.0/deg[y[i][j]];
		x[i][n]=0.0;
		x[i][i]=-1.0;
		if(i==1)x[i][n]-=1.0;
	}
	for(int i=1;i<n;i++){
		int r=i;
		for(int j=i;j<n;j++)if(fabs(x[j][i])>fabs(x[r][i]))r=j;
		if(r!=i)swap(x[i],x[r]);
		double div=x[i][i];
		for(int j=i;j<=n;j++)x[i][j]/=div;
		for(int j=i+1;j<n;j++){
			div=x[j][i];
			for(int k=i;k<=n;k++)x[j][k]-=div*x[i][k];
		}
	}
	for(int i=n-1;i>=1;i--){
		dis[i]=x[i][n];
		for(int j=i+1;j<n;j++)dis[i]-=dis[j]*x[i][j];
	}
	for(int i=1;i<=m;i++)w[i].w=dis[w[i].u]/deg[w[i].u]+dis[w[i].v]/deg[w[i].v];
	sort(w+1,w+m+1);
	double ans=0.0;
	for(int i=1;i<=m;i++)ans=ans+i*1.0*w[i].w;
	printf("%.3lf\n",ans);
	return 0;
}

斜率优化DP

P5574 [CmdOI2019] 任务分配问题

将决策点转化为和j有关的点,维护凸壳,然后使用二分或者单调队列找到最优点,并计算截距,从而得到答案

#include<bits/stdc++.h>
#define N 25005
using namespace std;
int y[N],fl=1,fr=0,n,m,x[N];
long long dp[N][30],ans=0;
inline int lowbit(int u){return u&-u;}
void update(int u,int val){
	for(int i=u;i<=n;i+=lowbit(i))y[i]+=val;
	return;
}
int query(int u){
	int now=0;
	for(int i=u;i;i-=lowbit(i))now+=y[i];
	return now;
}
void del(int u){
	update(x[u],-1);
	ans-=fr-fl+1-query(x[u]);
	return;
}
void adl(int u){
	update(x[u],1);
	ans+=fr-fl+1-query(x[u]);
	return;
}
void der(int u){
	update(x[u],-1);
	ans-=query(x[u]);
	return;
}
void adr(int u){
	ans+=query(x[u]);
	update(x[u],1);
	return;
}
long long query(int l,int r){
	while(fl<l)del(fl++);
	while(fl>l)adl(--fl);
	while(fr<r)adr(++fr);
	while(fr>r)der(fr--);
	return ans;
}
void solve(int l,int r,int L,int R,int id){
	if(r<l||R<L)return;
	int mid=(l+r)/2;
	dp[mid][id]=2e10;
	int minn=0;
	for(int i=L;i<=min(mid-1,R);i++){
		long long v=dp[i][id-1]+query(i+1,mid);
		if(v<dp[mid][id]){
			dp[mid][id]=v;
			minn=i;
		}
	}
	solve(l,mid-1,L,minn,id);
	solve(mid+1,r,minn,R,id);
	return;
}
signed main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&x[i]);
	for(int i=1;i<=n;i++){
		adr(++fr);
		dp[i][1]=ans;
	}
	for(int i=2;i<=m;i++)solve(1,n,1,n,i);
	long long now=2e10;
	for(int i=1;i<=m;i++)now=min(now,dp[n][i]);
	printf("%lld\n",now);
	return 0;
}

树形DP

XXYX Binary Tree

题目中的A不用管,记Y点为白点,然后题目可以转化为白点不相邻,然后除了根节点加了b个白点,然后总共有c/2个非叶子节点

考虑设计状态为 \(dp_{i,j,k}\) 表示以 \(i\) 为根的子树,选了 \(c/2\) 个非叶子白点,\(k\) 表示 \(i\) 的状态。

那么如果限制 \(j\le siz_i\),那么总转移复杂度就是 \(O(n^2)\) 的,可以直接做,但是注意对于边界情况 \(i\) 为叶子节点时,\(dp_{i,1,0}=dp_{i,1,1}=-inf\) 即不存在情况。

#include<bits/stdc++.h>
#define N 10005
using namespace std;
int dp[N][N][2],siz[N],fa[N],ls[N],rs[N],n;
void dfs(int u){
	siz[u]=1;
	if(!ls[u]){
		dp[u][0][1]=1;
		dp[u][0][0]=0;
		return;
	}
	dfs(ls[u]);
	siz[u]+=siz[ls[u]];
	dfs(rs[u]);
	siz[u]+=siz[rs[u]];
	for(int i=0;i<=siz[rs[u]];i++){
		for(int j=0;j<=siz[ls[u]];j++){
			dp[u][i+j][0]=max(dp[u][i+j][0],max(dp[ls[u]][j][0],dp[ls[u]][j][1])+max(dp[rs[u]][i][0],dp[rs[u]][i][1]));
			dp[u][i+j+1][1]=max(dp[u][i+j+1][1],dp[ls[u]][j][0]+dp[rs[u]][i][0]);
		}
	}
	return;
}
signed main(){
	int T;
	scanf("%d",&T);
	while(T--){
		int a,b,c;
		scanf("%d%d%d%d",&n,&a,&b,&c);
		for(int i=1;i<=n;i++)for(int j=0;j<=n;j++)dp[i][j][0]=dp[i][j][1]=-1e8;
		for(int i=1;i<=n;i++)ls[i]=rs[i]=siz[i]=fa[i]=0;
		for(int i=2;i<=n;i++){
			scanf("%d",&fa[i]);
			if(!ls[fa[i]])ls[fa[i]]=i;
			else rs[fa[i]]=i;
		}
		if(c%2||b-c/2+1<0){
			printf("No\n");
			continue;
		}
		dfs(1);
		if(dp[1][c/2][0]>=b-c/2&&b-c/2>=0||dp[1][c/2][1]>=b-c/2+1)printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}
posted @ 2025-08-18 20:49  Igunareo  阅读(12)  评论(0)    收藏  举报