0x51线性DP

ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443

poj2279 Mr. Young’s Picture Permutations

题目:http://poj.org/problem?id=2279

题目:有不超过5排学生,n1,n2..n5为每排人数
要求从前到后高度递减,从左到右高度递减,(想拍照...)
求有多少中安排的方法
-----------------------------------------------------
dp[a][b][c][d][e] 表示 第一排有a人...第二排b人...的方案数... 
线性dp,轮廓线dp
可以使用杨氏矩阵解决(不会,逃...)
#include<cstdio>
#include<iostream>
#include<cstring>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int N[6],n;
int main(){
	while(scanf("%d",&n) && n){
		memset(N,0,sizeof N);
		fo(i,1,n)scanf("%d",&N[i]);
		long long dp[N[1]+1][N[2]+1][N[3]+1][N[4]+1][N[5]+1]; // 内存不够,定义在里面 
		memset(dp, 0, sizeof(dp));
		dp[0][0][0][0][0]=1;
		fo(a,0,N[1]){
			fo(b,0,N[2]){
				fo(c,0,N[3]){
					fo(d,0,N[4]){
						fo(e,0,N[5]){
							if(a<N[1]) dp[a+1][b][c][d][e] += dp[a][b][c][d][e];
							if(b<N[2] && b<a) dp[a][b+1][c][d][e] += dp[a][b][c][d][e];
							if(c<N[3] && c<b && b<=a) dp[a][b][c+1][d][e] += dp[a][b][c][d][e];
							if(d<N[4] && d<c && c<=b && b<=a) dp[a][b][c][d+1][e] += dp[a][b][c][d][e];
							if(e<N[5] && e<d && d<=c && c<=b && b<=a) dp[a][b][c][d][e+1] += dp[a][b][c][d][e];
						}
					}
				}
			}
		}
		printf("%lld\n",dp[N[1]][N[2]][N[3]][N[4]][N[5]]); 
	}
	return 0;
}

CH5101 LCIS

题目:http://contest-hunter.org:83/contest/0x50「动态规划」例题/5101 LCIS

f[i][j] 表示 A1~Ai 与 B1~Bj 可以构成 B[j] 结尾的LCIS的长度
当A[i] != A[j] 时 f[i][j] = f[i-1][j] // Bj结尾
当A[i] == B[j] 时 f[i,j] = max(f[i-1,k]) + 1   0<k<j-1
i不变,当j增大时决策集从 0<k<j ====> 0<k<j+1
决策集只增不减少,所以我们可以使用一个变量维护这个决策集
决策集又增又减则可以使用单调列队维护
决策集不在列队头或队尾,可以考虑单调列队同时维护二叉堆 
#include<bits/stdc++.h>
#define ll long long 
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=3005;
int n,a[N],b[N],f[N][N];
int main(){
	scanf("%d",&n);
	fo(i,1,n)scanf("%d",&a[i]); 
	fo(i,1,n)scanf("%d",&b[i]);
	a[0]=b[0]=-0x3f3f3f3f;
	fo(i,1,n){
		// val 是决策集合S(i,j)中f[i-1][j]的最大值 
		int val = 0;
		if(b[0]<a[i]) val = f[i-1][0];
		// j 每加1, f[i][j] 的决策集增加 
		fo(j,1,n){
			if(a[i]==b[j]) f[i][j] = val+1;
			else f[i][j] = f[i-1][j];
			// j 即将增大为j+1,检查j能否进入新的决策集合 
			if(b[j] < a[i]) val = max(val, f[i-1][j]);		
		}
	}
	int ans = 0;
	fo(i,1,n)ans = max(ans, f[n][i]);
	cout<<ans;
	return 0;
} 

poj3666 Making the Grade

题目:http://poj.org/problem?id=3666

给一个数列A
求构造B,单调不上升或者单调不下降
使得sum(|A[i]-B[i]|)最小 i属于1~n
-----------------------------------
引理,B数列的任何一个数属于A
f[i][j] 表示 构造到第i个数,第i个数为j的sum值
f[i][j] = min(f[i-1][k] + |A[i]-j|) 0<=k<=j
显然i不变随着j的增大 k的范围增大,决策集只增不减
可以使用一个变量来维护决策集
A数组的数值比较大需要先离散化
------------------------------------
思考题
把序列A变成非严格单调递增的序列最少需要修改的数的个数
A的长度 - 最长不下降序列的长度

把序列A变成严格单调递增的序列最少需要修改的数的个数
构造 B[i] = A[i]-i
答案 =  B的长度 - B最长不下降序列的长度
因为 A[i] >= A[i-1]+1
===> A[i]-i >= A[i-1]-(i-1) 
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N = 2005;
int a[N],n,num[N],c[N];
ll f[N][N],dp[N][N];
int main(){
	scanf("%d",&n);
	fo(i,1,n)scanf("%d",&a[i]),num[i]=a[i];
	sort(num+1, num+1+n);
	int m = unique(num+1, num+1+n) - (num+1);
	fo(i,1,n) c[i] = lower_bound(num+1, num+1+m, a[i]) - num;
	memset(f,127,sizeof(f));
	memset(dp,127,sizeof(dp));
	f[0][0] = 0;
	dp[0][m+1] = 0; 
	fo(i,1,n){
		// 单调上升 
		ll temp = f[i-1][0];
		fo(j,1,m){
			temp = min(temp, f[i-1][j]);
			f[i][j] = temp + abs(a[i]-num[j]);
		}
		// 单调下降 
		temp = dp[i-1][m+1];
		for(int j=m; j>=1; j--){
			temp = min(temp, dp[i-1][j]);
			dp[i][j] = temp + abs(a[i]-num[j]);
		}
	}
	ll ans = 1e18;
	fo(i,1,m)ans = min(ans, min(dp[n][i],f[n][i]));
	printf("%lld\n",ans);;
	return 0;
} 

CH5102 Mobile Service

题目:http://contest-hunter.org:83/contest/0x50「动态规划」例题/5102 Mobile Service

有3个员工起始在1,2,3个位置
他们要去p1,p2...pN按顺序完成任务 N<=2000,位置有200个 
移动的代价为c[x][p1]从x到p1
要求一个位置不能同时被两个员工占领
求完成所有P后代价最少为多少
-----------------------------------
显然阶段为Pi
需要记录3个状态,3个员工的位置,直接记录的话内存不够 
我们可以发现在阶段i,必定有一个员工在pi,所以我们只需要记录另外两个员工的位置就好

f[i][x][y] 表示完成了前i个任务,3个员工的位置分别为 x,y,p[i]的最小花费
f[0][1][2] = 0; p[0] = 3;
// 分别dp,从p[i-1]到p[i],x到p[i],y到p[i] 
f[i][x][y] = min(f[i][x][y], f[i-1][x][y]+c(p[i-1],p[i]) ) x!=p[i],y!=p[i]
f[i][p[i-1]][y] = min(f[i][p[i-1]][y], f[i-1][x][y]+c(x,p[i])) p[i-1]!=p[i],y!=p[i]
f[i][x][p[i-1]] = min(f[i][x][p[i-1]], f[i-1][x][y]+c(y,p[i])) p[i-1]!=p[i],x!=p[i]
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=205,M=1005,INF=0x3f3f3f3f;
int c[N][N],p[M],n,m;
int f[M][N][N];
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,n)fo(j,1,n)scanf("%d",&c[i][j]);
	fo(i,1,m)scanf("%d",&p[i]);
	memset(f, 0x3f, sizeof(f));
	f[0][1][2]=0;
	p[0]=3;
	fo(i,1,m){
		fo(x,1,n){
			fo(y,1,n){
				if(f[i-1][x][y]!=INF){ // f[i-1][x][y]是个合法位置 
					if(x!=p[i] && y!=p[i]) 
						f[i][x][y] = min(f[i][x][y], f[i-1][x][y]+c[p[i-1]][p[i]]);
					if(y!=p[i] && p[i-1]!=p[i]) 
						f[i][p[i-1]][y] = min(f[i][p[i-1]][y], f[i-1][x][y]+c[x][p[i]]);
					if(x!=p[i] && p[i-1]!=p[i]) 
						f[i][x][p[i-1]] = min(f[i][x][p[i-1]], f[i-1][x][y]+c[y][p[i]]);
				}
			}
		}
	}
	int ans = INF;
	fo(i,1,n)fo(j,1,n)ans = min(ans, f[m][i][j]);
	printf("%d\n",ans); 
	return 0;
} 

CH5103 传纸条

题目:http://contest-hunter.org:83/contest/0x50「动态规划」例题/5103 传纸条

CH5103 传纸条
----------------------------
有这么一些状态
步数i,两个状态(x1,y1)(x2,y2)

我们考虑寻找能够覆盖整个状态空间的最小的 "维度集合" 

由于每次两个坐标只能走一次
所以我们可以观察到步数和坐标之间的关系有:
i+2 = x1+y1 = x2+y2
所以我们可以维护3个状态f[横纵坐标和][x1][x2]
那么可以通过横纵坐标和 以及 x1,x2
求出(x1,y1),(x2,y2)

转移方程:
每个点可以有前面的两个点转移过来,2*2=4
所以每个状态可以由四种状态转移过来,取最大

f[x1][y1][x2][y2] <====
	max(f[x1-1][y1][x2-1][y2],
		f[x1-1][y1][x2][y2-1],
		f[x1][y1-1][x2-1][y2],
		f[x1][y1-1][x2][y2-1]) + a[x1][y1] + a[x2][y2];
对应为:
f[i][x1][x2] <=====
	max(f[i-1][x1-1][x2],f[i-1][x1-1][x2-1],
		f[i-1][x1][x2-1],f[i-1][x1][x2-1]) + a[x1][y1] + a[x2][y2];
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=55;
int n,m,a[N][N];
int f[105][N][N];
int my_max(int a, int b, int c, int d){
	return max(max(a,b),max(c,d));
} 
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,n)fo(j,1,m)scanf("%d",&a[i][j]);
	fo(i,2,m+n){// 横纵坐标和相等
		for(int x1=1; x1<=n; x1++){
			for(int x2=1; x2<=n; x2++){
				if(i-x1<1 || i-x2<1)continue;
				int y1=i-x1, y2=i-x2;
				f[i][x1][x2] = my_max(f[i-1][x1-1][x2],f[i-1][x1-1][x2-1],f[i-1][x1][x2-1],f[i-1][x1][x2])
								+ a[x1][y1] + a[x2][y2];
				if(x1==x2) f[i][x1][x2] -= a[x1][y1];  // 同一个格子上只能踩一次 
			}
		}
	} 
	cout<<f[n+m][n][n];
	return 0;
}

CH5105 Cookies

题目:http://contest-hunter.org:83/contest/0x50「动态规划」例题/5105 Cookies

CH5105 Cookies
圣诞老人共有M个饼干,准备全部分给N个孩子。每个孩子有一个贪婪度,
第 i 个孩子的贪婪度为 g[i]。如果有 a[i] 个孩子拿到的饼干数比第 i 个孩子多,
那么第 i 个孩子会产生 g[i]*a[i]的怨气。给定N、M和序列g,
圣诞老人请你帮他安排一种分配方式,使得每个孩子至少分到一块饼干,
并且所有孩子的怨气总和最小。1≤N≤30, N≤M≤5000, 1<=gi<=10^7。 
--------------------------------------------------------------------------
安排啊方案啊...N,M都比较小,可以DP,不会啊...
"已经获得饼干的孩子","已经发放的饼干",可以作为阶段
然后一个孩子的g值会影响他获得的饼干数

贪心策略的g值高的孩子获得的饼干数多
可以交换两个值进行比较证明.
设g[i]>g[i-1]>g[i-2]...
获得饼干 c[i]>c[i-1]>c[i-2]...
贡献值 X = 0*g[i]+1*g[i-1]+2*g[i-2]...
 
 g[i]>g[i-1]>g[i-2]...
获得饼干 c[i-1]>c[i]>c[i-2]...
贡献值 Y = 0*g[i-1]+1*g[i]+2*g[i-2]...
g[i] > g[i-1] 显然Y>X...故用X

a[i]为第i个孩子的饼干数 
对于第i+1个孩子饼干数有两种选择,要么等于第i个孩子,要么少于第i个孩子a[i+1]=i
可是我们必须知道a[i],但是很难维护....

画出饼干递减的柱形图
发现若第i个孩子饼干数大于1的话,
则等价于把前i个孩子的饼干数都减少1
因为饼干数的相对大小顺序不变,所以总贡献也不变
f[i][j] <=== f[i][j-i] 

然后,若第i个孩子的饼干数为1的话,
则枚举i前面有k个孩子的饼干数等于1
f[i][j] <=== min( f[k][j-(i-k)] + k*(g[k+1]~g[i]) )
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=33,M=5005;
int n,m,g[N],f[N][M],a[N][M],b[N][M],c[N],sum[N],ans[N];
bool cmp(int i, int j){
	return g[i]>g[j];
}
void print(int n, int m){
	if(n==0 || m==0)return;
	print(a[n][m],b[n][m]);
	if(a[n][m]==n){
		for(int i=1; i<=n; i++)ans[c[i]]++;
	}else{
		for(int i=a[n][m]+1; i<=n; i++)ans[c[i]]=1;
	}
}
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,n)scanf("%d",&g[i]),c[i]=i;
	sort(c+1,c+1+n,cmp);
	memset(f,0x3f,sizeof(f));
	f[0][0]=0;  
	fo(i,1,n){
		sum[i] = sum[i-1]+g[c[i]];
		fo(j,i,m){
			// 第i个孩子的饼干数大于1,那么次状态等于前i个孩子的饼干数都减1
			// 因为相对排名不变,对答案的贡献不变 
			f[i][j] = f[i][j-i];  // i==j也可以...持平,总贡献为0 
			a[i][j] = i; // 记录上一个状态的i 
			b[i][j] = j-i;// 记录上一个状态的j 
			for(int k=0; k<i; k++){
				if(f[i][j] > f[k][j-(i-k)] + k*(sum[i]-sum[k])){
					f[i][j] = f[k][j-(i-k)] + k*(sum[i]-sum[k]);
					a[i][j] = k;
					b[i][j] = j-(i-k);
				}
			}
		}
	}
	cout<<f[n][m]<<endl;
	print(n,m);
	fo(i,1,n)printf("%d%c",ans[i],i==n?'\n':' ');
	return 0;
}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页