动态规划,且学且放弃

版权声明: https://blog.csdn.net/qq_40828060/article/details/83064425


最近准备把dp完完整整的复习一遍,开博记录

前言

记忆化搜索

记忆化搜索的定义

· 不依赖任何形式的外部变量

· 答案以返回值而非参数形式存在

· 对于相同参数返回值相同

与动态规划的关系

递归实现转移,因此是反向的

如何写记忆化搜索
  • 方法一

把这道题的dp状态和方程写出来

根据他们写出dfs函数

添加记忆化数组

  • 方法二

写出这道题的暴搜程序(最好是dfs)

将这个dfs改成"无需外部变量"的dfs

添加记忆化数组

动态规划的基本解题思路

四个步骤
确定子问题
定义状态
转移方程
统计答案/避免重复求解

具体过程详见下文 P2758 编辑距离

背包问题

01背包

01背包的空间优化问题

可以空间优化的根本原因:
第i个状态仅能转移到i-1个
即当一层状态更新完毕,就不会影响其余的状态
如果正向枚举
不满足此性质

P1048 采药

记忆化搜索

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=5010,maxm=5010,inf=0x1f1f1f1f;
int n,t,val[maxn],cost[maxn],ans[maxn][maxn];
int dfs(int vleft,int step)
{
	if(ans[step][vleft]!=-1)
		return ans[step][vleft];
	if(step>n)
		return ans[step][vleft]=0;
	int nput=-inf,put=-inf;
	nput=dfs(vleft,step+1);
	if(cost[step]<=vleft)
		put=dfs(vleft-cost[step],step+1)+val[step];
	return ans[step][vleft]=std::max(nput,put);
}
int main()
{
	memset(ans,-1,sizeof(ans));
	scanf("%d%d",&t,&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&cost[i],&val[i]);
	printf("%d",dfs(t,1));
	return 0;
}

递推

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=50100,maxm=50100,inf=0x1f1f1f1f;
int n,t,val[maxn],cost[maxn],ans[maxn];
int main()
{
	scanf("%d%d",&t,&n);
	for(register int i=1;i<=n;i++)
		scanf("%d%d",&cost[i],&val[i]);
	for(register int i=1;i<=n;i++)
		for(register int j=t;j>=cost[i];j--)
			ans[j]=std::max(ans[j],ans[j-cost[i]]+val[i]);
	printf("%d",ans[t]);
	return 0;
}

P1510 精卫填海

一开始读错了题,以为要刚好填满,就想把体积作为v,把问题转化成可行性背包
事实上该题可以把体积作为w,每当f[j]>W时,统计一下答案

#include<iostream>
#include<cstdio>
int const maxn=10101,maxm=10101,inf=0x1f1f1f1f;
int W,n,V,cv[maxn],w[maxn],f[maxn],ans;
int main()
{
	ans=-inf;
	scanf("%d%d%d",&W,&n,&V);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&w[i],&cv[i]);
	for(int i=1;i<=n;i++)
		for(int j=V;j>=cv[i];j--)
		{
			f[j]=std::max(f[j],f[j-cv[i]]+w[i]);
			if(f[j]>=W)
				ans=std::max(ans,V-j);
		}
	if(ans<0)
	{
		puts("Impossible");
		return 0;
	}
	printf("%d",ans);
	return 0;
}

P1566 加等式

模型竟然是可行性01背包求解方案数问题,完全没看出来…
题意其实是取某些数使他们的和等于集合内的某个数,每个数只能取一次,问有多少种方案,这样就很明显了
把集合内最大的数作为上限,背包必须填满
跑完背包只要把每个数对应的背包加起来即可
注意!要把自己相等的方案减去

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int t,n,f[1110],a[maxn],ans,mx;
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		memset(f,0,sizeof(f));
		ans=0;
		mx=-1;
		scanf("%d",&n);
		f[0]=1;
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]),mx=std::max(mx,a[i]);
		for(int i=1;i<=n;i++)
			for(int j=mx;j>=a[i];j--)
				f[j]+=f[j-a[i]];
		for(int i=1;i<=n;i++)
			ans+=f[a[i]];
		printf("%d\n",ans-n);
	}
	return 0;
}

P1504 积木城堡

还算有意思的可行性01背包
然而这种水题我竟然没看出来
对于每一堆积木,都求一下可行性,用桶统计一下每个高度的可行性
当某个高度的的可行性达到n种,说明n堆积木都能凑出这个高度,作为一个可行解
从高到低枚举保证答案最优
没有可行解要特判

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1110,maxm=100110,inf=0x1f1f1f1f;
int f[maxm],cv[maxn];
int n,sum,mn=inf,cnt,ton[maxm];
int min(int x,int y,int z)
{
    return std::min(std::min(x,y),z);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		memset(f,0,sizeof(f));
		f[0]=1;
		cnt=0,sum=0;
		for(int x;;)
		{
			scanf("%d",&x);
			if(x==-1)
				break;
			cv[++cnt]=x;
			sum+=x;			
		}
		mn=std::min(mn,sum);
		for(int j=1;j<=cnt;j++)
			for(int k=sum;k>=cv[j];k--)
				f[k]=std::max(f[k],f[k-cv[j]]);
		for(int k=1;k<=sum;k++)
			ton[k]+=f[k];
	}
	for(int k=mn;k>=0;k--)
		if(ton[k]==n)
		{
			printf("%d",k);
			return 0;
		}
	printf("0");
	return 0;
}

完全背包

P1474 货币系统

完全背包统计可行性方案数问题
其实不是特别明白…
感性理解一下就是,选择某个面值,就能获得组成当前面值的方案数
因为选择某个数,方案数是不会改变的

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef long long ll;
int const maxn=10110,maxm=10110,inf=0x1f1f1f1f;
int n,T;
ll f[maxn],cv[maxn];
int main()
{
	scanf("%d%d",&n,&T);
	for(int i=1;i<=n;i++)
		scanf("%lld",cv+i);
	f[0]=1;
	for(int i=1;i<=n;i++)
		for(int j=cv[i];j<=T;j++)
			f[j]+=f[j-cv[i]];
	printf("%lld",f[T]);
	return 0;
}

P2904 River Crossing

比较显然的背包问题
把奶牛个数看成容积,耗时看做价值
问题转化为了可行性最小背包问题
由于同一奶牛个数可以重复选,比如可以每次只带一只奶牛所以是完全背包

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=2511,maxm=2511;
int f[maxn],pre,n,m;
int main()
{
	memset(f,0x1f,sizeof(f));
	scanf("%d%d",&n,&m);
	f[0]=-m,pre=m;
	for(int w,i=1;i<=n;i++)
	{
		scanf("%d",&w),pre+=w;
		for(int j=i;j<=n;j++)
			f[j]=std::min(f[j],f[j-i]+pre+m);
	}
	printf("%d",f[n]);
	return 0;
}

P2725 Stamps

一开始没看到从1开始…
而且价值跟容积完全搞反了…
可行性完全背包
f[i]表示装到价值为i最少需要多少邮票
当f[j-w]>=sum时不能转移

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=54,maxm=2001000,inf=0x1f1f1f1f;
int f[maxm];
int n,sum;
int main()
{
	memset(f,0x1f,sizeof(f));
	f[0]=0;
	scanf("%d%d",&sum,&n);
	for(int w,i=1;i<=n;i++)
	{
		scanf("%d",&w);
		for(int j=w;j<=maxm;j++)
		{
			if(f[j-w]>=sum)
				continue;
			f[j]=std::min(f[j],f[j-w]+1);
		}
	}
	for(int i=1;i<=maxm;i++)
		if(f[i]==inf)
		{
			printf("%d",i-1);
			return 0;
		}
}

分组背包

P2409 Y的积木

分组背包的可行性方案数问题
f[i][j]表示前i种的和为j的方案数
对于每一种,都跑一遍01背包
因为每个种都从上一种转移,所以每一种一定只能选一个

#include<iostream>
#include<cstdio>
#include<cmath>
int const maxn=10011,maxm=111,inf=0x1f1f1f1f;
int f[maxm][maxn],n,K,cv[maxm][maxm],maxx[maxm],minn[maxm],mx,mn;
int main()
{
	scanf("%d%d",&n,&K);
	for(int i=1;i<=n;i++)
	{
		maxx[i]=-1;
		scanf("%d",&cv[i][0]);
		for(int k=1;k<=cv[i][0];k++)
		{
			scanf("%d",&cv[i][k]);
			maxx[i]=std::max(maxx[i],cv[i][k]);
		}
		mx+=maxx[i];
	}
	f[0][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=cv[i][0];j++)
			for(int k=mx;k>=cv[i][j];k--)
				f[i][k]+=f[i-1][k-cv[i][j]];	
	for(int k=1;k<=mx&&K;k++)
	{
		while(f[n][k]&&K)
		{
			f[n][k]--,K--;
			printf("%d ",k);
		}
	}
	return 0;
}

二维费用背包

相当于有两个限制条件的背包
基本转移如下

for(int i=1;i<=n;i++)
	for(int j=下限1;j<=限制1;j++)
		for(int k=下限2;k<=限制2;k++)
			f[j][k]=std::max(f[j][k],f[j-cv1[i]][k-cv2[i]);

P1759 通天之潜水

裸的二维费用背包+输出路径

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=111,maxm=210,inf=0x1f1f1f1f;
int f[maxm][maxm],cv1[maxm],cv2[maxn],w[maxn],path[maxm][maxm][maxn];
int n,V1,V2;
int main()
{
    scanf("%d%d%d",&V1,&V2,&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d%d",&cv1[i],&cv2[i],&w[i]);
    for(int i=1;i<=n;i++)
        for(int j=V1;j>=cv1[i];j--)
            for(int k=V2;k>=cv2[i];k--)
            {
                int lst1=j-cv1[i],lst2=k-cv2[i];
                if(f[j][k]<f[lst1][lst2]+w[i])
                {
                    f[j][k]=f[lst1][lst2]+w[i];
                    path[j][k][0]=path[lst1][lst2][0]+1;
                    path[j][k][path[j][k][0]]=i;
                    for(int l=1;l<=path[lst1][lst2][0];l++)
                        path[j][k][l]=path[lst1][lst2][l];
                }
            }
    printf("%d\n",f[V1][V2]);
    for(int i=1;i<=path[V1][V2][0];i++)
        printf("%d ",path[V1][V2][i]);
    return 0;
}

P1586 四方定理

二维费用方案数背包
一开始试图用Y的积木的思路写,给他分成四组
结果这道题要求不超过四个,所以不能这么做
虽然这两者很像,但还是略有不同
分组背包的状态表示的是考虑完i组限制j能得到的答案
二维费用背包表示考虑完限制i后限制j能得得到的答案
(根据这道题的特殊性,我们需要把每个限制i得到的答案加起来)

#include<cstdio>
#include<iostream>
using namespace std;
int f[5][32770],n,t;
int main()
{
	f[0][0]=1,n=32768;
	for(int i=1;i*i<=n;i++)
		for(int j=i*i;j<=n;j++)
			for(int k=1;k<=4;k++)
				f[k][j]+=f[k-1][j-i*i];
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		int ans=0;
		for(int k=1;k<=4;k++)
			ans+=f[k][n];
		printf("%d\n",ans);
	}
	return 0;
}

混合背包

把上述所有背包给合起来即可

P2623 物品选取

分组背包+多重背包+完全背包

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=2112,inf=0x1f1f1f1f;
int n,V,f[maxn];
int min(int x,int y,int z)
{
    return std::min(std::min(x,y),z);
}
void Pro(int a,int b)
{
	for(int j=V;j>=0;j--)
		for(int v=0;v<=j;v++)
		{
			int w=a*v*v-b*v;
			f[j]=std::max(f[j],f[j-v]+w);
		}	
}
void Pzo(int v,int w)
{
	for(int j=V;j>=v;j--)
		f[j]=std::max(f[j],f[j-v]+w);
}
void Pcp(int v,int w)
{
	for(int j=v;j<=V;j++)
		f[j]=std::max(f[j],f[j-v]+w);
}
void Pmu(int v,int w,int nm)
{
	if(v*nm>=V)
	{
		Pcp(v,w);
		return;
	}
	int k=1;
	while(k<=nm)
	{
		Pzo(v*k,w*k);
		nm-=k;
		k*=2;
	}
	Pzo(v*nm,w*nm);
}
int main()
{
	scanf("%d%d",&n,&V);
	for(int op,w,cv,num,i=1;i<=n;i++)
	{
		scanf("%d%d%d",&op,&w,&cv);
		if(op==1)
			Pro(w,cv);
		else if(op==2)
		{
			scanf("%d",&num);
			Pmu(cv,w,num);
		}
		else
			Pcp(cv,w);
	}
	printf("%d",f[V]);
	return 0;
}

坐标DP

P1434 滑雪

  • 记搜写法
    简单好用
 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=111,maxm=111,inf=0x1f1f1f1f;
int const dx[4]={0,0,1,-1};
int const dy[4]={1,-1,0,0};
int map[maxn][maxn],f[maxn][maxn],ans,n,m;
int cmp(int x,int y)
{
	return map[x]<map[y];
}
int dfs(int x,int y)
{
	if(x>n||x<1||y>m||y<1)
		return 0;
	if(f[x][y])
		return f[x][y];
	int nans=1;
	for(int p=0;p<4;p++)
	{
		int nx=x+dx[p],ny=y+dy[p];
		if(map[x][y]>map[nx][ny])
			nans=std::max(nans,dfs(nx,ny)+1);
	}
	return f[x][y]=nans;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&map[i][j]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			ans=std::max(ans,dfs(i,j));
	printf("%d",ans);
	return 0;
}
  • 递推写法
    转移与记搜刚好相反,从低向高转移,只要排一下序就能保证某一状态仅对临近状态有贡献,从而保证无后效性
    再考虑状态,最朴素的思路是二维数组,然而不太好排序
    我们考虑把二维转成一维
    把每一行的开头接在上一行的末尾
    这样就会得到一个这样的表格
i 1 2 3 4 5 6 7 8 9 10 11 12
ai 1 2 3 4 5 16 17 18 19 6 15 24

我们发现一个数x的上下左右有这样的关系,于是就可以转移了

x-m-1 x-m x-m+1
x-1 x x+1
x+m-1 x+m x+m+1
注意边界!
当且仅当x-m≤0时,x位于最上一行;
当且仅当x+m>n*m时,x位于最下一行;
当且仅当x mod m=0时,x位于最右一行;
当且仅当(X-1) mod m =0时,x位于最左一行。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=10110,maxm=10110,inf=0x1f1f1f1f;
int const dx[4]={0,0,1,-1};
int const dy[4]={1,-1,0,0};
int map[maxn],a[maxn],ans,f[maxn];
int cmp(int x,int y)
{
    return map[x]<map[y];
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    int len=n*m;
    for(int i=1;i<=len;i++)
        scanf("%d",&map[i]),a[i]=i;
//	for(int i=1;i<=len;i++)
//		printf("!!!%d %d\n",i,map[i]);
    std::sort(a+1,a+1+len,cmp);
    for(int i=1;i<=len;i++)
    {
//		printf("%d\n",a[i]);
        int x=a[i];
        f[x]=1;
        if(x-m>0 && map[x]>map[x-m])
            f[x]=std::max(f[x],f[x-m]+1);
        if((x-1)%m && map[x]>map[x-1])
            f[x]=std::max(f[x],f[x-1]+1);
        if(x+m<=len && map[x]>map[x+m])
            f[x]=std::max(f[x],f[x+m]+1);
        if(x%m && map[x]>map[x+1])
            f[x]=std::max(f[x],f[x+1]+1);
        ans=std::max(ans,f[x]);
    }
    printf("%d",ans);
    return 0;
}

P1004 方格取数

  • O(n^4)做法
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=51,maxm=51,inf=0x1f1f1f1f;
int const dx[4]={0,0,-1,-1};
int const dy[4]={-1,-1,0,0};
int const dxf[4]={-1,0,-1,0};
int const dyf[4]={0,-1,0,-1};
int n,m,map[maxn][maxn],f[maxn][maxn][maxn][maxn];
int main()
{
	scanf("%d",&n);
	for(int x,y,z;;)
	{
		scanf("%d%d%d",&x,&y,&z);
		if(!x&&!y&&!z)
			break;
		map[x][y]=z;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			for(int k=1;k<=n;k++)
			{
				int l=i+j-k;
				if(l<=0)
					continue;
				for(int p=0;p<4;p++)
					f[i][j][k][l]=std::max(f[i][j][k][l],f[i+dx[p]][j+dy[p]][k+dxf[p]][l+dyf[p]]);
				f[i][j][k][l]+=(map[i][j]+map[k][l]);
				if(i==k&&j==l)
					f[i][j][k][l]-=map[i][j];
			}
	printf("%d",f[n][n][n][n]);
	return 0;
}

Codevs 2853 方格游戏

空间控制在n^3就已经够了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=111,maxm=111,inf=0x1f1f1f1f;
int const dx[4]={0,0,-1,-1};
int const dy[4]={-1,-1,0,0};
int const dxf[4]={-1,0,-1,0};
int const dyf[4]={0,-1,0,-1};
int map[maxn][maxn],f[321][maxn][maxn];
int abs(int x)
{
	if(x<0)
		return -x;
	return x;
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&map[i][j]);
	for(int k=1;k<(n<<1);k++)
		for(int i=1;i<=std::min(n,k);i++)
			for(int j=1;j<=std::min(n,k);j++)
			{
				for(int p=0;p<4;p++)
				{
					if(i+dx[p]<1 || !(k-i+dy[p]) || j+dxf[p]<1 || !(k-i+dyf[p]))
						continue;
					f[k][i][j]=std::max(f[k][i][j],f[k-1][i+dx[p]][j+dxf[p]]);
				}
				f[k][i][j]+=abs(map[i][k-i+1]-map[j][k-j+1]);
			}
	printf("%d\n",f[2*n-1][n][n]);
	return 0;
}

P1508 likecloud

其实这题根本就是那道时代的眼泪,那道在ioi上横空出世的一道神题
但还是写出锅来了…
边界可以直接memset
必须从第一行开始搜,因为需要给他们赋上点权
从上往下搜比较暴力,从下往上搜写崩了,等等再调吧…

  • 从上往下
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int n,m,f[maxn][maxn],ans,w[maxn][maxn];
int max(int x,int y,int z)
{
	return std::max(std::max(x,y),z);
}
int main()
{
	memset(w,-0x1f,sizeof(w));
	scanf("%d%d",&n,&m);
	int x=(m/2+1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&w[i][j]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			f[i][j]+=max(f[i-1][j-1],f[i-1][j],f[i-1][j+1])+w[i][j];
	printf("%d",max(f[n][x],f[n][x+1],f[n][x-1]));
	return 0;
}

P2049 魔术棋子

这道题一看似乎是搜索,然而2^n肯定是过不了的
观察数据,k<=100,应该想到是关于剩余容量可能性的dp
f[i][j][k]表示i,j处是否能得到k这个数
最朴素的转移是枚举所有状态,枚举上一次的所有可能性看看能不能得到当前状态,复杂度O(nmk^2)
不过我们发现,枚举当前状态效率很低,因为有很多废状态根本不可能由上一个转移而来吗
所以我们直接枚举上一次的所有状态即可,复杂度O(nmk)稳如老狗

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=111,maxm=111,inf=0x1f1f1f1f;
int n,m,p,w[maxn][maxn],f[maxn][maxn][maxn],ans,anss[maxn];
int main()
{
	scanf("%d%d%d",&n,&m,&p);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&w[i][j]);
	f[1][1][w[1][1]%p]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int k=0;k<p;k++)
			{
				if(f[i][j][k*(w[i][j]%p)%p])
					continue;
				f[i][j][k*(w[i][j]%p)%p]=f[i-1][j][k]|f[i][j-1][k];
			}
	for(int k=0;k<p;k++)
	{
		ans+=f[n][m][k];
		if(f[n][m][k])
			anss[ans]=k;
	}
	printf("%d\n",ans);
	for(int i=1;i<=ans;i++)
		printf("%d ",anss[i]);
	return 0;
}

线性DP

P1057 传球游戏

感觉这题递推很神,于是就写了比较友善的记搜
顺便剪了个并无卵用的
在条件允许的时候可以两次两次跳,特殊地,有两种可能跳回原地,应注意

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int n,m,f[maxn][maxn];
int dfs(int u,int step)
{
    u+=u<=0?n:0;u+=u>n?-n:0;
    if(f[u][step])
        return f[u][step];
    if(step==m)
        return f[u][step]=u==1;
    return f[u][step]=step==m-1?dfs(u+1,step+1)+dfs(u-1,step+1):dfs(u+2,step+2)+dfs(u-2,step+2)+dfs(u,step+2)*2;
}
int main()
{
    scanf("%d%d",&n,&m);
    printf("%d",dfs(1,0));
    return 0;
}

递推写法
一直以为两层循环1-n在第一层无法转移
结果发现如果1-m在第一层就没问题了
第二次移动一定是由第一次移动更新而来
记搜是枚举点判断次数
递推应该反过来

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int n,m,f[maxn][maxn];
int dfs(int u,int step)
{
    u+=u<=0?n:0;u+=u>n?-n:0;
    if(f[u][step])
        return f[u][step];
    if(step==m)
        return f[u][step]=u==1;
    return f[u][step]=step==m-1?dfs(u+1,step+1)+dfs(u-1,step+1):dfs(u+2,step+2)+dfs(u-2,step+2)+dfs(u,step+2)*2;
}
int main()
{
    scanf("%d%d",&n,&m);
    f[1][0]=1;
    for(int i=1;i<=m;i++)
    {
        f[1][i]=f[n][i-1]+f[2][i-1];
        for(int j=2;j<n;j++)
            f[j][i]=f[j-1][i-1]+f[j+1][i-1];
        f[n][i]=f[1][i-1]+f[n-1][i-1];
    }
    printf("%d",f[1][m]);
    return 0;
}

P1754 球迷购票问题

很明显,50一定在100前
问题转化为括号匹配,然后直接套卡特兰数
f[i][j]表示已经拿了i张50,j张100
转移考虑对于每一种(i,j)都能从上一张的两种选择更新

for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)
		f[i][j]=f[i-1][j]+f[i][j-1];

只有50的状态为初始状态

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef long long ll;
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int n;
ll f[maxn][maxn];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		f[i][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			f[i][j]=f[i-1][j]+f[i][j-1];
	printf("%lld",f[n][n]);
	return 0;
}

P1095 守望者的逃离

time是c的关键字!!!
事实证明能用贪心做的dp尽量分着顺序写,每一部分单独考虑

#include<iostream>
#include<cstdio>
int const maxn=300110,maxm=111,inf=0x1f1f1f1f;
int m,s,tim,f[maxn];
int main()
{
	scanf("%d%d%d",&m,&s,&tim);
	for(int i=1;i<=tim;i++)
	{
		if(m>=10)
			f[i]=f[i-1]+60,m-=10;
		else
			f[i]=f[i-1],m+=4;
	}
	//优先处理闪烁
	for(int i=1;i<=tim;i++)
	{
		f[i]=std::max(f[i-1]+17,f[i]);
		if(f[i]>=s)
		{
			puts("Yes");
			printf("%d",i);
			return 0;
		}
	}
	puts("No");
	printf("%d",f[tim]);
	return 0;
}

P3399 丝绸之路

事实证明,状态对于动态规划重要性远大于其他!
才不会说我搞了个走到第i个城市休息了j天的状态然后不会初始化就一直锅着
f[i][j]表示第i天在第j个城市处

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1011,maxm=111,inf=0x1f1f1f1f;
int n,m,np[maxn],w[maxn],f[maxn][maxn];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&np[i]);
    for(int i=1;i<=m;i++)
        scanf("%d",&w[i]);
    memset(f,0x1f,sizeof(f));
    for(int i=0;i<=m;i++)
        f[i][0]=0;
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            f[i][j]=std::min(f[i-1][j-1]+w[i]*np[j],f[i-1][j]);
    printf("%d",f[m][n]);
    return 0;
}

P1387 最大正方形

水题,不过考虑到用二维前缀和直接水岂不是很low做题的初心,还是写了些递推,然而根本不会
因为最大的正方形一定不是以0结尾
所以我们可以用f[i][j]表示右下角为(i,j)的最大正方形
右下角如果为零,显然就不符合状态 if(!a[i][j]) f[i][j]=0;
对于正方形,我们肯定是想考虑他的内部填充,但对于每一次扩大出来的点,还是要处理一下,作为左右边界
因此转移就可以表示为
f[i][j]=min(f[i-1][j-1], 从当前位置向上延伸连续的1的个数, 当前位置左侧延伸连续1的个数)
简化一下会发现后两条就是对正方形边缘的处理
等价于f[i][j]=min(f[i-1][j],f[i][j-1],f[i-1][j-1])+1;

#include<cstdio>
#include<iostream>
int const maxn=111;
int f[maxn][maxn],n,m,ans;
int min(int x,int y,int z)
{
	return std::min(std::min(x,y),z);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int a,j=1;j<=m;j++)
			scanf("%d",&a),f[i][j]=a?min(f[i-1][j],f[i][j-1],f[i-1][j-1])+a:0,ans=std::max(ans,f[i][j]);
	printf("%d",ans);
	return 0;
}

P1681 最大正方形2

这题感觉跟最大正方形1一毛一样啊,就是转移条件变了变
f[i][j]表示右下角在(i,j)长度最大正方形
对于每一个f[i][j],我们都希望去考虑他的子结构
包括

  1. 内部填充
  2. 向左边延伸
  3. 向右边延伸

然后对(i,j)这个点特殊处理一下即可(显然只有(a[i][j] a[i-1][j-1]相等 a[i-1][j] a[i][j-1]相等,两对不等才合法)

转移方程

if(!(a[i][j]^a[i-1][j-1]^a[i-1][j]^a[i][j-1]))
                f[i][j]=min(f[i-1][j-1],f[i-1][j],f[i][j-1])+1;

最后答案记得+1,原因很显然:没有初始化

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1611,inf=0x1f1f1f1f;
int f[maxn][maxn],a[maxn][maxn],n,m,ans;
int min(int x,int y,int z)
{
    return std::min(std::min(x,y),z);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(!(a[i][j]^a[i-1][j-1]^a[i-1][j]^a[i][j-1]))
                f[i][j]=min(f[i-1][j-1],f[i-1][j],f[i][j-1])+1;
            ans=std::max(f[i][j],ans);
        }
    if(!ans)
        ans--;
       	//特判,原因显然是边长为1无法构成01相间的正方形
    printf("%d",ans+1);
    return 0;
}

P2004 领地选择

二维前缀和的应用
先求出以(1,1)为左上角每个点为右下角的矩形的和
即求二维前缀和
b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j];
由于正方形的性质
我们只需要枚举右下角的坐标(x,y)
f[i][j]表示以(i,j)为右下角(i-c,j-c)为左上角的正方形的和
转移方程:f[i][j]=b[i][j]-b[i-c][j]-b[i][j-c]+b[i-c][j-c]

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1611,inf=0x1f1f1f1f;
int f[maxn][maxn],a[maxn][maxn],b[maxn][maxn];
int lastx,lasty,n,m,c,ans=-inf;
int main()
{
	scanf("%d%d%d",&n,&m,&c);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&a[i][j]),b[i][j]=a[i][j]+b[i-1][j]+b[i][j-1]-b[i-1][j-1];
	for(int i=c;i<=n;i++)
		for(int j=c;j<=m;j++)
		{
			f[i][j]=b[i][j]-b[i-c][j]-b[i][j-c]+b[i-c][j-c];
			if(ans<f[i][j])
			{
				ans=f[i][j];
				lastx=i-c,lasty=j-c;
			}
		}  
	printf("%d %d",lastx+1,lasty+1);
	return 0;
}

P2072 宗教问题

这题线性结构十分明显,每次转移都要遍历寻找上一次的状态
f[i]表示前i个人至少要分成多少个集合
ff[i]表示前i个人的至少有多少点危险度
易知,每一次的转移都要寻找到上一个集合的状态
因此就有两重循环
i : 1~n 遍历所有点
j : i~1 寻找最后一个集合(也就是当前点所在的集合)的开头元素,注意当最后一个集合元素种数超过k就break
转移很简单
因为j是最后一个集合的开头,j-1就是上一个集合的结尾

f[i]=std::min(f[i],f[j-1]+1);
ff[i]=std::min(ff[i],ff[j-1]+cnt);
//其中cnt为当前集合的元素种数

边界问题
而且由于j-1会访问到0,所以0要特殊处理

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1101,maxm=1101,inf=0x1f1f1f1f;
int n,V,f[maxn],ff[maxn],m,k,bel[maxm],cnt,ton[maxn];
int min(int x,int y,int z)
{
    return std::min(std::min(x,y),z);
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
		scanf("%d",&bel[i]);
	memset(f,0x1f,sizeof(f));
	memset(ff,0x1f,sizeof(ff));
	f[0]=0,ff[0]=0;
	for(int i=1;i<=n;i++)
	{
		cnt=0;
		memset(ton,false,sizeof(ton));
		for(int j=i;j>=1;j--)
		{
			if(!ton[bel[j]])
				cnt++,ton[bel[j]]=true;
			if(cnt>k)
				break;
			f[i]=std::min(f[i],f[j-1]+1);
			ff[i]=std::min(ff[i],ff[j-1]+cnt);
		}
	}
	printf("%d\n%d",f[n],ff[n]);
	return 0;
}

P1564 膜拜

宗教那道题的弱化版
开始直接把板子改了改码了上去,然后WA了0.5h…
后来当发现不满足条件时,不能直接break掉,因为有可能会反向来人导致条件又满足了,比如m=1时,当前有221,下一个是2,然而下4个是2111,这样条件又满足了

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=2511,inf=0x1f1f1f1f;
int f[maxn],ff[maxn],ton[maxn],bel[maxn];
int n,m;
int min(int x,int y,int z)
{
    return std::min(std::min(x,y),z);
}
int abs(int x)
{
	return x>0?x:-x;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&bel[i]);
	memset(f,0x1f,sizeof(f));
	f[0]=0;
	for(int i=1;i<=n;i++)
	{
		memset(ton,0,sizeof(ton));
		for(int j=i;j>=1;j--)
		{
			ton[bel[j]]++;
			if(abs(ton[1]-ton[2])<=m||!ton[1]||!ton[2])
				f[i]=std::min(f[i],f[j-1]+1);
		}
	}
	printf("%d",f[n]);
	return 0;
}

(待处理)区间DP

P2426 删数

P2858 Treats for the cows

P1435 回文子串

P2066 机器分配

(待处理)单调队列优化DP

P1440 求m区间内的最小值

P2782 友好城市

P2627 修剪草坪

P3957 跳房子

二分答案+DP

实质上是二分答案,然后用dp检验可行性

P2370 yyy2015c01的U盘

对于大的背包来说,接口越大价值越大,满足单调性
直接二分答案+可行性01背包check是否可行

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1101,inf=0x1f1f1f1f;
int f[maxn],cv[maxn],w[maxn];
int n,p,V,mx=-1,ans;
int check(int mid)
{
	memset(f,0,sizeof(f));
	for(int i=1;i<=n;i++)
	{
		if(cv[i]>mid)
			continue;
		for(int j=V;j>=cv[i];j--)
			f[j]=std::max(f[j],f[j-cv[i]]+w[i]);
	}
	for(int j=V;j>=1;j--)
		if(f[j]>=p)
			return true;
	return false;
}
int main()
{
	scanf("%d%d%d",&n,&p,&V);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&cv[i],&w[i]),mx=std::max(mx,cv[i]);
	int l=1,r=mx;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(check(mid))
			ans=mid,
			r=mid-1;
		else
			l=mid+1;
	}
	if(ans)
	{
		printf("%d",ans);
		return 0;
	}
	printf("No Solution!");
	return 0;
}

(待处理)P3957 跳房子

(待处理)悬线法DP

P1436 棋盘分割

P1387 最大正方形

猜你喜欢

转载自blog.csdn.net/qq_40828060/article/details/83064425