动规A—线性dp

A.照相排列

AcWing271

\(1≤k≤5\),学生总人数不超过 30 人。

每排人数最多30,最多5排(数据范围较小,可以考虑多维枚举)
5维dp,5层循环枚举所有转移状态,对每一层能转移的进行累加转移
初值:\(f[0][0][0][0][0]=1\)
答案即为排满时的方案数

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=31;
int a[10];
long long f[N][N][N][N][N];
signed main()
{
    int k;
    cin>>k;
    while(k)
    {
        memset(a,0,sizeof(a));
        memset(f,0,sizeof(f));
        per(i,1,k) scanf("%d",a+i);
        f[0][0][0][0][0]=1;//i j p q e-->5 4 3 2 1
        per(i,0,a[1]) per(j,0,a[2])
        {
            per(p,0,a[3]) per(q,0,a[4]) per(e,0,a[5])
            {
                if(i<a[1]) f[i+1][j][p][q][e]+=f[i][j][p][q][e];
                if(j<a[2]&&i>j) f[i][j+1][p][q][e]+=f[i][j][p][q][e];
                if(p<a[3]&&j>p) f[i][j][p+1][q][e]+=f[i][j][p][q][e];
                if(q<a[4]&&p>q) f[i][j][p][q+1][e]+=f[i][j][p][q][e];
                if(e<a[5]&&q>e) f[i][j][p][q][e+1]+=f[i][j][p][q][e];
            }
        }
        printf("%lld\n",f[a[1]][a[2]][a[3]][a[4]][a[5]]);
        scanf("%d",&k);
    }
    return 0;
}

B. 最长公共上升子序列

给定两个序列,求这两个序列的最长公共上升子序列。
\(1≤n,m,a_i,b_i≤2000\)

两层循环枚举所有匹配方式

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=2010;
int a[N],b[N],dp[N][N];
signed main()
{
	int n,m;
	cin>>n;
	per(i,1,n) scanf("%d",a+i);
	cin>>m;
	per(i,1,m) scanf("%d",b+i);
	per(i,1,n) per(j,1,m)//从每一个a里的数字去b里匹配
	{
		dp[i][j]=dp[i][j-1];//从上一个状态递推
		if(a[i]==b[j]) per(k,0,i)//有公共的,向前找最长公共上升
		{
			if(a[i]>a[k]) dp[i][j]=max(dp[i][j],dp[k][j-1]+1);
		}
	}
	int ans=0;
	per(i,1,n) ans=max(ans,dp[i][m]);//a的各个位置的max
	printf("%d\n",ans);
    return 0;
}

C. 分级

poj3666 AcWing273

给定长度为 N 的序列 A,构造一个长度为 N 的序列 B,满足:
B 非严格单调,即\(B_1≤B_2≤…≤B_N\)\(B_1≥B_2≥…≥B_N\)
\(最小化 S=\sum\limits_{i=1}^N∣Ai−Bi∣。\)
求最小的S

一个性质:一定存在一组最优解\(b[i]\),使得每个\(b[i]\)都是原序列中\(a[i]\)的值。

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=2010;
int a[N],aa[N],f[N][N];
bool cmp(int f,int s){ return f>s;}
signed main()
{
	int n,ans=0x3f3f3f3f;
	cin>>n;
	per(i,1,n) scanf("%d",a+i),aa[i]=a[i];
	sort(aa+1,aa+n+1);//正序
	memset(f,0x3f,sizeof(f));
	per(i,1,n) f[0][i]=0;
	per(i,1,n) per(j,1,n) f[i][j]=min(f[i][j-1],f[i-1][j]+abs(a[i]-aa[j]));
    //比较a[i]取哪个aa[i]结果最小(保证单调性)
	per(i,1,n) ans=min(ans,f[n][i]);
	memset(f,0x3f,sizeof(f));
	per(i,1,n) f[0][i]=0;
	reverse(aa+1,aa+n+1);//逆序
	per(i,1,n) per(j,1,n) f[i][j]=min(f[i][j-1],f[i-1][j]+abs(a[i]-aa[j]));
    //比较a[i]取哪个aa[i]结果最小(保证单调性)
	per(i,1,n) ans=min(ans,f[n][i]);
	printf("%d\n",ans);
	return 0;
}

D. 移动服务

AcWing274

一个公司有三个移动服务员,最初分别在位置1,2,3处。
如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个地方去。
某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。
从 p到 q 移动一个员工,需要花费 \(c(p,q)\)
这个函数不一定对称,但保证\(c(p,p)=0\)
给出 N 个请求,请求发生的位置分别为 \(p_1∼p_N\)
公司必须按顺序依次满足所有请求,且过程中不能去其他额外的位置,目标是最小化公司花费,请你帮忙计算这个最小花费

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=210,M=1010;
int a[N][N],p[M],f[2][N][N];
//p 存请求,f为压维dp,后两维是两个服务员的位置,第三个的位置由p可推出
signed main()
{
	int z,v,u,n,m;
	cin>>n>>m;
	per(i,1,n) per(j,1,n) scanf("%d",&a[i][j]);
	per(i,1,m) scanf("%d",p+i);
	p[0]=3;
	memset(f,0x3f,sizeof(f));
	per(i,0,m-1)
	{
		memset(f[(i+1)%2],0x3f,sizeof(f[(i+1)%2]));
		per(x,1,n) per(y,1,n)
		{
			z=p[i],v=f[i%2][x][y];//z:第三个服务员位置,v:枚举每一个转移来的状态
			if(i==0&&x==1&&y==2) v=0;//初值
			if(x==y||y==z||x==z) continue;
			u=p[i+1];//转移
			f[(i+1)%2][x][y]=min(f[(i+1)%2][x][y],v+a[z][u]);
			f[(i+1)%2][x][z]=min(f[(i+1)%2][x][z],v+a[y][u]);
			f[(i+1)%2][z][y]=min(f[(i+1)%2][z][y],v+a[x][u]);
		}
	}
	int ans=INT_MAX;
	per(i,1,n) per(j,1,n)
	{
		z=p[m];
		if(i==j||j==z||i==z) continue;
		ans=min(ans,f[m%2][i][j]);//取最小值
	}
	printf("%d\n",ans);
	return 0;
}

E. I-区域

AcWing276

\(N×M\) 的矩阵中,每个格子有一个权值,要求寻找一个包含 K 个格子的凸连通块(连通块中间没有空缺,并且轮廓是凸的),使这个连通块中的格子的权值和最大。
注意:凸连通块是指:连续的若干行,每行的左端点列号先递减、后递增,右端点列号先递增、后递减。
求出这个最大的权值和,并给出连通块的具体方案,输出任意一种方案即可。

状态表示:\(f[i][j][l][r][x][y]\),表示当前处理到了第i行,已经选出了j个格子,第i行的选择是从第l列到第r列,x,y表示当前行的左右端点是往外伸还是往里面缩,我们规定端点的列坐标比上一行的列坐标小用1表示,大了用0表示,所以左端点往外伸/缩的状态对应x=1/0,右端点往外伸/缩的状态对应y=0/1。

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=16;
int a[N][N];
int f[N][N*N][N][N][2][2];//左:扩/缩-1/0,右:0/1
struct aaa{
	int i,k,l,r,x,y;
}g[N][N*N][N][N][2][2],e;
signed main()
{
	int n,m,k,val;
	cin>>n>>m>>k;
	per(i,1,n) per(j,1,m) scanf("%d",&a[i][j]);
	memset(f,-0x3f3f3f3f,sizeof(f));
	per(i,1,n) per(j,0,k)
	{
		per(l,1,m) per(r,1,m)
		{
			if(j<r-l+1) continue;
			//左扩,右扩
			{
				auto &vf=f[i][j][l][r][1][0];
				auto &vg=g[i][j][l][r][1][0];
				if(j==r-l+1) vf=0;
				per(p,l,r) per(q,p,r)
				{
					val=f[i-1][j-(r-l+1)][p][q][1][0];
					if(val>vf)
					{
						vf=val;
						vg={i-1,j-(r-l+1),p,q,1,0};
					}
				}
				per(u,l,r) vf+=a[i][u];
			}
			//左扩,右缩
			{
				auto &vf=f[i][j][l][r][1][1];
				auto &vg=g[i][j][l][r][1][1];
				if(j==r-l+1) vf=0;
				per(p,l,r) per(q,r,m) per(y,0,1)
				{
					val=f[i-1][j-(r-l+1)][p][q][1][y];
					if(val>vf)
					{
						vf=val;
						vg={i-1,j-(r-l+1),p,q,1,y};
					}
				}
				per(u,l,r) vf+=a[i][u];
			}
			//左缩,右扩
			{
				auto &vf=f[i][j][l][r][0][0];
				auto &vg=g[i][j][l][r][0][0];
				// if(j==r-l+1) vf=0;
				per(p,1,l) per(q,l,r) per(x,0,1)
				{
					val=f[i-1][j-(r-l+1)][p][q][x][0];
					if(val>vf)
					{
						vf=val;
						vg={i-1,j-(r-l+1),p,q,x,0};
					}
				}
				per(u,l,r) vf+=a[i][u];
			}
			//左缩,右缩
			{
				auto &vf=f[i][j][l][r][0][1];
				auto &vg=g[i][j][l][r][0][1];
				// if(j==r-l+1) vf=0;
				per(p,1,l) per(q,r,m) per(x,0,1) per(y,0,1)
				{
					val=f[i-1][j-(r-l+1)][p][q][x][y];
					if(val>vf)
					{
						vf=val;
						vg={i-1,j-(r-l+1),p,q,x,y};
					}
				}
				per(u,l,r) vf+=a[i][u];
			}
		}
	}
	int ans=0;
	per(i,1,n) per(l,1,m) per(r,1,m) per(x,0,1) per(y,0,1)
	{
		val=f[i][k][l][r][x][y];
		if(val>ans)
		{
			ans=val;
			e={i,k,l,r,x,y};
		}
	}
	printf("%d\n",ans);
	if(!ans) return 0;
	while(e.k)
	{
		per(i,e.l,e.r) printf("%d %d\n",e.i,i);
		e=g[e.i][e.k][e.l][e.r][e.x][e.y];
	}
	return 0;
}

F. 饼干

AcWing277

圣诞老人共有 \(M\)个饼干,准备全部分给 \(N\)个孩子。
每个孩子有一个贪婪度,第i个孩子的贪婪度为 \(g[i]\)
如果有 \(a[i]\) 个孩子拿到的饼干数比第 i 个孩子多,那么第 i 个孩子会产生 \(g[i]×a[i]\) 的怨气。
给定 N、M和序列 g,圣诞老人请你帮他安排一种分配方式,使得每个孩子至少分到一块饼干,并且有孩子的怨气总和最小。

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
int g[35],f[35][5010],s[35];//f[i][j]前i个小孩分到j个饼干的怨气值
signed main()
{
	int n,m;
	cin>>n>>m;
	per(i,1,n) scanf("%d",g+i);
	sort(g+1,g+n+1);
	reverse(g+1,g+n+1);//从大到小排序
	memset(f,0x3f,sizeof(f));
	f[0][0]=0;
	per(i,1,n)//怨气值从大到小,枚举小孩
	{
		s[i]=s[i-1]+g[i];
		per(j,i,m)//枚举饼干数
		{
			f[i][j]=min(f[i][j],f[i][j-i]);//每个小孩多给一块 j-i
			per(k,0,i-1) f[i][j]=min(f[i][j],f[k][j-i+k]+k*(s[i]-s[k]));
		}
	}
	printf("%d\n",f[n][m]);
	return 0;
}

G. 花店橱窗

n行m列的矩阵,要求每行选一个数,且该数的列数>上一行选的数
且要求输出选择的方案

二维dp,转移最大值(\(f\))的同时记录从哪转移来的(\(p\))

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=110;
int a[N][N],f[N][N],p[N][N],st[N];
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    int n,m;
    cin>>n>>m;
    per(i,1,n) per(j,1,m) scanf("%d",&a[i][j]);
    memset(f,-0x3f,sizeof(f));
    per(i,0,m) f[0][i]=0;
    per(i,0,n) f[i][0]=0;//初始化
    per(i,1,n) per(j,i,m)
    {
        if(j!=i)//有选择
        {
            if(f[i][j-1]>=f[i-1][j-1]+a[i][j])//选前面的数比当前优
            {
                f[i][j]=f[i][j-1];
                p[i][j]=p[i][j-1];
            }
            else//当前更优
            {
                f[i][j]=f[i-1][j-1]+a[i][j];
                p[i][j]=j;
            }
        }
        else //只能选当前
        {
            f[i][j]=f[i-1][j-1]+a[i][j];
            p[i][j]=j;
        }
    }
    int ans=-INT_MAX,nw=0,top=0;
    per(i,n,m) if(ans<f[n][i]) ans=f[n][i],nw=i;
    while(n)//从后往前找,入栈
    {
        st[++top]=nw;
        nw=p[--n][nw-1];
    }
    printf("%d\n",ans);
    for(int i=top;i>=1;--i) printf("%d ",st[i]);//出栈
    return 0;
}

H. 低买

求一个序列的最长单调递减序列长度个数

n<5000

\(n^2\)求最长单调序列,加统计方案

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=5010;
int a[N],f[N],dp[N];
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    int n,maxn=0,ans=0;
    cin>>n;
    per(i,1,n) scanf("%d",a+i);
	per(i,1,n)
	{
		f[i]=1;
		per(j,1,i-1)
		{
			if(a[j]>a[i]) f[i]=max(f[i],f[j]+1);//暴力统计
		}
		per(j,1,i-1)
		{
			if(f[i]==f[j]&&a[i]==a[j]) dp[j]=0;//更新清零
			else if(f[i]==f[j]+1&&a[j]>a[i]) dp[i]+=dp[j];//寻找转移,方案累加
		}
		maxn=max(maxn,f[i]);
		if(f[i]==1) dp[i]=1;//赋初始值
	}
	per(i,1,n) if(f[i]==maxn) ans+=dp[i];
	printf("%d %d\n",maxn,ans);
    return 0;
}

I. 旅行

两个序列,求他们的最长不连续公共序列

二维枚举,刷表,深搜输出

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=90;
char a[N],b[N],f[N][N],path[N],n,m;
void sol(int i,int j,int u,int len)
{
	if(u>len)
	{
		puts(path+1);
		return;
	}
	int x,y;
	if(a[i]==b[j]) 
	{
		path[u]=a[i];
		sol(i+1,j+1,u+1,len);//记录,找下一个
	}
	else per(k,0,25)//按字典序找
	{
		x=y=0;
		per(p,i,n) if(a[p]==k+'a') 
		{
			x=p;
			break;
		}
		per(p,j,m) if(b[p]==k+'a') 
		{
			y=p;
			break;
		}
		if(x&&y&&f[x][y]==f[i][j]) sol(x,y,u,len);
	}
}
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
	cin>>a+1>>b+1;
	n=strlen(a+1),m=strlen(b+1);
	for(int i=n;i>=1;--i) for(int j=m;j>=1;--j) 
	{
		if(a[i]!=b[j]) f[i][j]=max(f[i+1][j],f[i][j+1]);
		else f[i][j]=f[i+1][j+1]+1;//求最长公共序列
	}
	sol(1,1,1,f[1][1]);
    return 0;
}

J. 减操作

定义数组第 i 位上的减操作:把 $a_i $和 \(a_{i+1}\) 换成 \(a_i−a_{i+1}\)

con([12,10,4,3,5],2)=[12,6,3,5]
con([12,6,3,5],3)=[12,6,−2]
con([12,6,−2],2)=[12,8]
con([12,8],1)=[4]

问题转化为先求出各个位置的符号,再构造con

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=110,M=10010,base=10000;
int a[N],f[N][M*2],ans[N];
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
	int n,t;
	cin>>n>>t;
	per(i,1,n) scanf("%d",a+i);
	f[1][a[1]+base]=1,f[2][a[1]-a[2]+base]=-1;//a_2一定是 -
	per(i,3,n) per(j,-10000+base,10000+base) //dp求符号
	{
		if(f[i-1][j])
		{
			f[i][j-a[i]]=-1;
			f[i][j+a[i]]=1;
		}
	}
	int v=t+base;
	int x=n;
	while(x>1)
	{
		ans[x]=f[x][v];//逆推,找出每个位置对应符号
		if(ans[x]==1) v-=a[x];
		else v+=a[x];
		x--;
	}
	int cnt=0;
	per(i,2,n)
	{
		if(ans[i]==1)//为“+” 先执行 con
		{
			printf("%d ",i-cnt-1);
			++cnt;
		}
	}
	per(i,2,n) if(ans[i]==-1) printf("1 ");//对剩余“ - ”进行con
    return 0;
}

K. 传纸条

一个矩阵,找两条不重叠路线,使其权值和最大

四维矩阵dp,分别代表两个点的位置,分别有四种转移情况

\(f[i][j][k][l]= max( f[i-1][j][k-1][l], f[i-1][j][k][l-1], f[i][j-1][k-1][l],f[i][j-1][k][l-1])\)

特判重合

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=55;
int a[N][N],f[N][N][N][N];
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
	int n,m;
	cin>>n>>m;
	per(i,1,n) per(j,1,m) scanf("%d",&a[i][j]);
	per(i,1,n) per(j,1,m) per(k,1,n) per(l,1,m)
	{
		if(i==k&&j==l) f[i][j][k][l]=-100;
		else f[i][j][k][l]=max(max(f[i-1][j][k-1][l],f[i][j-1][k-1][l]),max(f[i-1][j][k][l-1],f[i][j-1][k][l-1]))+a[i][j]+a[k][l];
	}
	printf("%d\n",max(f[n-1][m][n][m-1],f[n][m-1][n-1][m]));
    return 0;
}

L. 乌龟棋

一行n个格子,每个格子有权值,有4种卡片,每次分别可以走1,2,3,4步,每跳一次,能拿取落点的权值,问怎么走才能拿到最大权值

输入的步数总和=n

注意处理越界状态

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=360,M=45;
int a[N],b[5],f[M][M][M][M];
int sol(int i,int j,int k,int l){ return a[i+j*2+k*3+l*4];}
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
	int n,m,x;
	cin>>n>>m;
	per(i,0,n-1) scanf("%d",a+i);
	per(i,1,m) scanf("%d",&x),b[x]++;
	f[0][0][0][0]=a[0];
	per(i,0,b[1]) per(j,0,b[2]) per(k,0,b[3]) per(l,0,b[4])//表示每张牌分别用了几次
	{
		x=sol(i,j,k,l);
		int &t=f[i][j][k][l];
		if(i) t=max(t,f[i-1][j][k][l]+x);
		if(j) t=max(t,f[i][j-1][k][l]+x);
		if(k) t=max(t,f[i][j][k-1][l]+x);
		if(l) t=max(t,f[i][j][k][l-1]+x);
	}
	printf("%d\n",f[b[1]][b[2]][b[3]][b[4]]);
	return 0;
}

参考文章:https://blog.csdn.net/weixin_54562114?type=blog

posted @ 2023-01-02 16:58  f2021yjm  阅读(25)  评论(0编辑  收藏  举报