在这片梦想之地,不堪回首的过去像泡沫一样散去,不愿面对的明天也永远不会到来,|

PassName

园龄:3年1个月粉丝:32关注:16

[动态规划] 线性 dp

线性 dp

SP15637 GNYR04H

按照编号从小到大摆放所有人

    1. 每个人都只能放在已经存在的某个人的后面
    1. (除第一行外)任何一行的人数都不能比后一行多

状态表示:f[a][b][c][d][e] 表示第一行 a 个人,第二行 b 个人,...,第五行 e 个人的合法方案数

然后在每个状态下枚举每一行,如果某一行能放入一个人,那么就将当前状态的方案数累加到放入一个人的新方案的方案数中。

总结:设计动态规划的转移方程,不一定要以“如何计算出一个状态”的形式给出,也可以考虑“一个已知状态应该更新哪些后续阶段的未知状态”。

int n, f[N][N][N][N][N];

signed main()
{
    while (cin >> n and n != 0)
    {
        int s[5] = {0};
        for (rint i = 0; i < n; i++) cin >> s[i];
        memset(f, 0, sizeof f);
        f[0][0][0][0][0] = 1;
        for (rint a = 0; a <= s[0]; a++)
          for (rint b = 0; b <= s[1]; b++)
            for (rint c = 0; c <= s[2]; c++)
              for (rint d = 0; d <= s[3]; d++)
                for (rint e = 0; e <= s[4]; e++)
                {
                    int &x = f[a][b][c][d][e];
                    if (a && a - 1 >= b) x += f[a - 1][b][c][d][e];
                    if (b && b - 1 >= c) x += f[a][b - 1][c][d][e];
                    if (c && c - 1 >= d) x += f[a][b][c - 1][d][e];
                    if (d && d - 1 >= e) x += f[a][b][c][d - 1][e];
                    if (e) x += f[a][b][c][d][e - 1];
                }
        cout << f[s[0]][s[1]][s[2]][s[3]][s[4]] << endl;
    }
    return 0;
}

AcWing 272. 最长公共上升子序列

f[i][j] 表示 a[1]~a[i]b[1]~b[j] 可以构成的以 b[j] 结尾的最长公共上升子序列的长度。

a[i]b[j] 时,有

f[i][j]=f[i1][j]

a[i]=b[j] 时,有

f[i][j]=max0k<j,bk<ai{f[i1][k]}+1

在转移过程中,把满足 0k<j,bk<aik 构成的集合称为 f[i][j] 进行状态转移时的决策集合,记为 S(i,j)。注意到,在第二层循环 j1 增加到 m 事,第一层循环 i 是一个定值,这使得条件 bk<ai 是固定的。因此,当变量 j 增加 1 时,k 的取值范围从 0k<j 变为 0k<j+1,即整数 j 可能会进入新的决策集合。

aibj

S(i,j+1)=S(i,j)

反之则有

S(i,j+1)=S(i,j){j}

int n;
int a[N], b[N];
int f[N][N];
//表示 a[1]~a[i] 与 b[1]~b[j] 可以构成的以 b[j] 结尾的最长公共上升子序列的长度。

signed main()
{
    cin >> n;
    a[0] = b[0] = -inf; 
    for (rint i = 1; i <= n; i++) cin >> a[i];
    for (rint i = 1; i <= n; i++) cin >> b[i];

    for (rint i = 1; i <= n; i++)
    {
        int val = 1; 
		//val 是决策集合 S(i, j) 中 f[i - 1][k] + 1 的最大值
        for (rint j = 1; j <= n; j++)
        {
            if (a[i] == b[j]) f[i][j] = val; 
			//f[i][j] = f[i - 1][j] + 1
            else f[i][j] = f[i - 1][j];
            //j 即将变成 j + 1,如果满足 a[i] > b[j],则将 j 加入新的决策集合
            if(a[i] > b[j]) val = max(val, f[i - 1][j] + 1); //更新新的决策集合中 f[i - 1][k] + 1 的最大值
        }
    }

    int ans = 0; 
    for (rint i = 1; i <= n; i++) ans = max(ans, f[n][i]); 
    cout << ans << endl;

    return 0;
}

对于决策集合中的元素只僧增多不减少的情景,就可以维护一个变量来记录决策集合的当前信息,避免重复扫描降低复杂度。

P2893 Making the Grade G

构造出的序列 b 需要满足单调性,可以是单调递增也可以是单调递减,这里只需要正着反着各算一次取最小即可。

引理:在满足 s 最小的前提下,一定存在一种构造方案,使得序列 b 中的所有数值都在序列 a 中出现过。

依次考虑在完成前 i 个数的构造时 s 的最小值。因此我们可以按照
长度从 1 ~ n 来对已经处理完的前缀序列进行转移,在转移的过程中,为了确保单调性,还需要知道 b[i1] 的取值。

为了能在转移的同时知道 b[i1] 的取值,我们可以直接用二维的状态来转移。

f[i][j] 表示完成前 i 个数的构造,其中 b[i]=j 时,s 的最小值。

由此得出状态转移方程

f[i][j]=min0kj{f[i1][k]+|aij|}

对序列 a 中出现的数进行离散化,把 dp 状态中第二维 j 的范围降低到 O(n)。本题的转移和上一题非常相似,决策集合只多不少,O(1) 即可实现转移,时间复杂度 O(n2)

int n;
int a[N], b[N]; 
//原序列、离散化序列
int f[N][N]; 
//示完成前 i 个数的构造,其中 b[i] = j 时,s 的最小值

int work()
{
    for (rint i = 1; i <= n; i++) b[i] = a[i]; 
	//将原序列拷贝到离散化序列中
    sort(b + 1, b + 1 + n); 

    for (rint i = 1; i <= n; i++)
    {
        int val = inf; 
		//val 表示所有 f[i - 1][k] 的最小值
        for (rint j = 1; j <= n; j++) //枚举第 i 位的取值
        {
            val = min(val, f[i - 1][j]); 
			//决策集合加入新的 j,需要更新最小的 f[i - 1][k]
            f[i][j] = val + abs(a[i] - b[j]); 
			//状态转移方程 ( j 是离散化后的下标,b[j] 才是要选的值 )
        }
    }

    int ans = inf; 
	//记录最小值 s
    for (rint i = 1; i <= n; i++) ans = min(ans, f[n][i]); 
	//从第 n 位的所有取值中取最小值
    return ans;
}

signed main()
{
    cin >> n;
    for (rint i = 1; i <= n; i++) cin >> a[i];
    int res1 = work(); //构造递增序列的最小值
    reverse(a + 1, a + 1 + n); //将序列颠倒
    int res2 = work(); //构造递减序列的最小值
    cout << min(res1, res2) << endl; //两种情况取最小值
    return 0;
}

SP703 SERVICE

可以发现本题 dp 的 "阶段" 就是 "已经完成的请求数量",通过指派一个员工,可以把一个 "完成 i1 个请求" 的状态
转移到 "完成 i 个请求" 的状态。

为了计算指派员工的花费,在转移时我们还需要知道每个服务员的位置,由于员工的数量不多,因此最直接的方法就是将
三个员工的位置也存储在每个状态中。

f[i][x][y][z] 表示已经完成了第 i 个请求,三个员工分别在 x,y,z 时的最小花费。

我们只需要考虑 f[i][x][y][z] 能更新哪些状态,很显然, 能更新的状态只有三种,即指派哪个员工去完成请求:

p[i] 表示第 i 个请求所在的位置

f[i+1][p[i+1]][y][z]=minf[i+1][p[i+1]][y][z],f[i][x][y][z]+c(x,p[i+1])

f[i+1][x][p[i+1]][z]=minf[i+1][x][p[i+1]][z],f[i][x][y][z]+c(y,p[i+1])

f[i+1][x][y][p[i+1]]=minf[i+1][x][y][p[i+1]],f[i][x][y][z]+c(z,p[i+1])

可以发现,在第 i 个请求完成时,一定有某个员工位于 p[i],我们只需要记录另外两个员工的位置即可。由此能降一维。

f[i][x][y] 表示已经完成了第 i 个请求,三个员工分别在 p[i],x,y 时的最小花费。

根据和上面类似的方式,这里同样有三种更新状态。

f[i+1][x][y]=minf[i+1][x][y],f[i][x][y]+c(p[i],p[i+1])

f[i+1][p[i+1]][y]=minf[i+1][p[i]][y],f[i][x][y]+c(x,p[i+1])

f[i+1][x][p[i+1]]=minf[i+1][x][p[i]],f[i][x][y]+c(y,p[i+1])

还需要定义初始状态,最开始三个员工分别在 1,2,3,不妨设 p[0]=3,那么初值可设 f[0][1][2]=0,目标为 f[n][?][?]

注意,本题要求同一位置不能出现两个员工,所以转移的过程中还需要特判状态的合法性。

总结:

  • 1.若阶段不足以表示一个状态,可以把所需的附加信息也作为状态的维度

  • 2.要选择最小的能够覆盖整个状态空间的维度。

int n, m;
int d[N][N]; 
int p[M]; 
//p[i] 表示第 i 个请求所在的位置
int f[M][N][N]; 
//f[i][x][y] 表示已经完成了第 i 个请求,三个员工分别在 p[i], x, y 时的最小花费。

signed main()
{
	int T;
	cin >> T;
	while (T--)
	{
	    cin >> n >> m;
	    memset(d, 0, sizeof d);
	    memset(p, 0, sizeof p);
	    for (rint i = 1; i <= n; i++)
	        for (rint j = 1; j <= n; j++)
	            cin >> d[i][j];
	    for (rint i = 1; i <= m; i++) cin >> p[i];
	    memset(f, 0x3f, sizeof f);
	    p[0] = 3;
	    f[0][1][2] = 0;
	    for (rint i = 0; i < m; i++)
	    {
	        for (rint x = 1; x <= n; x++)
	        {
	            for (rint y = 1; y <= n; y++)
	            {
	                int z = p[i], u = p[i + 1];
	                if(x == z || x == y || y == z) continue; 
	                f[i + 1][x][y] = min(f[i + 1][x][y], f[i][x][y] + d[z][u]);
	                f[i + 1][z][y] = min(f[i + 1][z][y], f[i][x][y] + d[x][u]);
	                f[i + 1][x][z] = min(f[i + 1][x][z], f[i][x][y] + d[y][u]);
	            }				
			}
		}
	    //从所有完成请求的方案中取花费最小值
	    int res = inf;
	    for (rint x = 1; x <= n; x++) //枚举完成所有请求时剩下两个员工所在的位置
	    {
	        for (rint y = 1; y <= n; y++)
	        {
	            int z = p[m];
	            if(x == z || x == y || y == z) continue; 
	            res = min(res, f[m][x][y]); 
	        }		
		}
	    cout << res << endl;	
	}
    return 0;
}

P1006 传纸条

按照每一步来看,本题的每个位置都是一个二维坐标,因此考虑每个状态用两个二维坐标表示,得出初步的状态表示。

f[x1][y1][x2][y2] 表示第一条路径走到 (x1,y1),第二条路经走到 (x2,y2) 时的最大价值

但是这里有个问题,我们没法限制每条路径的长度,因为两条路径是同时走的,我们没法保证两个路径同时到终点,而且两条路径可能存在重合,我们没法保证两条路径不会重合。

因此还需要加入一个限制条件,可以发现限制条件就是长度,保证当前状态两条路径都只走了 i 步。

但是这样就有 5 个属性需要存储,五维状态过于复杂,我们可以挖掘一下其中的性质来进行优化。

由于两条路径当前都只走了 i 步,因此 x1+y1=x2+y2=i,即 y1=ix1,y2=ix2

可以发现,由于只能往右走或往下走,所以我们只需要知道横坐标或纵坐标,另一个坐标可以直接计算得出,因此可以降两维。

f[i][x1][x2] 表示第一条路径走到 (x1,ix1),第二条路经走到 x2,ix2) 时的最大价值

每条路径都有向右、向下两种走法,所以每个状态有四种转移方式,

w[x][y] 表示 (x,y) 上的物品价值

f[i][x1][x2]=maxf[i][x1][x2],f[i1][x11][x21]+w

f[i][x1][x2]=maxf[i][x1][x2],f[i1][x11][x2]+w

f[i][x1][x2]=maxf[i][x1][x2],f[i1][x1][x21]+w

f[i][x1][x2]=maxf[i][x1][x2],f[i1][x1][x2]+w

最开始两个路径都从 (1,1) 开始,因此初始状态为 f[2][1][1]=0,结束状态为 f[n+m][n][n]

int n, m;
int w[N][N]; 
//w[i][j] 表示 (i, j) 位置上的价值
int f[N + N][N][N]; 
//f[i][x1][x2] 表示第一条路径走到 (x1, i - x1),第二条路经走到 (x2, i - x2) 时的最大价值

signed main()
{
    cin >> n >> m;
    for (rint i = 1; i <= n; i++)
        for (rint j = 1; j <= m; j++)
            cin >> w[i][j];

    memset(f, -0x3f, sizeof f);
    f[2][1][1] = 0;

    for (rint i = 2; i <= n + m; i++)
    {
        for (rint x1 = 1; x1 <= n; x1++)
        {
            for (rint x2 = 1; x2 <= n; x2++)
            {
                int y1 = i - x1, y2 = i - x2;
                if (y1 < 1 || y1 > m || y2 < 1 || y2 > m) continue; 
                int t = w[x1][y1];
                if (x1 != x2 || i == 2 || i == n + m) 
				//除了起点和终点,其他时候两条路径不能走到同一个格子上
                {
                    t += w[x2][y2];
                    int &x = f[i][x1][x2];
                    //状态转移方程
                    x = max(x, f[i - 1][x1 - 1][x2 - 1] + t);
                    x = max(x, f[i - 1][x1 - 1][x2] + t);
                    x = max(x, f[i - 1][x1][x2 - 1] + t);
                    x = max(x, f[i - 1][x1][x2] + t);
                }
            }			
		}		
	}

    cout << f[n + m][n][n] << endl;

    return 0;
}

AcWing 276. I-区域

从上往下看它的左端点下标先递减后递增右端点下标先递增后递减, 从上往下看,它的左端点下标先递减后递增,右端点下标先递增后递减。

f[i,j,l,r,0/1,0/1] 表示当前为第 i 行,已经用了 j 个格子,凸连通块在第 i 行的左端点为 l,右端点为 r,左端点现在在递增 (0) 还是递减 (1),右端点在递增 (0) 还是递减 (1)。那么可以列出状态转移方程:

len=rl+1

f[i][j][l][r][1][0]=p=lra[i][p]+maxlpqr{f[i1][jlen][p][q][1][0]}

f[i][j][l][r][1][1]=p=lra[i][p]+maxlprq{max0y1{f[i1][jlen][p][q][1][y]}}

f[i][j][l][r][0][0]=p=lra[i][p]+maxplqr{max0x1{f[i1][jlen][p][q][x][0]}}

f[i][j][l][r][0][1]=p=lra[i][p]+maxplrq{max0x1{max0y1{f[i1][jlen][p][q][x][y]}}}

目标状态:max{f(i,K,l,r,x,y)}

时间复杂度为 O(NM4K)

int n, m, k;
int a[N][N], s[N][N]; 
//a 表示原数组,s 表示每一行的前缀和数组
// f[i][j][l][r][x][y] 表示前 i 行选择了 j 个格子,其中第 i 行选择了第 l 到 r 个格子(若不选均为 0),
// 左边界的单调性类型为 x,右边界的单调性类型为 y(0 表示递增,1 表示递减)时,能构成的凸连通块的最大权值和。
int f[N][M][N][N][2][2];
int res;
//记录最大价值
//pre 记录所有状态的前驱状态,ans 记录最优的结束状态
struct Pre
{
    int i, j, l, r, x, y;
} pre[N][M][N][N][2][2], ans;
// f[i][j][l][r][x][y] 表示当前需要更新的状态
// f[i - 1][j - (r - l + 1)][L][R][X][Y] 表示当前状态的前驱状态
// w 表示当前转移过来的价值

void turn(int i, int j, int l, int r, int x, int y, int L, int R, int X, int Y, int w)
{
    if (w <= f[i][j][l][r][x][y]) return; 
	//只有 > 记录的价值才需要更新
    
	//更新
    f[i][j][l][r][x][y] = w;
    pre[i][j][l][r][x][y] = {i - 1, j - (r - l + 1), L, R, X, Y};
}

void print(Pre t) 
//从结束状态递归回推整个方案
{
    if (!t.j) return; //从结束状态回推到没选中格子的状态(起始状态)即可结束
    print(pre[t.i][t.j][t.l][t.r][t.x][t.y]); //往回递推
    //输出当前层选中的所有格子
    for (rint i = t.l; i <= t.r; i++) printf("%lld %lld\n", t.i, i);
}

signed main()
{
    cin >> n >> m >> k;
    for (rint i = 1; i <= n; i++)
    {
        for (rint j = 1; j <= m; j++)
        {
            cin >> a[i][j];
            s[i][j] = s[i][j - 1] + a[i][j]; 
        }		
	}
	
    memset(f, -0x3f, sizeof f);
    for (rint i = 0; i <= n; i++) f[i][0][0][0][1][0] = 0;

    for (rint i = 1; i <= n; i++)
      for (rint j = 1; j <= k; j++)
        for (rint l = 1; l <= m; l++)
          for(rint r = l; r <= m; r++)
          {
            if(r - l + 1 > j) break; 
			//当前行选的格子数 > 选中的总格子数,不合法的状态直接跳过

            int w = s[i][r] - s[i][l - 1]; 
			//记录当前行选中 l ~ r 能获得的总价值

            //状态 1 左边界递减,右边界递增(左、右边界都扩张)
            //状态 1.1 j == r - l + 1
            if (j == r - l + 1) 
			{
				f[i][j][l][r][1][0] = w + f[i - 1][0][0][0][1][0]; 
				//f[i - 1][0][0][0][1][0] = 0
			}
            else //状态 1.2 j > r - l + 1
            {
                for (rint p = l; p <= r; p++)
                  for (rint q = p; q <= r; q++)
                    turn(i, j, l, r, 1, 0, p, q, 1, 0, w + f[i - 1][j - (r - l + 1)][p][q][1][0]);
            }

            //状态 2 左边界递减,右边界递减(左边界扩张,右边界收缩)
            for (rint p = l; p <= r; p++)
                for (rint q = r; q <= m; q++)
                {
                    turn(i, j, l, r, 1, 1, p, q, 1, 0, w + f[i - 1][j - (r - l + 1)][p][q][1][0]);
                    turn(i, j, l, r, 1, 1, p, q, 1, 1, w + f[i - 1][j - (r - l + 1)][p][q][1][1]);
                }

            //状态 3 左边界递增,右边界递增(左边界收缩,右边界扩展)
            for (rint p = 1; p <= l; p++)
                for (rint q = l; q <= r; q++)
                {
                    turn(i, j, l, r, 0, 0, p, q, 0, 0, w + f[i - 1][j - (r - l + 1)][p][q][0][0]);
                    turn(i, j, l, r, 0, 0, p, q, 1, 0, w + f[i - 1][j - (r - l + 1)][p][q][1][0]);
                }

            //状态 4 左边界递增,右边界递减(左、右边界都收缩)
            for (rint p = 1; p <= l; p++)
                for (rint q = r; q <= m; q++)
                {
                    turn(i, j, l, r, 0, 1, p, q, 0, 0, w + f[i - 1][j - (r - l + 1)][p][q][0][0]);
                    turn(i, j, l, r, 0, 1, p, q, 0, 1, w + f[i - 1][j - (r - l + 1)][p][q][0][1]);
                    turn(i, j, l, r, 0, 1, p, q, 1, 0, w + f[i - 1][j - (r - l + 1)][p][q][1][0]);
                    turn(i, j, l, r, 0, 1, p, q, 1, 1, w + f[i - 1][j - (r - l + 1)][p][q][1][1]);
                }

                //更新并记录最大价值,以及最大价值对应的状态(结束状态)
                if (j == k) //所有选中 k 个格子的状态都是结束状态
                {
                    for (rint x = 0; x < 2; x++)
                      for (rint y = 0; y < 2; y++)
                        if(res < f[i][k][l][r][x][y])
                        {
							res = f[i][k][l][r][x][y];
							ans = {i, k, l, r, x, y};
						}
                            
                    }
                }

    printf("Oil : %lld\n", res);
    print(ans);

    return 0;
}

AcWing 277. 饼干

贪心的角度出发,贪婪度大的孩子应该获得更多的饼干,才能使整体的怨气总和变得更小,这一点用邻项交换的方法能证明。因此可以降所有孩子按照贪婪值从大到小排序,这样他们分配到的饼干数就是单调递减的。根据这个性质,我们能够更好的维护 a[i],即分到的饼干比第 i 个孩子多的人数,便于我们计算每个孩子的怨气

f[i][j] 表示前 i 个孩子一共分配了 j 块饼干时,他们的怨气总和最小值

考虑第 i+1 个孩子分到的饼干数量,以此来进行状态转移,转移时共有两种情况。

    1. i+1 个孩子获得的饼干数比第 i 个孩子少,此时由于每个孩子分到的饼干数时递减的,所以前 i 个孩子分到的饼干数都比第 i+1 个孩子多,而后面的所有孩子分到的饼干数量都不会大于第 i+1 个孩子,因此此时 a[i+1]=i,即有 i 个人分到的饼干比第 i+1 个孩子多。
    1. i+1 个孩子获得的饼干数和第 i 个孩子相同,此时还需要知道 i 前面有几个孩子与 i 获得的饼干数也相同,才能计算出 a[i+1]

无论是以上哪种情况,我们都需要知道第 i 个孩子获得的饼干数,以及 i 前面有多少个孩子与 i 获得的饼干数相同,但是目前的 dp 状态难以维护这两个信息。

这里我们不妨对状态做一个等价转换。

    1. 若第 i 个孩子获得的饼干数大于 1,则等价于分配 ji 个饼干给前 i 个孩子,即每人少拿一块饼干,这样所有人获得的饼干数之间的相对大小关系没有发生改变,从而怨气总和也不变。
    1. 若第 i 个孩子获得的饼干数为 1,由于每个孩子必须有一块饼干,因此不能再减少,这时则需要枚举 i 前面有多少个孩子也获得 1 块饼干。

通过这样一个等价转换的方式得出整个算法的状态转移方程:

f[i][j]=min(f[i][ji],min0k<i{f[k][j(ik)]+kp=k+1ig[p]})

最开始没有分配一个饼干,因此起始状态为 f[0][0]=0,结束状态就是将 m 个饼干分给 n 个小孩,即 f[n][m]

这题启发我们可以用额外的算法确定 dp 状态的计算顺序,还可以在状态空间中运用等效的方法对状态进行缩放,使得需要计算的问题得到非常大的简化,更容易维护和转移。

int n, m;
//g[i] 表示第 i 个小孩的贪婪度
//c[i] 表示贪婪度第 i 大的小孩在原序列中排第 c[i] 个
//s[i] 表示 c[i] 的前缀和
int g[N], c[N], s[N];
int f[N][M]; 
//f[i][j] 表示前 i 个孩子一共分配了 j 块饼干时,他们的怨气总和最小值
pair<int, int> pre[N][M]; 
//pre[i][j] 记录 f[i][j] 的前驱状态
int res[N]; 
//res[i] 表示最优方案中第 i 个孩子分到的饼干数

bool cmp(int a, int b) //比较函数,按照贪婪度从大到小排序
{
    return g[a] > g[b];
}

void print(pair<int, int> t) 
//从结束状态回推整个方案
{
    int a = t.first, b = t.second;
    if (!a) return; 
	//如果回推完每个人的饼干数,结束
    print(pre[a][b]); //从当前状态继续回推方案

    /*
    如果当前状态和前一个状态分糖果的人数相同,说明是 f[i][j] = f[i][j - i],
    这种情况在转移的过程中我们进行了前 i 个孩子都少一块饼干的等价转换,所以
    回推的时候需要往回转换,即前 i 个孩子增加一块饼干
    */
    if (pre[a][b].first == a) for(rint i = 1; i <= a; i++) res[c[i]]++;
    else
    {
        /*
        否则说明前一个状态到当前状态的中间这段小孩都是相同饼干数,由于递归进
        去再出来,所以这一段在整个序列的最末尾,这一段小孩的饼干数对当前状态
        来说可以取最小,即取 1
        */
        for (rint i = pre[a][b].first + 1; i <= a; i++) res[c[i]] = 1;
    }
}

signed main()
{
    cin >> n >> m;
    for (rint i = 1; i <= n; i++) cin >> g[i];
    for (rint i = 1; i <= n; i++) c[i] = i;
    sort(c + 1, c + 1 + n, cmp); 
    for (rint i = 1; i <= n; i++) s[i] = s[i - 1] + g[c[i]];
    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;

    for (rint i = 1; i <= n; i++)
    {
        for (rint j = i; j <= m; j++) //每个小孩至少一个饼干,前 i 个小孩至少 i 个饼干
        {
            //状态 1
            f[i][j] = f[i][j - i];
            pre[i][j] = {i, j - i};

            //状态 2
            for (rint k = 0; k < i; k++) 
			//枚举有多少个小孩的饼干数 > 第 i 个小孩
                if(f[i][j] > f[k][j - (i - k)] + (s[i] - s[k]) * k)
				//如果当前状态的怨气总和能更小
                {
                    f[i][j] = f[k][j - (i - k)] + (s[i] - s[k]) * k;
                    pre[i][j] = {k, j - (i - k)};
                }
        }		
	}

    cout << f[n][m] << endl; 
    print({n, m}); 
    for (rint i = 1; i <= n; i++) cout << res[i] << " "; 
    return 0;
}

本文作者:PassName

本文链接:https://www.cnblogs.com/spaceswalker/p/18185822

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   PassName  阅读(7)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起