DP合集

写在前面


在本篇开始前,想先说明写这篇博客的初衷和希望达到的效果。
本文开始写于2023.8.23,博主第一年高中开学前,dp是从初中开始就一直很薄弱的版块,在考试中稍微难一点就做不出来,只能打暴力。但其实正解很好实现,很好得分。
初中时,教练和学长都说dp就是要多做题才能体悟它,初中来不及写,高中就必须直面自己的问题,才能提升自己的能力。所以决定写这样一篇文章,记录自己的dp学习之旅,我承诺每天自己至少独立思考出一道dp
(不知道你们什么时候能看到,等我多写一些再公开,不然有点尴尬)
本博客可能的用法:
1.看一些题的题解和代码
2.每天和博主一起写几道题
3.和博主交流dp的思路

正文


P1944 最长括号匹配

一发就过了,爽!
第一眼是区间dp,再一看范围1e6,很明显dp的复杂度为O(n)O(nlogn).
所以dp先只设一位f[i]表示以i结尾的最长括号匹配。
如果i与i-1的最长括号匹配的前一位可以匹配的,就有f[i]=f[i1]+2+f[if[i1]2](然后我就不会不匹配的情况)
看一眼题解发现题解就是这么转的,它说不匹配则没有结果。
思考一下,发现很对,i-1的最长匹配一应是左右括号数相等,如果i与i-1的最长匹配最长匹配中一个点匹配,则这个点后面的右括号一定多于左括号,不会是一个匹配

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
char s[N];
int f[N],ans,cur;
int main()
{
	scanf("%s",s+1);
	int n=strlen(s+1);
	f[1]=0;
	for(int i=2;i<=n;i++)
	{
		if(s[i]=='('||s[i]=='[')continue;
		if((s[i]==')'&&s[i-f[i-1]-1]=='(')||(s[i]==']'&&s[i-f[i-1]-1]=='['))f[i]=f[i-1]+2+f[i-f[i-1]-2];
		else f[i]=0;
		if(f[i]>ans)ans=f[i],cur=i;
	}
	if(ans==0)return 0;
	for(int i=cur-ans+1;i<=cur;i++)printf("%c",s[i]); 
	return 0;
 } 

P4310 绝世好题

没看题解!
很显然的dp,最朴素的做法是f[i]=maxj<i,i&j0(f[j])+1
复杂度为O(n2),虽然过不去,但给了我们提示。枚举j 是复杂度的瓶颈,考虑快速找j,就要思考什么时候i&j0
可以想到i,j 二进制上有一位相同则不为0.
所以改为枚举二进制,复杂度就可行了。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int dp[N],ans,n,a[N];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	{
		int x=a[i],l=0,mx=0;
		while(x)
		{
			if(x&1)mx=max(dp[l],mx);
			x>>=1;
			l+=1;
		}
		if(mx+1>ans)ans=mx+1;
		x=a[i],l=0;
		while(x)
		{
			if(x&1)dp[l]=mx+1;
			x>>=1;
			l+=1;
		}
	}
	printf("%d",ans); 
	return 0;
 } 

P4059 [Code+#1] 找爸爸

破防了,绿题都不会,要退役了。
很显然,答案的统计与当前匹配的位置和0的位置有关,所以有dp[i][j][0/1/2]表示第一行统计到i,第二行统计到j,结尾没有0/第一行结尾有0/第二行结尾有0。(两位都为0,一定不优)
转移方程

dp[i][j][0]=max(dp[i1][j1][0],dp[i1][j1][1],dp[i1][j1][2])+d(a[i],b[j])

dp[i][j][1]=max(dp[i][j1][0]A,dp[i][j1][1]B,dp[i][j1][2]A)

dp[i][j][2]=max(dp[i][j1][0]A,dp[i][j1][2]B,dp[i][j1][1]A

#include <bits/stdc++.h>
using namespace std;
int a[3010],b[3010],d[5][5],A,B;
char s[3010],t[3010]; 
long long dp[3010][3010][3];
int main()
{
	scanf("%s%s",s,t);
	int len1=strlen(s),len2=strlen(t);
	for(int i=0;i<len1;i++)
	{
		if(s[i]=='A')a[i+1]=1;
		if(s[i]=='T')a[i+1]=2;
		if(s[i]=='G')a[i+1]=3;
		if(s[i]=='C')a[i+1]=4;
	}
	for(int i=0;i<len2;i++)
	{
		if(t[i]=='A')b[i+1]=1;
		if(t[i]=='T')b[i+1]=2;
		if(t[i]=='G')b[i+1]=3;
		if(t[i]=='C')b[i+1]=4;
	}
	for(int i=1;i<=4;i++)
		for(int j=1;j<=4;j++) scanf("%d",&d[i][j]);
	scanf("%d%d",&A,&B); 
	for(int i = max(len1,len2);i; i--) 
	{
    	dp[0][i][0] = dp[i][0][0] = dp[0][i][2] = dp[i][0][1] = -(1LL << 60);
    	dp[0][i][1] = dp[i][0][2] = - A - B * (i - 1);
  	}
  	dp[0][0][1] = dp[0][0][2] = -(1LL << 60);
	for(int i=1;i<=len1;i++)
		for(int j=1;j<=len2;j++)
		{
			dp[i][j][0]=max(max(dp[i-1][j-1][0],dp[i-1][j-1][1]),dp[i-1][j-1][2])+d[a[i]][b[j]];
			dp[i][j][1]=max(dp[i][j-1][0]-A,max(dp[i][j-1][1]-B,dp[i][j-1][2]-A));
			dp[i][j][2]=max(max(dp[i-1][j][0]-A,dp[i-1][j][1]-A),dp[i-1][j][2]-B); 
		}
	printf("%lld\n",max(max(dp[len1][len2][0],dp[len1][len2][1]),dp[len1][len2][2]));
	return 0;
} 

P1868 饥饿的奶牛

又活过来了(果然数据结构)
简单思想
f[]=maxx<(f[x])++1
复杂度是O(n2)
考虑从一个区间的最大值转移,上数据结构,只求1-n的最大值,用树状数组快速算即可。

#include <bits/stdc++.h>
using namespace std;
const int N=3e6+10;
int n,sh[N],dp[N],mx;
struct node
{
    int x,y;
}a[N];
bool cmp(node x,node y)
{
    return x.y<y.y;
}
int lowbit(int x)
{
    return x&(-x);
}
int query(int x)
{
	if(x<0)return 0;//防负数
    int ans=0;
    while(x)
    {
        ans=max(ans,sh[x]);
        x-=lowbit(x);
    }
    return ans;
}
void add(int x,int val)
{
	if(x<0)return ;
    while(x<=mx)
    {
        if(sh[x]<val)
        {
            sh[x]=val;
            x+=lowbit(x);
        }
        else break;
    }
    return ;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y),mx=max(mx,max(a[i].x,a[i].y));
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        dp[a[i].y]=max(dp[a[i].y],query(a[i].x-1)+(a[i].y-a[i].x+1));
        add(a[i].y,dp[a[i].y]);
    }
    printf("%d\n",query(a[n].y)); 
    return 0;
}

P1282 多米诺骨牌

又没想清楚。
首先可以想到,答案一定与第一第二行的和有关,因为范围最大为6000,所以只能记一维,这里因为我们知道一二行的和有关,所以一行就可以知道另一行。
所以,理所当然设出dp[i][j]处理了前i个数,第一行和为j需要的最小旋转次数、
转移就像背包。

dp[i][j]=min(dp[i][j],min(dp[i1][ja[i]],dp[i1][jb[i]]+1));

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int n,dp[N][2*N],sum,ans=0x3f3f3f3f,ans1=0x3f3f3f3f,a[N],b[N];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]),sum+=a[i]+b[i];
	memset(dp,0x3f,sizeof(dp));
	dp[1][a[1]]=0,dp[1][b[1]]=1;
	for(int i=2;i<=n;i++)
	{
		for(int j=sum;j>=min(a[i],b[i]);j--)
		{
			if(j>a[i])dp[i][j]=min(dp[i][j],dp[i-1][j-a[i]]);
			if(j>b[i])dp[i][j]=min(dp[i][j],dp[i-1][j-b[i]]+1);
			if(i==n&&dp[i][j]!=1061109567)
			{
				int c=abs(2*j-sum);
				if(c<ans)
				{
					ans=c;
					ans1=dp[i][j];
				}
				else if(c==ans)ans1=min(dp[i][j],ans1);
			}
		}
	}
	printf("%d\n",ans1); 
	return 0;
}

P1284 三角形牧场
做出来了,还推出了一些无用的性质。
先还在想周长一定是,什么三角形面积最大。
经过无用的证明用海伦公式证明了,等边三角形最大(没有任何用处,但题解区有贪心加模拟退火用这个性质)
发现可以暴算,800*800直接枚举。
dp[i][j]能否拼成一条边为i,一条边为j。
转移方程为dp[i][j]|=dp[i][ja[k]],dp[i][j]|=dp[ia[k]][j]

#include <bits/stdc++.h>
using namespace std;
int n,a[1100],ans=-1,sum;
bool dp[8100][8100];
bool cheak(int x,int y)
{
	int z=sum-x-y;
	if(x+y>z&&x+z>y&&y+z>x)return 1;
	else return 0;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),sum+=a[i];
	dp[0][0]=1;
	for(int k=1;k<=n;k++)
	{
		for(int i=sum;i>=0;i--)
			for(int j=sum;j>=0;j--)
			{
				if(j>=a[k])dp[i][j]|=dp[i][j-a[k]];
				if(i>=a[k])dp[i][j]|=dp[i-a[k]][j];
			}
	} 
	double p=sum/2.0;
	for(int i=1;i<=sum;i++)
		for(int j=1;j<=sum;j++)
		{
			if(!dp[i][j]||!cheak(i,j))continue;
			double s=sqrt(p*(p-i)*(p-j)*(p-(sum-i-j)))*100;
			ans=max((int)s,ans);
		}
	printf("%d\n",ans);
	return 0;
 } 

P4084 [USACO17DEC] Barn Painting G

今天考试在树型dp想出方法后唐了,写一道树型dp奖励自己,结果更唐,虽然对了,单写法一点都不优秀。
首先题目很显然树型dp,dp[u][1/2/3]表示u涂颜色1,颜色2,颜色3的方案数。
然后对于有特定的颜色的点,就除了特定颜色外初始化为0,其他点每个颜色都设为1。
然后转移即可。有一个小问题,如果儿子节点有特定颜色则父节点该颜色为0,我自己是再写一遍循坏判断(不简洁)

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10,mod=1e9+7;
#define int long long
int n,k;
int tot,hd[N],go[N*2],nxt[N*2],col[N],dp[N][4];
void add(int x,int y)
{
	nxt[++tot]=hd[x];go[tot]=y;hd[x]=tot;
	nxt[++tot]=hd[y];go[tot]=x;hd[y]=tot;
	return ;	
}
void dfs(int u,int f)
{
	if(col[u]==-1)dp[u][1]=dp[u][2]=dp[u][3]=1;
	else dp[u][col[u]]=1;
	for(int i=hd[u];i;i=nxt[i])
	{
		int v=go[i];
		if(v==f)continue;
		dfs(v,u);
		if(col[u]==-1) 
		for(int j=1;j<=3;j++)
		{
			int cnt=0;
			for(int k=1;k<=3;k++)
			{
				if(j==k)continue;
				cnt+=dp[v][k];	
			}
			dp[u][j]=(dp[u][j]*cnt)%mod;		
		}
		else
		{
			int cnt=0;
			for(int k=1;k<=3;k++)
				if(k!=col[u])cnt+=dp[v][k];
			dp[u][col[u]]=(dp[u][col[u]]*cnt)%mod;			
		}
	}
	for(int i=hd[u];i;i=nxt[i])
	{
		int v=go[i];
		if(v==f)continue; 
		if(~col[v]) dp[u][col[v]]=0;
	} 
}
signed main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		add(x,y);
		col[i]=-1;
	}
	col[n]=-1;
	for(int i=1;i<=k;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		col[x]=y;
	}
	dfs(1,0);
	if(col[1]==-1) printf("%d\n",(dp[1][1]+dp[1][2]+dp[1][3])%mod);
	else printf("%d\n",dp[1][col[1]]%mod); 
	return 0;
} 

更优秀的写法

#include<bits/stdc++.h>
#define maxn 200005
#define ll long long
using namespace std;
const int TT=1e9+7;
int n,x,y,lnk[maxn],nxt[maxn],son[maxn],tot,m;
ll f[maxn][3];
inline int read(){
	int ret=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-f;ch=getchar();}
	while (ch<='9'&&ch>='0') ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
inline void add(int x,int y){nxt[++tot]=lnk[x];lnk[x]=tot;son[tot]=y;}
inline void Dfs(int x,int fa){
	for (int i=0;i<3;i++){
		if (f[x][i]){for (int j=0;j<i;j++) f[x][j]=0;break;}
		f[x][i]=1;
	}
	for (int i=lnk[x];i;i=nxt[i])
	  if (son[i]!=fa){
	  	Dfs(son[i],x);
	  	f[x][0]=f[x][0]*((f[son[i]][1]+f[son[i]][2])%TT)%TT;
                f[x][1]=f[x][1]*((f[son[i]][0]+f[son[i]][2])%TT)%TT;
                f[x][2]=f[x][2]*((f[son[i]][1]+f[son[i]][0])%TT)%TT;
	  }
}
int main(){
	n=read(),m=read();
	for (int i=1;i<n;i++) x=read(),y=read(),add(x,y),add(y,x);
	for (int i=1;i<=m;i++) x=read(),y=read()-1,f[x][y]=1;
	Dfs(1,0);
	printf("%lld",(f[1][0]+f[1][1]+f[1][2])%TT);
	return 0;
}

“破锣摇滚”乐队 Raucous Rockers

感觉没什么难度。读完题后就有了一个暴力的想法。设dp[i][j][k]存前i首歌用jCD最后一张用了k分钟的最多乐曲数,显然一首歌要么不用,要么新建一张CD,要么接在当前CD上。直接转移即可。

#include <bits/stdc++.h>
using namespace std;
const int N=22;
int n,t,m,a[N],dp[N][N][N],ans;
int main()
{
	scanf("%d%d%d",&n,&t,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			dp[i][j][a[i]]=dp[i-1][j-1][t]+1;//新建
			for(int tt=1;tt<=t;tt++)
			{
				dp[i][j][tt]=max(max(dp[i][j][tt-1],dp[i-1][j][tt]),dp[i][j][tt]);//不要
				if(tt>a[i])dp[i][j][tt]=max(dp[i][j][tt],dp[i-1][j][tt-a[i]]+1);//接在后面
			}	
			if(i==n)ans=max(ans,dp[i][j][t]);
		}
	}
	printf("%d\n",ans);
	return 0;
 } 

P5322 [BJOI2019] 排兵布阵

dp[i][j]表示前i个城堡用j个士兵最大总分,转移不用说简单背包,一开始感觉时间复杂度不对,是O(nm2).
看题解发现,这有一个贪心与dp的结合。显然只比对手的两倍多一个是最优的,不然会浪费,所以确定赢几局来转移,复杂度是O(n2m)

#include <bits/stdc++.h>
using namespace std;
const int N=210,M=20010;
#define int long long 
int s,n,m,ans,gs[N][N],dp[N][M];
signed main()
{
	scanf("%lld%lld%lld",&s,&n,&m);
	for(int i=1;i<=s;i++)
		for(int j=1;j<=n;j++)
			scanf("%lld",&gs[j][i]);
	for(int i=1;i<=n;i++)
		sort(gs[i]+1,gs[i]+s+1);
	for(int i=1;i<=n;i++)
		for(int j=m;j>=1;j--)
		{
			dp[i][j]=dp[i-1][j];
			for(int k=1;k<=s;k++) 
				if(j-gs[i][k]*2-1>=0)dp[i][j]=max(dp[i][j],dp[i-1][j-gs[i][k]*2-1]+i*k);			
		}
	for(int i=1;i<=m;i++)ans=max(dp[n][i],ans);
	printf("%lld\n",ans);
	return 0;
}

P2339 [USACO04OPEN] Turning in Homework G

开学后很久没写了,一放国庆全是蓝紫黑,感觉越来越菜了,啥也不会做,都是听老师讲看题解才会的。
回归正题,贪心与dp的结合。
先有一个简单的贪心对于一个区间,交作业的最优的一种解是先交区间的一个端点。如果先交中间的点,一定要绕回来交前面的作业,不如直接等到时间走一遍。
所以常规定义dp[i][j][0/1]表示交了区间[i][j]最后落在ij的最小时间,发现不好合并(考虑两个点),所以转化一下方式,
定义dp[i][j][0/1]表示除了区间[i][j]都交了最后交在ij的最小时间(应为贪心然我们知道一个大区间如何变成小区间)
然后转移方程就好写了(反正我理解了很久)

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int c,h,b,dp[N][N][2];
struct node
{
	int x,t;
}zy[N];
bool cmp(node x,node y)
{
	if(x.x!=y.x)return x.x<y.x;
	else x.t<y.t; 
}
int main()
{
	scanf("%d%d%d",&c,&h,&b);
	for(int i=1;i<=c;i++)scanf("%d%d",&zy[i].x,&zy[i].t);
	sort(zy+1,zy+c+1,cmp);
	memset(dp,0x3f,sizeof(dp));
	dp[1][c][0]=max(zy[1].x,zy[1].t);
	dp[1][c][1]=max(zy[c].x,zy[c].t);
	for(int k=c-1;k>=1;k--)
	{
		for(int i=1;i+k-1<=c;i++)
		{
			int j=i+k-1;
			dp[i][j][1]=min(dp[i][j][1],max(dp[i][j+1][1]+zy[j+1].x-zy[j].x,zy[j].t));
			dp[i][j][1]=min(dp[i][j][1],max(dp[i-1][j][0]+zy[j].x-zy[i-1].x,zy[j].t));
			dp[i][j][0]=min(dp[i][j][0],max(dp[i][j+1][1]+zy[j+1].x-zy[i].x,zy[i].t));
			dp[i][j][0]=min(dp[i][j][0],max(dp[i-1][j][0]+zy[i].x-zy[i-1].x,zy[i].t));
//dp[i][j+1]只能落在j+1上,不然i就已经交了,同理。其他的要深刻理解贪心
		}
	}
	int ans=1e9+7;
	for(int i=1;i<=c;i++)
	{
		ans=min(ans,min(dp[i][i][0],dp[i][i][1])+(int)abs(zy[i].x-b));
	}
	printf("%d\n",ans);
	return 0;
}

P1912 [NOI2009] 诗人小G

因为是第一篇决策单调性,就单独写了题解戳这里

P4042 [AHOI2014/JSOI2014] 骑士游戏

以为dp不了,因为有环不好处理有后效性,但我们先不管,直接写出方程。

dp[i]=max(k[i],s[i]+jdp[j])

显然dp[i]能从第二项转移当且仅当所有dp[j]<dp[i],且要先算出dp[j],所以反向连边。不然直接魔法攻击。
所以按dp[i],大小排序来更新,有点像最短路,最小的dp[i]一定是固定下来了,用它去更新别的区间。
以魔法攻击为初值,只有正环不影响。

#include <bits/stdc++.h>
using namespace std;
//#define int long long
const int N=2e5+10,M=1e6+10;
#define ll long long
int n,tot,nxt[M],go[M],hd[N],p[N];
ll dp[N],ans[N],s[N],k[N];
bool vis[N];
void add(int x,int y)
{
	nxt[++tot]=hd[x];go[tot]=y;hd[x]=tot;
	return ;
}
struct node
{
	ll id,w;
	bool operator<(const node& x)const{
		return x.w<w; 
	}
};
priority_queue<node> q;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int x,y;cin>>s[i]>>k[i]>>p[i];
		for(int j=1;j<=p[i];j++)cin>>y,add(y,i);
		q.push((node){i,k[i]}); 
		dp[i]=s[i];
	}
	while(!q.empty())
	{
		ll u=q.top().id,w=q.top().w;q.pop();
		if(vis[u])continue;
		vis[u]=1,ans[u]=w;
		for(int i=hd[u];i;i=nxt[i])
		{
			int v=go[i];
			if(vis[v]||dp[v]>k[v])continue;
			dp[v]+=w;p[v]--;
			if(!p[v])q.push((node){v,dp[v]});
		}
	}
	printf("%lld\n",ans[1]);
	return 0;
}

P4749 [CERC2017] Kitchen Knobs

想水一个题解,不知道过没过。(改了无数遍现在过了)
https://www.luogu.com.cn/article/lqk1zw0e

[ABC221G] Jumping sequence

先有一个套路,直接做不了dp的原因是x,y相互影响,所以考虑见它们分开,将(x,y)变成(x-y,x+y),那么如果我们转化后可以凑出x,y,那么对于每一步如论x,y加或减都有对应的操作。
只转化不够,减di不好做,所将两边同时加di,系数变为0,2,就可以做了

#include <bits/stdc++.h>
using namespace std;
const int N=2e3+10;
int n,A,B,d[N],mx,ans[N];
bitset<3600010> dp[N];//没开够
int aabs(int x){
	return x>0?x:-x; 
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>A>>B;
	for(int i=1;i<=n;i++)cin>>d[i],mx+=d[i];
	int t=A;
	A=A-B,B=t+B;
	if (aabs(A)>mx||aabs(B)>mx) return cout<<"No",0;//特判
    if ((A+mx)&1||(B+mx)&1) return cout<<"No",0;
	A=(A+mx)/2,B=(B+mx)/2;
	dp[1][0]=1;
	for(int i=1;i<=n;i++)dp[i+1]=dp[i]|(dp[i]<<d[i]);
	if(dp[n+1][A]==0||dp[n+1][B]==0)
	{
		cout<<"No"<<'\n';
		return 0;
	}
	for(int i=n;i>=1;i--)
	{
		int tmp=0;
		if(!dp[i][A]){A-=d[i],tmp++;}
		if(!dp[i][B]){B-=d[i],tmp+=2;}
		ans[i]=tmp;
	}
	cout<<"Yes"<<'\n';
	for(int i=1;i<=n;i++)
	{
		if(ans[i]==0)cout<<"L";
        if(ans[i]==1)cout<<"D";
        if(ans[i]==2)cout<<"U";
        if(ans[i]==3)cout<<"R";
	}
	return 0;
}

P4870 [BalticOI 2009 Day1] 甲虫

因为露水数会不断减小,所以你不可能越过一个去吃另一个,所以你一定会吃一个区间,同时你的结束条件一定是吃完露水或者是吃不到露水,考虑进行区间dp。设dpi,j,0/1表示吃完 [i,j] 区间的露水最后停在 ij 的收益。但是我们发现不好转移,因为时间对我们的收益有影响。但是时间这维不好统计。
所以考虑换一种方式来思考,收益可以表示为露水数×m 蒸发数,所以我们考虑在转移的同时将时间印象的蒸发数记录下来。设dp[i][j][0/1]表示吃完 [i,j] 区间的露水最后停在 ij 的最下蒸发数。有一个显然的转移。

	dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(len-j+i)*(x[i+1]-x[i]));
	dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(len-j+i)*(x[j]-x[i]));
	dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(len-j+i)*(x[j]-x[i]));
	dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(len-j+i)*(x[j]-x[j-1]));

但是这个转移有一个致命的问题,直接减会出现负数,同时我们又不好判断负数。又发现数据范围支持O(n3) 所以可以加一维枚举。考虑枚举喝到的露水数,最优方案一定会被枚举到同时取到负数一定不优,减少负数的部分一定更优。
所以直接写。

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
#define int long long
int n,m,pos,dp[N][N][2],sum[N],zs;
struct node
{
	int v,x;
}a[N];
bool cmp(node x,node y)
{
	return x.x<y.x;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>pos;
	for(int i=1;i<=n;i++)cin>>a[i].x;
	for(int i=1,y;i<=n;i++)cin>>y,zs+=y;
	for(int i=1;i<=n;i++)cin>>a[i].v;
	a[++n].x=pos;a[n].v=0;
	sort(a+1,a+n+1,cmp);//从小到大排序
	memset(dp,0x3f,sizeof(dp));	
	for(int i=1;i<=n;i++)
	{
		sum[i]=sum[i-1]+a[i].v;
		if(a[i].x==pos&&a[i].v==0)dp[i][i][1]=dp[i][i][0]=0;
	}
	//dp[pos][pos][1]=dp[pos][pos][0]=0;
	for(int siz=2;siz<=n;siz++)//枚举喝到的水滴数 
	{
		for(int i=1;i+siz-1<=n;i++)
		{
			int j=i+siz-1;//区间左右端点
			dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(sum[n]-sum[j]+sum[i])*(a[i+1].x-a[i].x));
			dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(sum[n]-sum[j]+sum[i])*(a[j].x-a[i].x));
			dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(sum[n]-sum[j-1]+sum[i-1])*(a[j].x-a[i].x));
			dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(sum[n]-sum[j-1]+sum[i-1])*(a[j].x-a[j-1].x));
		}	
	} 
	double jg=(zs-min(dp[1][n][1],dp[1][n][0]))*1.0/1000.0;
	cout<<fixed<<setprecision(3)<<jg<<'\n'; 
	return 0;
}

同款练习题:P2466 [SDOI2008] Sue 的小球

P4925 [1007] Scarlet的字符串不可能这么可爱

不是dp题,但是出现在我的题单里了,就写了。写完了不知道放在哪,所以勉强写在这里吧。
对于回文一直我们都只需要看前两位即可,只要与前两位没有冲突就一定没有冲突。所以你就会写dp了,但是发现长度最大为1e18 所以只能思考数学方法,因为只有一个位置有限制,所以考虑它的两边填什么,考虑先填左边有(k1)种可能,右边就有(k2)种,其他位置也有(k2) 种,所以总答案位(k1)(k2)n2,思考一下如果没有左边,发现答案相同。所以直接做即可。

#include <bits/stdc++.h>
using namespace std;
#define int long long
int n,k,p,s,w,ans;
int ksm(int x,int y)
{
	int res=1;
	while(y)
	{
		if(y&1)res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>k>>n>>p;k%=p;
	cin>>s>>w;
	if(n==1)
	{
		if(s)cout<<'1'<<'\n';
		else cout<<k<<'\n';
		return 0;
	}
	if(k==1)
	{
		cout<<'0'<<'\n';
		return 0;
	}
	ans=(k-1)%p*ksm((k-2)%p,n-2)%p; 
	if(s==0)//没有限制 
		ans=ans*k%p;
	cout<<ans<<'\n'; 
	return 0;
 } 

P2585 [ZJOI2006] 三色二叉树

简单题,首先看题后很明显的树形dp,直接设状态为 dpi,0/1/2 表示第 i 个点染成 0/1/2 的最多染为绿色的点数。转移就比较明显,直接判断剩下的儿子节点填什么即可。做法可以先递归建树,然后做,也可以直接建树和dfs一起做。

#include <bits/stdc++.h>
using namespace std;
const int N = 1000000;
char s[N];
int n, sh[N][2], f[N][3], g[N][3], now= 0;
#define ls sh[x][0]
#define rs sh[x][1]
void build()
{
	now++;int x=now;
	if(s[x]=='0')return ;//叶子
	if(s[x]=='1')
	{
		sh[x][0]=x+1;build();
	} 
	if(s[x]=='2')
	{
		sh[x][0]=now+1;build();
		sh[x][1]=now+1,build();
	}
} 
void dfs(int x)
{
	if(ls)dfs(ls);
	if(rs)dfs(rs);
	if(!ls && !rs) f[x][0] = g[x][0] = 1, f[x][1] = f[x][2] = g[x][1] = g[x][2] = 0;//叶子节点单独处理
	f[x][0] = max(f[ls][1] + f[rs][2], f[ls][2] + f[rs][1]) + 1; 
	f[x][1] = max(f[ls][0] + f[rs][2], f[ls][2] + f[rs][0]);
	f[x][2] = max(f[ls][0] + f[rs][1], f[ls][1] + f[rs][0]);
	g[x][0] = min(g[ls][1] + g[rs][2], g[ls][2] + g[rs][1]) + 1;
	g[x][1] = min(g[ls][0] + g[rs][2], g[ls][2] + g[rs][0]);
	g[x][2] = min(g[ls][0] + g[rs][1], g[ls][1] + g[rs][0]);//g是最小值
//不用担心只有一个儿子的情况,用零不影响答案
}
int main()
{
	cin>>s+1;
	build();//建树 
	dfs(1);
	cout<<max(f[1][0],max(f[1][1],f[1][2]))<<' '<<min(g[1][0],min(g[1][1],g[1][2]))<<'\n';
	return 0;
}

P3174 [HAOI2009] 毛毛虫

简单题,我甚至不确定是否可以算是 dp 题,感觉从贪心出发也行。
就是考虑你肯定只能在每个点考虑将两个子树上最长和次长的链合起来试图更新答案。所以我们要存到每个点的最长链即可(有一些细节,需要自己理解)

#include <bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int mx[N],n,m,ans;
vector<int> ve[N];
void add(int x,int y)
{
	ve[x].push_back(y);
	ve[y].push_back(x); 
	return ;
}
void dfs(int u,int fa)
{
	int len=ve[u].size();
	//if(fa==0)len=max(len-1,0);
	for(int i=0;i<len;i++)
	{
		int v=ve[u][i];
		if(v==fa)continue;
		dfs(v,u);
		ans=max(ans,mx[u]+mx[v]+len-3); 
		mx[u]=max(mx[v],mx[u]);
	}
	if(len==1)mx[u]=2;
	mx[u]+=len-1;
	ans=max(ans,mx[u]);
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	if(n==1)//特殊情况
	{
		cout<<"1"<<'\n';
		return 0;
	}
	for(int i=1;i<=m;i++)
	{
		int a,b;cin>>a>>b;
		add(a,b);
	}
	dfs(1,0);
	cout<<ans<<'\n';
	return 0;
}
posted @   storms11  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示