AcWing271. 杨老师的照相排列

思路

这是一个计数的题目,如果乱考虑,肯定会毫无头绪,所以我们从1号到最后一个依次进行安排。

经过反复实验,发现两个规律

  1. 每一行的同学必须是从左向右依次连续放置。(这样状态表示仅仅需要每一行的人数就行了)
  2. 下一行的人数不能多余上一行

有两种思考方式:

  1. lyd思考方式:

    现在已经安排好学生了,dp[a][b][c][d][e]是按照这种方法安排的总的方案数。然后往以后的情况推倒。
  2. y总思考方式:

    思考怎样才能推倒到现在的情况。

时间复杂度分析:

第一排,第二排……第五排的人数乘起来。

\[\frac{a+b+c+d+e}{5} \ge ^5\sqrt{a*b*c*d*e}\\
当且仅当全部相等时才取等号
\]

所以状态总数为\(7^5 = 16807\)(因为还要从0开始计算!!!)

代码实现

版本一(这一个版本我不太好理解)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[32][32][32][32][32];
ll N[8];
void solve(int n)
{
memset(dp, 0, sizeof(dp));
memset(N, 0, sizeof(N));
for(int i = 1; i<= n; i++) scanf("%lld", N+i);
dp[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]) dp[a1+1][a2][a3][a4][a5] += dp[a1][a2][a3][a4][a5];
if(a2<N[2] && a2 < a1) dp[a1][a2+1][a3][a4][a5] += dp[a1][a2][a3][a4][a5];
if(a3<N[3] && a3 < a2) dp[a1][a2][a3+1][a4][a5] += dp[a1][a2][a3][a4][a5];
if(a4<N[4] && a4 < a3) dp[a1][a2][a3][a4+1][a5] += dp[a1][a2][a3][a4][a5];
if(a5<N[5] && a5 < a4) dp[a1][a2][a3][a4][a5+1] += dp[a1][a2][a3][a4][a5];
}
printf("%lld\n", dp[N[1]][N[2]][N[3]][N[4]][N[5]]);
}
int main()
{
int n;
while(scanf("%lld", &n) && n) solve(n);
return 0;
}

版本二(y总版本+注释)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[32][32][32][32][32];
ll N[8];
void solve(int n)
{
memset(dp, 0, sizeof(dp));
memset(N, 0, sizeof(N));
for(int i = 1; i<= n; i++) scanf("%lld", N+i);
dp[0][0][0][0][0] = 1;//以这种情况为初始情况的只有dp[1][0][0][0][0].
for(ll a1 = 0; a1 <= N[1]; a1++)
for(ll a2 = 0; a2 <= min(N[2], a1); a2++)//有一个取min,是只遍历合法的空间
for(ll a3 = 0; a3 <= min(N[3], a2); a3++)
for(ll a4 = 0; a4 <= min(N[4], a3); a4++)
for(ll a5 = 0; a5 <= min(N[5], a4); a5++)
{
ll &v = dp[a1][a2][a3][a4][a5];
if(a1 && a2<=a1-1) v+= dp[a1-1][a2][a3][a4][a5];//注意合理性检验
//当某一行不为0的时候,才可能是最后一个人是从这一行增加的
//在把某一行删除之后,要考虑所得到的集合是不是满足探索到的规律
if(a2 && a3<=a2-1) v+= dp[a1][a2-1][a3][a4][a5];
if(a3 && a4<=a3-1) v+= dp[a1][a2][a3-1][a4][a5];
if(a4 && a5<=a4-1) v+= dp[a1][a2][a3][a4-1][a5];
if(a5) v+= dp[a1][a2][a3][a4][a5-1];
} printf("%lld\n", dp[N[1]][N[2]][N[3]][N[4]][N[5]]);
}
int main()
{
int n;
while(scanf("%lld", &n), n) solve(n);
return 0;
}

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

输入样例:

4
2 2 1 3
2 1 2 3

输出样例:

2

思路

状态表示

我们可以仿照最长公共子序列,设置状态f[i][j]来表示A[1~i]B[1~j]的最长上升子序列。但是由于上升子序列还需要知道最后一个值是怎么,所以不妨设状态

f[i][j]来表示A[1~i]B[1~j]的,以B[j]为结尾的最长公共上升子序列。

状态的属性是MAX(max可以重复求,但是一定不能遗漏)

状态转移

y总 认为:一定要考虑last进行划分!

初步划分为两种情况:包含A[i]这一个元素 和 不包含A[i]这一个元素。

对于第一种情况,显然是f[i-1][j]

对于第二种情况,就显得有一些麻烦(需要划分倒数第二个相同元素究竟还是哪一个)

for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
dp[i][j] = dp[i-1][j];//第一种情况
int maxv = 1;//如果没有倒数第二个
if(a[i] == b[j])//注意第二种情况是有条件的
{
for(int k = 1; k <= j-1; k++)
{
if(b[k] < b[j])//这是第二种情况的第二个条件
maxv = max(maxv, dp[i-1][k]+1);
}
dp[i][j] = max(dp[i][j], maxv);
}
}

y总可以AC,但我是超时

仔细考虑循环,现在是在第二层循环里面,所以i是不变,而a[i] == b[j],所以b[j]也是不变的。

同时,现在在第二层循环,a[i]是不变的。

所以第三层循环其实是已经知道答案了,还像苍蝇一样再遍历一遍。

代码实现

#include <bits/stdc++.h>
using namespace std;
#define N 3020
int a[N], b[N];
int dp[N][N];
int main()
{
int n;
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);
for(int i = 1; i <= n; i++)
{
int maxv = 1;
for(int j = 1; j <= n; j++)
{
dp[i][j] = dp[i-1][j];
if(a[i] == b[j])
{
dp[i][j] = max(dp[i][j], maxv); }
if(b[j] < a[i]) maxv = max(maxv, dp[i][j]+1);//注意maxv的含义:第二种情况下的dp值
}
}
int ans = 0;
for(int i = 1; i<= n; i++)
ans = max(ans, dp[n][i]);
cout << ans;
return 0;
}

AcWing273. 分级

输入样例:

7
1
3
2
4
5
3
9

输出样例:

3

思路

这一道题目我想了比较长的时间,理解的不够到位。

一、对B的元素总是在A中的理解

这个是y总的笔记。解释:

  1. \(A_1,A_2...A_5\)表示的是原始的A数列。
  2. \(A'_1,A'_2...A'_5\)表示的是按照从小到大排序之后的A序列。
  3. B与哪一个A对齐就代表这一个B与这一个A是匹配的
  4. B的纵坐标代表了B与排好序的A序列中的元素值的大小对比。

现在要证明:总是存在一种最优解,使得在这组最优解里面,对于任意一个\(x\in B\),总有\(x\in A\)。|

通俗一点讲,就是B是A的子集。

选取一个区间,(粉色框框里面)

假设这些 \(B_i\)所对应的 \(A_i\)的值 有x个值小于等于\(A'_i\),有 y个值大于等于\(A'_{i+1}\),分三种情况进行构造:

x > y ,说明如果将粉色框整体向下移动,直到最小的红点等于 ,此操作保证使整体结果更优。

x < y,则整体向上移动,使得最大的等于 ,可以使得总和变小。

x == y ,则上移下移都一样,总和不会改变。

引用自https://fangkaipeng.com/?p=1111[1]

通过上面这种方法在三个点中消去一个点之后,再使用同样的方法想去剩余的两个点

问题:为什么不可以一个点一个点来进行考虑呢?

因为B序列是单调递增的。如果在上述的三个点中,\(B_2\ge A'_2\),并且\(B_3\le A'_1\)那么就如果单点考虑,那么我把\(B_2\)往上移动,\(B_3\)往下移动,B序列就不再是非严格单调递增的了。

这样,B的取值就发生了离散,仅仅有A中拍好序的元素一样多。

二、闫氏动态规划的分析方法

可以有一个数组,f[i][j]表示所有从第一位到第i位,已经被匹配好,并且第i为是\(A'_i\)的集合。

属性是sum的最小值

转移方程:

考虑上一位f[i-1][1],f[i-1][2],f[i-1][3].....f[i-1][j]

挑一个最小的 f[i][j] = min+abs(j-A[i])

代码实现

#include <bits/stdc++.h>
using namespace std;
#define N 2010
int a[N];
int n;
int b[N];
int dp[N][N];
int work()
{
for(int i = 1; i<= n; i++) b[i] = a[i];
sort(b+1, b+1+n); for(int i = 1; i<= n; i++)
{
int minv = 0x3f3f3f3f;
for(int j = 1; j <= n; j++)
{
minv = min(minv, dp[i-1][j]);
dp[i][j] = minv+abs(b[j]-a[i]);
}
} int ans = 0x3f3f3f3f;
for(int i = 1; i <= n; i++)
{
ans = min(ans, dp[n][i]);
}
return ans;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", a+i); int ans = work(); reverse(a+1, a+1+n);//递减的反过来求一下就行了 ans = min(ans, work()); printf("%d", ans);
return 0;
}

AcWing\274. 移动服务

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

输入格式

第 1行有两个整数 L,N,其中 L 是位置数量,N 是请求数量,每个位置从 1 到 L编号。

第 2 至 L+1 行每行包含 L 个非负整数,第 i+1 行的第 j 个数表示 c(i,j),并且它小于 2000。最后一行包含 N个整数,是请求列表。一开始三个服务员分别在位置 1,2,3。

输出格式

输出一个整数 M,表示最小花费。

数据范围

3≤L≤200,1≤N≤1000

输入样例:

5 9
0 1 1 1 1
1 0 2 3 2
1 1 0 4 1
2 1 5 0 1
4 2 3 4 0
4 2 4 1 5 4 3 2 1

输出样例:

5

思路

DP的本质就是暴力搜索的优化,所以不需要尽力去找特别优秀的状态标示

状态表示:f[i][x][y][z]i表示已经处理了第i个位置的请求。

x,y,z分别表示三个工作人员所在的位置。

但是很明显量太过于大!

容易联想到:当完成了某一个任务的时候,必定有一个人在第i个请求的发生地。

所以可以压缩到三个状态。

状态转移

前提:按照拓扑序来进行!!!

  1. 从某一个点扫描他会影响到的点(在这一个题目中,一个点仅仅会影响三个点)。
  2. 对于当前的点,从可以影响他的状态的点中找。

问题:在状态里面,x与y顺序可以交换吗?

不能,在这里,必须把工作人员编上号,而不能把他们视作三个相同的人。

我想用广度优先来减少遍历,可是代码太过于复杂

 tmp = q.front(), q.pop();
int i = tmp.i;
int z = p[i];
int x = tmp.x;
int y = tmp.y;
if(i>=N) continue;
if(p[i+1] != y && i+1 != z && y != z)
{
tmp.i = i+1, tmp.x = x, tmp.y = y;
q.push(tmp);
f[i+1][x][y] = min(f[i+1][x][y], f[i][x][y]+c[z][p[i+1]]);
}

但是3的指数级别增长过快,所以还不如全部循环一遍。

同时,还要判断某一个数在不在队列里面。。。。

注意:如果遇到x,y,z在一起,那么就忽略。

代码实现

#include <bits/stdc++.h>
using namespace std;
#define N1 210
#define N2 1010
int c[N1][N1];
int f[N2][N1][N1];
int p[N2];
int main()
{
int L, N;
scanf("%d%d", &L, &N);
for(int i = 1; i <= L; i++)
for(int j = 1; j <= L; j++) scanf("%d", &c[i][j]);
for(int i = 1; i <= N; i++) scanf("%d", p+i);
memset(f, 0x3f, sizeof(f)); f[0][1][2] = 0;//设置初始条件!
p[0] = 3; for(int i =0; i <= N; i++)
for(int x = 1; x <= L; x++)
for(int y = 1; y <= L; y++)
{
int z = p[i];
//这一个是要排除不可能的情况:
//在下面的三种转移情况中,可能会出现两个快递员在一起的情况,虽然更新了,但是我在i++之后不考虑他就行了
if(x==y || x == z || y == z) continue; //注意:在这里有一个大问题:
f[i+1][x][y] = min(f[i+1][x][y], f[i][x][y]+c[z][p[i+1]]);
f[i+1][y][z] = min(f[i+1][y][z], f[i][x][y]+c[x][p[i+1]]);//下一步还可以是f[i+1][z][y]
//这种情况刚好与上述的情况是对称的,按道理来说,应该添加一遍,但是实际上,由于取的是min,
//所以确保有一个就行了如果把语句换成
//f[i+1][y][z] = min(f[i+1][y][z], f[i][x][y]+c[x][p[i+1]]),
//最终结果就是在对称的位置上有一个同样大小的值
f[i+1][x][z] = min(f[i+1][x][z], f[i][x][y]+c[y][p[i+1]]); }
int ans = 0x3f3f3f3f;
for(int i = 1; i <= L; i++)
for(int j = 1; j <= L; j++)
if(f[N][i][j] < ans && (p[N]!=i && p[N]!=j && i!=j)) ans = f[N][i][j];//注意排除不可能情况
cout << ans;
return 0;
}

AcWing275. 传纸条

小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排坐成一个 mn 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 (1,1),小轩坐在矩阵的右下角,坐标 (m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。 在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙,反之亦然。 还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 表示),可以用一个 0∼100 的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的两条路径。

输入格式

第一行有 2 个用空格隔开的整数 mn,表示学生矩阵有 mn

接下来的 m 行是一个 m×n 的矩阵,矩阵中第 ij 列的整数表示坐在第 ij 列的学生的好心程度,每行的 n 个整数之间用空格隔开。

输出格式

输出一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

数据范围

1≤n,m≤50

输入样例:

3 3
0 3 9
2 8 5
5 7 0

输出样例:

34

思路

在这道题目中,先后从左上到右下还有从右下到左上可以转化为两条从左上到右下的重合的路径。

在这道题目中,y总讲到了方格取数的问题,并且把这一道题目等价于方格取数,按照方格取数的方法来进行解答。最终答案是正确的。

证明过程参考vlehr ,的题解[2]

方格数组的题目:假设方格里面是黄金(非负数),从左上角向右下角走,可以走两次,每一次向下或者是向右。到达一个格子以后,就把这一个格子的黄金全部拿走(黄金不可再生!)

询问:最多可以拿到多少的黄金。

两类情况:

  1. 当拓展出去的更优的时候,那么方格数数还有传纸条是一样的,肯定没有交点。
  2. 当拓展出去的和原来的一样的时候,方格数数还有传纸条的结果相同,但是过程不相同。

这道题目还有状态表示。

理论上是要有f[x1][y1][x2][y2]四种状态,

但是从左上方走到右下方,走且必须走\(m+n\)步。

所以可以抽象f[k][x1][x2],这样,就可以把\(N*N*N*N\) 转化为\((N+N)*N*N*N\)这种情况。

代码实现

#include <bits/stdc++.h>
using namespace std;
#define N 54
int f[N*2][N][N];
int c[N][N];
int main()
{
int n, m;
scanf("%d%d", &n, &m);//我的习惯是n行 m列
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%d", &c[i][j]);
for(int k = 2; k <= n+m; k++)
for(int x1 = max(1, k-m); x1 <= min(k-1, n); x1++)
for(int x2 = max(1, k-m); x2 <= min(k-1, n); x2++)
{
//if(x1 == x2 && x1!=1 && x1 != m) 这是我的一个BUG,注意,x1==x2!=1并不意味这一定是左上角的,因为可能是k!=2
if(x1 == x2 && k!=2 && k != m+n)
{
f[k][x1][x2] = -0x3f3f3f3f;
continue;//已经是不合法的情况,现在让其他点永远不可能从这一个点转移过去!
}
//和 y总 一样偷懒 ~ ~ ~
//f[k][x1][x2] = 0;
for(int a = 0; a <= 1; a++)
for(int b = 0; b <= 1; b++)
{
f[k][x1][x2] = max(f[k][x1][x2], f[k-1][x1-a][x2-b] + c[x1][k-x1]+c[x2][k-x2]);
}
}
cout << f[m+n][n][n];//这里是我出的BUG,一定要时刻注意我的状态表示具体代表的是什么 return 0;
}

AcWing276. I-区域

N×M 的矩阵中,每个格子有一个权值,要求寻找一个包含 K 个格子的凸连通块(连通块中间没有空缺,并且轮廓是凸的),使这个连通块中的格子的权值和最大。

注意:凸连通块是指:连续的若干行,每行的左端点列号先递减、后递增,右端点列号先递增、后递减。

求出这个最大的权值和,并给出连通块的具体方案,输出任意一种方案即可。

输入格式

第一行包含三个整数 N,MK。接下来 N 行每行 M 个整数,表示 N×M 的矩阵上每个格子的权值(均不超过 1000)。

输出格式

第一行输出 Oil : X,其中 X 为最大权值和。

接下来 K 行每行两个整数 x**iy**i,用来描述所有格子的具体位置,每个格子位于第 x**i 行,第 y**i 列。

数据范围

1≤N,M≤15,0≤KN×M

输入样例:

2 3 4
10 20 30
40 2 3

输出样例:

Oil : 100
1 1
1 2
1 3
2 1

思路

这道题目真的真的是折磨折磨折大磨。———xjsc01

我选择放弃(调了一天,浪费一天,建议不要做)

错误代码

#include <bits/stdc++.h>
using namespace std;
#define N 16
int f[N][N*N][N][N][2][2];
struct STATE{
int i, j, l, r, x, y;
}g[N][N*N][N][N][2][2];
int c[N][N];
int main()
{
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j ++)
scanf("%d", &c[i][j]);
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) continue;//r-l+1 < k原来
//左扩张,有=右扩张
{
int &vf = f[i][j][l][r][1][0];
STATE &vg = g[i][j][l][r][1][0];
for(int p = l; p <= r; p++)
for(int q = p; q <= r; q++)
{
if(f[i-1][j-(r-l+1)][p][q][1][0] > vf)
{
vf = f[i-1][j-(r-l+1)][p][q][1][0];
vg.i = i-1, vg.j = j-(r-l+1), \
vg.l = p, vg.r = q, vg.x = 1,vg.r = 0;
}
}
for(int xx = l; xx <= r; xx++) vf += c[i][xx];
}
//左扩张,右收缩
{
int &vf = f[i][j][l][r][1][1];
STATE &vg = g[i][j][l][r][1][1];
for(int p = l; p <= r; p++)
for(int q = r; q <= m; q++)
for(int y = 0; y <= 1; y++)
{
if(f[i-1][j-(r-l+1)][p][q][1][y] > vf)
{
vf = f[i-1][j-(r-l+1)][p][q][1][y];
vg.i = i-1, vg.j = j-(r-l+1), \
vg.l = p, vg.r = q, vg.x = 1,vg.r = y;
}
}
for(int xx = l; xx <= r; xx++) vf += c[i][xx];
}
//左收缩右扩张
{
int &vf = f[i][j][l][r][0][0];
STATE &vg = g[i][j][l][r][0][0];
for(int p = 1; p <= l; p++)
for(int q = l; q <= r; q++)
for(int x = 0; x <= 1; x++)
{
if(f[i-1][j-(r-l+1)][p][q][x][0] > vf)
{
vf = f[i-1][j-(r-l+1)][p][q][x][0];
vg.i = i-1, vg.j = j-(r-l+1), \
vg.l = p, vg.r = q, vg.x = x,vg.r = 0;
}
}
for(int xx = l; xx <= r; xx++) vf += c[i][xx];
}
//左收缩,右收缩
{
int &vf = f[i][j][l][r][0][1];
STATE &vg = g[i][j][l][r][0][1];
for(int p = 1; p <= l; p++)
for(int q = r; q <= m; q++)
for(int x = 0; x <= 1; x++)
for(int y = 0; y <= 1; y++)
{
if(f[i-1][j-(r-l+1)][p][q][x][y] >vf)
{
vf = f[i-1][j-(r-l+1)][p][q][x][y];
vg.i = i-1, vg.j = j-(r-l+1), \
vg.l = p, vg.r = q, vg.x = x,vg.r = y;
}
}
for(int xx = l; xx <= r; xx++) vf += c[i][xx];
}
}
STATE state;
int ans = 0;
for(int i = 1; i <= n; i++)
for(int l = 1; l <= m; l++)
for(int r = l; r <= m; r++)
for(int x = 0; x <= 1; x++)
for(int y = 0; y <= 1; y++)
{
if(f[i][k][l][r][x][y] > ans)
{
ans = f[i][k][l][r][x][y];
state.i = i, state.j = k;
state.l = l, state.r = r;
state.x = x, state.y = y;
}
}
printf("Oil : %d\n", ans);
while(state.j)
{
for(int xx = state.l; xx <= state.r; xx++)
printf("%d %d\n", state.i, xx);
state = g[state.i][state.j][state.l][state.r][state.x][state.y];
}
return 0;
}

AcWing277. 饼干

圣诞老人共有 M 个饼干,准备全部分给 N 个孩子。每个孩子有一个贪婪度,第 i 个孩子的贪婪度为 g[i]。

如果有 a[i] 个孩子拿到的饼干数比第 i 个孩子多,那么第 i 个孩子会产生 g[ia[i] 的怨气。

给定 NM 和序列 g,圣诞老人请你帮他安排一种分配方式,使得每个孩子至少分到一块饼干,并且所有孩子的怨气总和最小。

输入格式

第一行包含两个整数 N,M

第二行包含 N 个整数表示 g1∼g**N

输出格式

第一行一个整数表示最小怨气总和。第二行 N 个空格隔开的整数表示每个孩子分到的饼干数,若有多种方案,输出任意一种均可。

数据范围

1≤N≤30,NM≤5000,1≤g**i≤107

输入样例:

3 20
1 2 3

输出样例:

2
2 9 9

思路

知识点一:排序不等式



所以在本题中,最优解一定在给贪婪程度大的孩子分配多的糖果的情况里面。

DP状态:(精髓:由于所有孩子的嫉妒值仅仅与排名有关,如果要是小孩得到的糖果数量大于一,那么我可以全部减一,最终的答案并不会发生变化)

状态表示:f[i][j]表示在安排了经过排序的1~i个孩子,使用j个糖果,最终孩子的嫉妒值的最小值。

转态转移:(深邃的思想)

有两种情况(假定最右面为1 的个数)

  1. k==0,这个时候,给所有的孩子的糖果数全部减一。
  2. \(1 \le k \le i\)这个时候,相当于最后k个孩子有一个糖果,进而进行转移。

注意:上述转移可能来自不合法的情况,但是初始化是最大值,遍历过的一定是合法的。

不合的地方转移过来一定是无穷大,所以没有关系!

代码实现

#include <bits/stdc++.h>
using namespace std;
#define N1 32
#define N2 5010
typedef pair<int, int> PII;
PII a[N1];//first用来读入孩子的贪婪度,second用于确定开始的序号
int s[N1];//排序之后的贪婪度的前缀和
int f[N1][N2];
int ans[N1];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i].first);
a[i].second = i;
} sort(a+1, a+1+n);
reverse(a+1, a+1+n);//偷懒做法 memset(f, 0x3f, sizeof(f));//一定要进行初始化!~
for(int i = 1; i <= n; i++) s[i] = s[i-1] + a[i].first; f[0][0] = 0;
for(int i = 1; i <= n; i++)
for(int j = i; j <= m; j++)//注意,我的i的其实条件就是为了使我每一次更新的全部是合法法。
{
f[i][j] = f[i][j-i];
for(int k = 1; k <= i; k++)//注意:这一个状态的上一个转态可能是合法的,也可能不合法,
//但是我每一次更新的全部都是合法法,不合法法没有被更新,所以是无穷大,并不会影响结果。
{
f[i][j] = min(f[i][j], f[i-k][j-k] + (s[i]-s[i-k])*(i-k));
}
} printf("%d\n", f[n][m]); {//回溯!!!
int i = n, j = m, h = 0;
while(i)
{
if(f[i][j] == f[i][j-i])//如果是K== 0,即没有为 1 的方格数
{
h++;
j -= i;
}
else
{
for(int k = 1; k <= i; k++)
{
if(f[i][j] == f[i-k][j-k] + (s[i]-s[i-k])*(i-k))
{
for(int u = i-k+1; u <= i; u++)
ans[a[u].second] = 1+h;
i -= k;
j -= k;
break;
}
}
}
}
}
for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
return 0;
}


  1. 引用自https://fangkaipeng.com/?p=1111

  2. 引用自https://www.acwing.com/solution/content/12389/

算法竞赛进阶指南0x51 线性DP的更多相关文章

  1. POJ1639 算法竞赛进阶指南 野餐规划

    题目描述 原题链接 一群小丑演员,以其出色的柔术表演,可以无限量的钻进同一辆汽车中,而闻名世界. 现在他们想要去公园玩耍,但是他们的经费非常紧缺. 他们将乘车前往公园,为了减少花费,他们决定选择一种合 ...

  2. 算法竞赛进阶指南 0x00 基本算法

    放在原来这个地方不太方便,影响阅读体验.为了读者能更好的刷题,另起一篇随笔. 0x00 基本算法 0x01 位运算 [题目][64位整数乘法] 知识点:快速幂思想的灵活运用 [题目][最短Hamilt ...

  3. 算法竞赛进阶指南 0x52 背包

    背包问题是线性背包中的一类重要问题. 0/1背包 模型: 给定N个物品,每一个物品具有两种属性,一个是体积 \(v_i\) ,另一个是容积 \(w_i\) . 有一个容积为M的背包,求一种方案,使得选 ...

  4. 算法竞赛进阶指南0x14 Hash

    组成部分: 哈希函数: 链表 AcWing137. 雪花雪花雪花 因为所需要数据量过于大,所以只能以O(n)的复杂度. 所以不可能在实现的过程中一一顺时针逆时针进行比较,所以采用一种合适的数据结构. ...

  5. 《算法竞赛进阶指南》0x10 基本数据结构 Hash

    Hash的基本知识 字符串hash算法将字符串看成p进制数字,再将结果mod q例如:abcabcdefg 将字母转换位数字(1231234567)=(1*p9+2*p8+3*p7+1*p6+2*p5 ...

  6. 《算法竞赛进阶指南》1.4Hash

    137. 雪花雪花雪花 有N片雪花,每片雪花由六个角组成,每个角都有长度. 第i片雪花六个角的长度从某个角开始顺时针依次记为ai,1,ai,2,-,ai,6. 因为雪花的形状是封闭的环形,所以从任何一 ...

  7. bzoj 1787 && bzoj 1832: [Ahoi2008]Meet 紧急集合(倍增LCA)算法竞赛进阶指南

    题目描述 原题连接 Y岛风景美丽宜人,气候温和,物产丰富. Y岛上有N个城市(编号\(1,2,-,N\)),有\(N-1\)条城市间的道路连接着它们. 每一条道路都连接某两个城市. 幸运的是,小可可通 ...

  8. POJ1722 算法竞赛进阶指南 SUBSTRACT减操作

    原题连接 题目描述 给定一个整数数组\(a_1,a_2,-,a_n\). 定义数组第 i 位上的减操作:把\(a_i\)和\(a_{i+1}\)换成\(a_i - a_{i+1}\). 用con(a, ...

  9. 算法竞赛进阶指南--快速幂,求a^b mod p

    // 快速幂,求a^b mod p int power(int a, int b, int p) { int ans = 1; for (; b; b >>= 1) { if (b &am ...

随机推荐

  1. Elasticsearch高级之-集群搭建,数据分片

    目录 Elasticsearch高级之-集群搭建,数据分片 一 广播方式 二 单播方式 三 选取主节点 四 什么是脑裂 五 错误识别 Elasticsearch高级之-集群搭建,数据分片 es使用两种 ...

  2. Vue的Vuex的使用

    一.Vuex是什么? 1:Vuex是一个专为vue.js应用程序开发的状态管理模式,核心就是一个store仓库,采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化 ...

  3. Java高并发-Java内存模型和线程安全

    一.原子性 原子性是指一个操作是不可中断的.即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰. i++是原子操作吗? 不是,包含3个操作:读i,i=i+1,写i 32位的机子上读取 ...

  4. nodejs使用 svg-captcha 做验证码及验证

    一.需求 使用 nodejs 做后端开发,需要请求验证码,在 github 上看到了 svg-captcha 这个库,发现他是将 text 转 svg 进行返回的,安全性也有保证,不会被识别成文字. ...

  5. 安装Nmap到CentOS(YUM)

    Nmap是Linux下的网络扫描工具,我们可以扫描远端主机上那些端口在开放状态. 运行环境 系统版本:CentOS Linux release 7.3.1611 (Core) 软件版本:无 硬件要求: ...

  6. 论文阅读 Predicting Dynamic Embedding Trajectory in Temporal Interaction Networks

    6 Predicting Dynamic Embedding Trajectory in Temporal Interaction Networks link:https://arxiv.org/ab ...

  7. 面试突击51:为什么单例一定要加 volatile?

    单例模式的实现方法有很多种,如饿汉模式.懒汉模式.静态内部类和枚举等,当面试官问到"为什么单例模式一定要加 volatile?"时,那么他指的是为什么懒汉模式中的私有变量要加 vo ...

  8. Johnson 全源最短路

    学这个是为了支持在带负权值的图上跑 Dijkstra. 为了这个我们要考虑把负的权值搞正. 那么先把我们先人已经得到的结论摆出来.我们考虑先用 SPFA 对着一个满足三角形不等式的图跑一次最短路,具体 ...

  9. 树形dp基础

    今天来给大家讲一下数形dp基础 树形dp常与树上问题(lca.直径.重心)结合起来 而这里只讲最最基础的树上dp 1.选课 题目描述 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程 ...

  10. vue面试总结-2022

    1.vue生命周期及各周期得特点 beforCreate 特点: 初始化实例,不能使用data和methods.ref 示例 beforeCreate: function () { console.g ...