线性DP组题

线性dp体现为在线性空间上的递推,dp的阶段沿着各个维度线性增长,譬如说最长上升子序列,最长公共子序列就是最为简单的两个问题。

这里记录一些《算法竞赛进阶指南》中的线性dp例题
1、[POJ2279]Mr.Young’s Picture Permutations
2、[CH5101]最长公共上升子序列(LCIS)
3、[POJ3666]Making the Grade
4、[CH5102]Mobile Service
5、[CH5103]传纸条(NOIP2008)
6、[CH5104]I-County
7、[CH5105]Cookies


1、[POJ2279]Mr.Young’s Picture Permutations
http://poj.org/problem?id=2279

由于行数k很小,所以我们可以用k个维度表示每一行的状态,设f[a1,a2,…,ak]表示每一行已经安排的学生人数,那么当每次要处理一个新人的时候我们需要考虑所有满足条件的行 i i i
1、 a i &lt; N i a_i&lt;N_i ai<Ni(可以加入一个新人而不超过限制)
2、 i = 1 i=1 i=1或者 a 1 &gt; a i , a 2 &gt; a i , . . . a i − 1 &gt; a i a_1&gt;a_i,a_2&gt;a_i, ... a_{i-1}&gt;a_i a1>ai,a2>ai,...ai1>ai(保证从前往后身高递减)

然后就可以转移了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int k;
int n[6];
int main()
{
	while(scanf("%d",&k)&&k)
	{
		memset(n,0,sizeof(n));
		for(int i=1;i<=k;i++) scanf("%d",&n[i]);
		long long f[n[1]+1][n[2]+1][n[3]+1][n[4]+1][n[5]+1];
		memset(f,0,sizeof(f)); f[0][0][0][0][0]=1;
		for(int a1=0;a1<=n[1];a1++)
			for(int a2=0;a2<=n[2];a2++)
				for(int a3=0;a3<=n[3];a3++)
					for(int a4=0;a4<=n[4];a4++)
						for(int a5=0;a5<=n[5];a5++)
						{
							if(a1<n[1]) f[a1+1][a2][a3][a4][a5]+=f[a1][a2][a3][a4][a5];
							if(a2<n[2] && a1>a2) f[a1][a2+1][a3][a4][a5]+=f[a1][a2][a3][a4][a5];
							if(a3<n[3] && a1>a3 && a2>a3) f[a1][a2][a3+1][a4][a5]+=f[a1][a2][a3][a4][a5];
							if(a4<n[4] && a1>a4 && a2>a4 && a3>a4) f[a1][a2][a3][a4+1][a5]+=f[a1][a2][a3][a4][a5];
							if(a5<n[5] && a1>a5 && a2>a5 && a3>a5 && a4>a5) f[a1][a2][a3][a4][a5+1]+=f[a1][a2][a3][a4][a5];
						}
		printf("%lld\n",f[n[1]][n[2]][n[3]][n[4]][n[5]]);
	}
	return 0;
}

2、[CH5101]最长公共上升子序列(LCIS)

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

我们在之前求解
最长上升子序列(LIS)问题:
f [ i ] f[i] f[i]为以 a [ i ] a[i] a[i]为结尾的最长上升子序列长度,则
f [ i ] = m a x { f [ j ] } + 1   ( 0 ≤ j &lt; i , A [ j ] &lt; A [ i ] ) f[i]=max \lbrace f[j] \rbrace +1\ (0\leq j&lt;i,A[j]&lt;A[i]) f[i]=max{f[j]}+1 (0j<i,A[j]<A[i])

最长公共子序列(LCS)问题:
f [ i , j ] f[i,j] f[i,j]表示 a [ 1 ~ i ] a[1~i] a[1i] b [ i ~ j ] b[i~j] b[ij]的最长公共子序列的长度,则
f [ i , j ] = m a x { f [ i , j − 1 ] f [ i − 1 , j ] f [ i − 1 , j − 1 ] + 1 if  a [ i ] = b [ j ] f[i,j]=max \begin{cases} f[i,j-1] \\ f[i-1,j] \\ f[i-1,j-1]+1 &amp;\text{if }a[i]=b[j] \end{cases} f[i,j]=maxf[i,j1]f[i1,j]f[i1,j1]+1if a[i]=b[j]

那么我们把两者结合起来:

f [ i , j ] f[i,j] f[i,j]表示 a [ 1 ~ i ] a[1~i] a[1i] b [ i ~ j ] b[i~j] b[ij]的最长公共上升子序列的长度,且我们默认以 b [ j ] b[j] b[j]结尾,那么:
当 a [ i ] ̸ = b [ j ] 时 , f [ i , j ] = f [ i − 1 , j ] 当a[i] \not=b[j]时,f[i,j]=f[i-1,j] a[i]̸=b[j]f[i,j]=f[i1,j]
当 a [ i ] = b [ j ] 时 , f [ i , j ] = m a x { f [ i − 1 , k ] } + 1 ( 0 ≤ k &lt; j , b [ k ] &lt; a [ i ] ) 当a[i]=b[j]时,f[i,j]=max \lbrace f[i-1,k] \rbrace+1(0 \leq k &lt;j,b[k]&lt;a[i]) a[i]=b[j]f[i,j]=max{f[i1,k]}+1(0k<j,b[k]<a[i])

我们发现这个转移所需要的复杂度是 O ( n 3 ) O(n^3) O(n3)级别的,我们需要把它优化到 O ( n 2 ) O(n^2) O(n2)级别。

因为j每一次增加1,都会带来 f [ i − 1 ] [ k ] f[i-1][k] f[i1][k]的集合增加1,也就是说 f [ i − 1 ] [ k ] f[i-1][k] f[i1][k]的集合的增加是随着j的增加单调的,那么我们就可以维护当前集合中最大的 f [ i − 1 ] [ k ] f[i-1][k] f[i1][k]来减少一维 。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=3e3+10;
int f[N][N],n;
int a[N],b[N];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
	f[0][0]=1; int maxx;
	for(int i=1;i<=n;i++)
	{
		maxx=0;
//发现f[i-1][k]的集合的增加是随着j的增加单调的,那么我们就可以维护最大的f[i-1][k]来减少一维 
		for(int j=1;j<=n;j++)
		{
			if(b[j]==a[i]) f[i][j]=maxx+1;
			else f[i][j]=f[i-1][j];
			if(b[j]<a[i]) maxx=max(maxx,f[i-1][j]);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			ans=max(ans,f[i][j]);
	printf("%d\n",ans);
	return 0;
}

3、[POJ3666]Making the Grade
题意:
给定长度为N的序列A,构造一个长度为N的序列B,满足:
1、B非严格单调,即 B 1 ≤ B 2 ≤ . . . . ≤ B N B_1\leq B_2\leq ....\leq B_N B1B2....BN B 1 ≥ B 2 ≥ . . . . ≥ B N B_1\geq B_2\geq ....\geq B_N B1B2....BN
2、最小化 S = ∑ i = 1 N ∣ A i − B i ∣ S=\sum_{i=1}^{N}|A_i-B_i| S=i=1NAiBi

猜结论:一定有一种构造B的方案,使得B中的数都在A中出现过。
(我也不会证明)

设列DP方程:
f [ i , j ] f[i,j] f[i,j]表示完成前i个数的构造,且 b [ i ] = j b[i]=j b[i]=j时,S的最小值。
f [ i , j ] = m i n { f [ i − 1 , k ] + ∣ a [ i ] − j ∣ } ( 0 ≤ k ≤ j ) f[i,j]=min \lbrace f[i-1,k]+|a[i]-j| \rbrace(0\leq k \leq j) f[i,j]=min{f[i1,k]+a[i]j}(0kj)
我们根据引理可以离散化f的第二维,且这题与上一题相似, f [ i − 1 ] [ k ] f[i-1][k] f[i1][k]的集合的增加是随着j的增加单调的增加,那么我们也可以 O ( 1 ) O(1) O(1)完成转移。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=2010;
int a[N],b[N];
int f[N][N];//f[i][j]表示把完成前i个数的构造且第Bi=j时的最小代价。
int main()
{
	int n;scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	int m=unique(b+1,b+n+1)-(b+1);
	memset(f,63,sizeof(f)); f[0][0]=0;
	int ans=f[1][0];
	for(int i=1;i<=n;i++)
	{
		int minn=f[i-1][0];
		for(int j=1;j<=m;j++)
		{
			minn=min(minn,f[i-1][j]);
			f[i][j]=minn+abs(a[i]-b[j]);
		}	
	}
	for(int i=1;i<=m;i++)
		ans=min(ans,f[n][i]);
	printf("%d\n",ans);
	return 0;
}

4、[CH5102]Mobile Service
http://contest-hunter.org:83/contest/0x50「动态规划」例题/5102 Mobile Service

DP方程很好设列:
f [ i ] [ x ] [ y ] [ z ] f[i][x][y][z] f[i][x][y][z]前i个请求,三人位置之前分别位于x,y,z最小花费

在第i-1请求完成之后,一定有人会到达 p [ i − 1 ] p[i-1] p[i1],那么我们在计算第i个请求的时候,只需要计算剩下两个人情况。

那么重设 f [ i ] [ x ] [ y ] f[i][x][y] f[i][x][y]表示前i个请求,其他人位置之前分别位于x,y最小花费,则

f [ i ] [ x ] [ y ] = m i n ( f [ i ] [ x ] [ y ] , f [ i − 1 ] [ x ] [ y ] + c [ p [ i − 1 ] ] [ p [ i ] ] ) f[i][x][y]=min(f[i][x][y],f[i-1][x][y]+c[p[i-1]][p[i]]) f[i][x][y]=min(f[i][x][y],f[i1][x][y]+c[p[i1]][p[i]])
f [ i ] [ p [ i − 1 ] ] [ y ] = m i n ( f [ i ] [ p [ i − 1 ] ] [ y ] , f [ i − 1 ] [ x ] [ y ] + c [ x ] [ p [ i ] ] ) f[i][p[i-1]][y]=min(f[i][p[i-1]][y],f[i-1][x][y]+c[x][p[i]]) f[i][p[i1]][y]=min(f[i][p[i1]][y],f[i1][x][y]+c[x][p[i]])
f [ i ] [ x ] [ p [ i − 1 ] ] = m i n ( f [ i ] [ x ] [ p [ i − 1 ] ] , f [ i − 1 ] [ x ] [ y ] + c [ 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]]) f[i][x][p[i1]]=min(f[i][x][p[i1]],f[i1][x][y]+c[y][p[i]])

初始状态设f[0][1][2]=0且p[0]=3表示一开始三人的位置

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=210;
const int Q=1010;
int f[Q][N][N];
int p[Q];
int c[N][N];
/*
f[i][x][y] 前i个请求,其他人位置之前分别位于x,y最小花费
*/
int main()
{
	int n,Q;scanf("%d%d",&n,&Q);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&c[i][j]);
	for(int i=1;i<=Q;i++) scanf("%d",&p[i]);
	memset(f,0x3f,sizeof(f)); 
	int INF=f[0][0][0];
    p[0]=3; f[0][1][2]=0;
	for(int i=1;i<=Q;i++)
	{
		for(int x=1;x<=n;x++)
			for(int y=1;y<=n;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(p[i]!=y && p[i]!=p[i-1])
					f[i][p[i-1]][y]=min(f[i][p[i-1]][y],f[i-1][x][y]+c[x][p[i]]);
				if(p[i]!=x && p[i]!=p[i-1])
					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;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			ans=min(ans,f[Q][i][j]);
	printf("%d\n",ans);
	return 0;	
}

5、[CH5103]传纸条(NOIP2008)
http://contest-hunter.org:83/contest/0x50「动态规划」例题/5103 传纸条

直接开四维 f [ i , j , u , v ] f[i,j,u,v] f[i,j,u,v]表示一条路的末端在点 [ i , j ] [i,j] [i,j]另一条路的末端在点 [ u , v ] [u,v] [u,v]取数之和最大是多少。然后直接就可以转移,判断一下两条路走到一个点上的情况即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=51;
int a[N][N];
int f[N][N][N][N];
int n,m;
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++)
			for(int u=1;u<=n;u++)
				for(int v=1;v<=m;v++)
				{
					int t=0;
					if(i>1 && u>1) t=max(t,f[i-1][j][u-1][v]);
					if(i>1 && v>1) t=max(t,f[i-1][j][u][v-1]);
					if(j>1 && u>1) t=max(t,f[i][j-1][u-1][v]);
					if(j>1 && v>1) t=max(t,f[i][j-1][u][v-1]);	
					if(i==u && j==v) f[i][j][u][v]=t+a[i][j];
					else f[i][j][u][v]=t+a[i][j]+a[u][v];
				}
	printf("%d\n",f[n][m][n][m]);
	return 0;
}

6、[CH5104]I-County
http://contest-hunter.org:83/contest/0x50「动态规划」例题/5104 I-country

一个凸联通块转换为连续的若干行,每行的左端点纵坐标先递减,再递增;右端点纵坐标先递增,再递减
那么我们设 f [ i , j , l , r , x , y ] f[i,j,l,r,x,y] f[i,j,l,r,x,y]表示当前处理的行数i,直到第i行已经选出来的格子数j,当前行的左右端点l,r,以及表示左端点的增减状态x以及右端点的增减状态y(x=0递增,x=1递减,y也是一样)

我们把左端点递减,右端点递增的状态称为扩张;把左端点递增,右端点递减的状态称为收缩。
注意他们不一定一起扩张收缩,也就是说可能左扩右缩。
那么为了保证只完成一次的扩张与收缩且先收缩在再扩张,收缩的状态可能从扩张或者收缩的状态转移过来来,然而扩张只能从扩张的状态转移过来,确定了转移方针我们就可以发现转移很简单,就可以开始愉快的转移啦。

1、左右端点都扩张:
f [ i , j , l , r , 1 , 0 ] = ∑ p = l r a [ i , p ] + { f [ i − 1 , 0 , 0 , 0 , 1 , 0 ] if  j = r − l + 1 m a x { f [ i − 1 , j − ( r − l + 1 ) , p , q , 1 , 0 ] } ( l ≤ p ≤ q ≤ r ) if  j &gt; r − l + 1 f[i,j,l,r,1,0]=\sum_{p=l}^{r}a[i,p]+ \begin{cases} f[i-1,0,0,0,1,0] &amp;\text{if \enspace}j=r-l+1\\ max\lbrace f[i-1,j-(r-l+1),p,q,1,0] \rbrace(l\leq p\leq q \leq r) &amp;\text{if \enspace}j&gt;r-l+1 \end{cases} f[i,j,l,r,1,0]=p=lra[i,p]+{f[i1,0,0,0,1,0]max{f[i1,j(rl+1),p,q,1,0]}(lpqr)if j=rl+1if j>rl+1
第一种情况为当前的[l,r]区间的数已经有我想要的j个了,那么我们直接从j=0的状态转移过来就成,第二种的话很好理解,p,q枚举被继承状态的l,r,j-(r-l+1)表示前i-1个选了多少个。

2、左端点扩张右端点收缩:
f [ i , j , l , r , 1 , 1 ] = ∑ p = l r a [ i , p ] + m a x { f [ i − 1 , j − ( r − l + 1 ) , p , q , 1 , y ] } ( l ≤ p ≤ r ≤ q , y = 0 / 1 ) f[i,j,l,r,1,1]=\sum_{p=l}^{r}a[i,p]+max\lbrace f[i-1,j-(r-l+1),p,q,1,y] \rbrace(l\leq p\leq r \leq q,y=0/1) f[i,j,l,r,1,1]=p=lra[i,p]+max{f[i1,j(rl+1),p,q,1,y]}(lprq,y=0/1)

3、左端点收缩右端点扩张
f [ i , j , l , r , 0 , 0 ] = ∑ p = l r a [ i , p ] + m a x { f [ i − 1 , j − ( r − l + 1 ) , p , q , x , 0 ] } ( p ≤ l ≤ q ≤ r , x = 0 / 1 ) f[i,j,l,r,0,0]=\sum_{p=l}^{r}a[i,p]+max\lbrace f[i-1,j-(r-l+1),p,q,x,0] \rbrace(p\leq l\leq q \leq r,x=0/1) f[i,j,l,r,0,0]=p=lra[i,p]+max{f[i1,j(rl+1),p,q,x,0]}(plqr,x=0/1)

4、左右都收缩
f [ i , j , l , r , 0 , 1 ] = ∑ p = l r a [ i , p ] + m a x { f [ i − 1 , j − ( r − l + 1 ) , p , q , x , y ] } ( p ≤ l ≤ r ≤ q , x = 0 / 1 , y = 0 / 1 ) f[i,j,l,r,0,1]=\sum_{p=l}^{r}a[i,p]+max\lbrace f[i-1,j-(r-l+1),p,q,x,y] \rbrace(p\leq l\leq r \leq q,x=0/1,y=0/1) f[i,j,l,r,0,1]=p=lra[i,p]+max{f[i1,j(rl+1),p,q,x,y]}(plrq,x=0/1,y=0/1)

初始状态为 f [ i , 0 , 0 , 0 , 1 , 0 ] = 0 f[i,0,0,0,1,0]=0 f[i,0,0,0,1,0]=0

这题还需要记录路径,我们可以开一个和dp数组相当的数组来记录每一步走了哪一个状态,然后递归输出。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int f[16][226][16][16][2][2];
int a[16][16],s[16][16];
struct path//记录状态路径 
{
	int i,j,l,r,x,y;	
}pa[16][226][16][16][2][2];
int main()
{	
	int n,m,K; scanf("%d%d%d",&n,&m,&K);
	if(K==0){puts("Oil : 0"); return 0;}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			s[i][j]=s[i][j-1]+a[i][j];
		}
	
	memset(f,128,sizeof(f));
	for(int i=0;i<=n;i++) f[i][0][0][0][1][0]=0;
	
	int ans=0; path t;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=K;j++)
			for(int l=1;l<=m;l++)
				for(int r=l;r<=m;r++)
				{
					if(r-l+1>j) break;
					int sum=s[i][r]-s[i][l-1];
					
					f[i][j][l][r][1][0]=sum;//扩张 
					int mx=f[i-1][0][0][0][1][0];
					pa[i][j][l][r][1][0]=(path){i-1,0,0,0,1,0};
					for(int p=l;p<=r;p++)
						for(int q=p;q<=r;q++)
						{
							if( (q-p+1) + (r-l+1) > j) break;
							if(mx<f[i-1][j-(r-l+1)][p][q][1][0])
							{
								mx=f[i-1][j-(r-l+1)][p][q][1][0];
								pa[i][j][l][r][1][0]=(path){i-1,j-(r-l+1),p,q,1,0};
							}
						}
					f[i][j][l][r][1][0]+=mx;
					
					f[i][j][l][r][1][1]=sum;
					mx=0;//左扩右缩 
					for(int p=l;p<=r;p++)
						for(int q=r;q<=m;q++)
						{
							if( (q-p+1) + (r-l+1) > j) break;
							for(int y=0;y<=1;y++)
								if(mx<f[i-1][j-(r-l+1)][p][q][1][y])
								{
									mx=f[i-1][j-(r-l+1)][p][q][1][y];
									pa[i][j][l][r][1][1]=(path){i-1,j-(r-l+1),p,q,1,y};
								}
						}
					f[i][j][l][r][1][1]+=mx;
					
					f[i][j][l][r][0][0]=sum;
					mx=0;//左缩右扩 
					for(int p=1;p<=l;p++)
						for(int q=l;q<=r;q++)
						{
							if( (q-p+1) + (r-l+1) > j) break;
							for(int x=0;x<=1;x++)
								if(mx<f[i-1][j-(r-l+1)][p][q][x][0])
								{
									mx=f[i-1][j-(r-l+1)][p][q][x][0];
									pa[i][j][l][r][0][0]=(path){i-1,j-(r-l+1),p,q,x,0};
								}
						}
					f[i][j][l][r][0][0]+=mx;
					
					f[i][j][l][r][0][1]=sum;
					mx=0;//收缩 
					for(int p=1;p<=l;p++)
						for(int q=r;q<=m;q++)
						{
							if( (q-p+1) + (r-l+1) > j) break;
							for(int x=0;x<=1;x++)
								for(int y=0;y<=1;y++)
									if(mx<f[i-1][j-(r-l+1)][p][q][x][y])
									{
										mx=f[i-1][j-(r-l+1)][p][q][x][y];
										pa[i][j][l][r][0][1]=(path){i-1,j-(r-l+1),p,q,x,y};
									}
						}
					f[i][j][l][r][0][1]+=mx;
					
					if(j==K)
					{
						for(int x=0;x<=1;x++)
							for(int y=0;y<=1;y++)
							{
								if(ans<f[i][j][l][r][x][y])
								{	
									ans=f[i][j][l][r][x][y];
									t=(path){i,j,l,r,x,y};
								}
							}
					}
				}
	printf("Oil : %d\n",ans);
	for(;t.i;t=pa[t.i][t.j][t.l][t.r][t.x][t.y])
		for(int i=t.l;i<=t.r;i++)
			printf("%d %d\n",t.i,i);
	return 0;
		
}

7、[CH5105]Cookies
http://contest-hunter.org:83/contest/0x50「动态规划」例题/5105 Cookies

容易想到一个贪心:怨气值大的人应拿的饼干尽量多。
那么如何尽量拿的多呢?用dp来解决。

f [ i , j ] f[i,j] f[i,j]表示前i个人已经发了j块饼干的最小怨气和。
首先我们按怨气值从大到小排序,然后假设我们已经处理了i-1个孩子,我们现在要处理第i个孩子,有两种情况:
1、第i个孩子获得的饼干比第i-1个孩子少,那么此时a[i]=i-1(因为前面有i-1个人获得的比i个多)
2、第i个孩子获得的饼干数与第i-1个孩子相同,那么我们就需要知道前面有多少个和第i-1个孩子获得的饼干数相同(这些孩子的编号一定是连续的)才能计算a[i]。

但是如果之直接转移,维护他们的时间代价太大了。那么怎么办呢?

我们可以将状态转移做一个等价代换:
因为相对大小的不不变性,我们可以把第i个孩子分配的饼干数缩放到1,再考虑前面的孩子获得的饼干数相等。
1、若第i个孩子的饼干数大于1,则等价于分配j-i个饼干给前i个孩子,每人少拿一块饼干,这样的话获得的饼干数大小顺序不变,怨气之和也不变。
2、若第i个孩子的饼干数等于1,那么就枚举i前面有多少孩子也能获得1块饼干

转移方程为:
f [ i ] [ j ] = m i n { f [ i , j − 1 ] m i n { f [ k , j − ( i − k ) ] + k ∗ ∑ p = k + 1 i g [ p ] } ( 0 ≤ k &lt; i ) f[i][j]=min \begin{cases} f[i,j-1] \\ min \lbrace f[k,j-(i-k)]+k*\sum_{p=k+1}^{i}g[p]\rbrace(0\leq k&lt;i) \end{cases} f[i][j]=min{f[i,j1]min{f[k,j(ik)]+kp=k+1ig[p]}(0k<i)

因为这题的转移不大直观,所以这题记录路径稍微麻烦。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=30+10;
const int M=5000+10;
struct node
{
	int id,x;
}a[N],pa[N][M];
bool cmp(node a,node b){return a.x>b.x;}
int f[N][M];
int sum[N];
int main()
{
	int n,m;scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int x; scanf("%d",&x);
		a[i]=(node){i,x}; 
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i].x;
	memset(f,127,sizeof(f)); f[0][0]=0;
	for(int i=1;i<=n;i++)
		for(int j=i;j<=m;j++)
		{
			if(f[i][j]>f[i][j-i])//大于1块饼干
			{
				f[i][j]=f[i][j-i]; 
				pa[i][j]=(node){i,j-i};
			}
			int s=sum[i];
			for(int k=0;k<i;k++)//只有1块饼干
			{
				s-=a[k].x;
				if(f[i][j]>f[k][j-(i-k)]+k*s)
				{
					f[i][j]=f[k][j-(i-k)]+k*s; 
					pa[i][j]=(node){k,j-(i-k)};
				}
			}
		}
	printf("%d\n",f[n][m]);
	
	int ans[N]; memset(ans,0,sizeof(ans));
	node now=(node){n,m};
	while(now.id && now.x)
	{
		node last=pa[now.id][now.x];
		if(last.id==now.id)//上一个等于这一个说明这一个大于1块饼干,所有人一齐减了1
			for(int i=1;i<=last.id;i++)
				ans[a[i].id]++;
		else//不等于说明这个人只有1块饼干,只有这一段减了1 
			for(int i=last.id+1;i<=now.id;i++)
				ans[a[i].id]++;
		now=last;
	}
	for(int i=1;i<=n;i++) 
		printf("%d ",ans[i]);
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值