fabs(double)abs(int)
数字三角形模型
1018.最低通行费用
一个商人穿过一个 N×N 的正方形的网格,去参加一个非常重要的商务活动。
他要从网格的左上角进,右下角出。
每穿越中间 1 个小方格,都要花费 1 个单位时间。
商人必须在 (2N−1) 个单位时间穿越出去。
而在经过中间的每个小方格时,都需要缴纳一定的费用。
这个商人期望在规定时间内用最少费用穿越出去。
请问至少需要多少费用?
注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。
输入格式
第一行是一个整数,表示正方形的宽度 N。
后面 N 行,每行 N 个不大于 100 的正整数,为网格上每个小方格的费用。
输出格式
输出一个整数,表示至少需要的费用。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 110;int g[N][N],f[N][N];int main(){int n;cin >> n;for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= n; j ++ ){cin >> g[i][j];}}memset(f, 0x3f, sizeof f);f[1][1] = g[1][1];// 注意这里初始化的作用/*因为要取最小值,所以把不合法状态全部初始未正无穷,只能从f[1][1]出发*/for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= n; j ++ ){if(i==1&&j==1){continue;}f[i][j] = min(f[i][j-1],f[i-1][j])+g[i][j];}}cout << f[n][n];}
1027. 方格取数
设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:
某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
输入格式
第一行为一个整数N,表示 N×N 的方格图。
接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。
行和列编号从 1 开始。
一行“0 0 0”表示结束。
输出格式
输出一个整数,表示两条路径上取得的最大的和。
#include <iostream>using namespace std;const int N = 15;int n;int w[N][N], f[N*2][N][N];int main() {cin >> n;int a,b,c;while(cin >> a >> b >> c, a || b || c) w[a][b] = c;for(int k = 2; k <= 2*n; k++) {for(int i1 = 1; i1 <= n; i1++) {for(int i2 = 1; i2 <= n; i2++) {int j1 = k-i1, j2 = k-i2;if(j1>=1&&j1<=n&&j2>=1&&j2<=n) {// 不能越界int &x = f[k][i1][i2];int t = w[i1][j1];if(i1!=i2) t += w[i2][j2];//不重合则需要加两个权重.x = max(x, f[k-1][i1-1][i2-1]+t);x = max(x, f[k-1][i1-1][i2]+t);x = max(x, f[k-1][i1][i2-1]+t);x = max(x, f[k-1][i1][i2]+t);//保留最大属性}}}}cout << f[n*2][n][n] << endl;return 0;}
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 15;int g[N][N];int f[N*2][N][N];int main(){int n;cin >> n;int x,y,w;while(cin>>x>>y>>w&&(x||y||w)){g[x][y] = w;}for (int i = 2; i <= 2*n; i ++ ){for (int j = 1; j <= n; j ++ ){for (int k = j; k <= n; k ++ ){ // 这里也可以写成k从j开始if(i>=j&&i>=k){auto& t = f[i][j][k];t = max(t,f[i-1][j][k]);t = max(t,f[i-1][j-1][k-1]);t = max(t,f[i-1][j-1][k]);t = max(t,f[i-1][j][k-1]);t += g[j][i-j];if(j!=k){t += g[k][i-k];}}}}}cout <<f[2*n][n][n];}
275. 传纸条
小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。
一次素质拓展活动中,班上同学安排坐成一个 m 行 n 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。
幸运的是,他们可以通过传纸条来进行交流。
纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 (1,1),小轩坐在矩阵的右下角,坐标 (m,n)。
从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。
班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙,反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 表示),可以用一个 0∼100 的自然数来表示,数越大表示越好心。
小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。
现在,请你帮助小渊和小轩找到这样的两条路径。
输入格式
第一行有 2 个用空格隔开的整数 m 和 n,表示学生矩阵有 m 行 n 列。
接下来的 m 行是一个 m×n 的矩阵,矩阵中第 i 行 j 列的整数表示坐在第 i 行 j 列的学生的好心程度,每行的 n 个整数之间用空格隔开。
输出格式
输出一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。
数据范围
1≤n,m≤50
输入样例:
3 3
0 3 9
2 8 5
5 7 0
输出样例:
34
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 55;int g[N][N];int f[N*2][N][N];int main(){int n,m;cin >> n >> m;for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= m; j ++ ){cin >> g[i][j];}}/*这道题与上一题不同的地方有两点1. 上题是走过A点之后,A点的值变为0,但是第二次还可以走但是这道题是走过之后就不能走了2. 本题的n和m不一样,上题是一个n*n的矩阵*/int sum = max(n,m);for (int i = 2; i <= 2*sum; i ++ ){for (int j = 1; j <= sum; j ++ ){for (int k = j+1; k <= sum; k ++ ){ // 这里直接从j+1开始,因为k!=jif(i>=j&&i>=k&&j!=k){auto& t = f[i][j][k];t = max(t,f[i-1][j][k]);t = max(t,f[i-1][j-1][k-1]);t = max(t,f[i-1][j-1][k]);t = max(t,f[i-1][j][k-1]);t += g[j][i-j]+g[k][i-k];}}}}cout << f[2*sum][sum-1][sum]<<endl;}
这里我想解释一下为什么k可以从j+1开始 首先要明确
f[i][j][k]表示的是什么呢 ,表示两个点a1=(j,i-j),a2=(k,i-k); 也就是说表示的是两个点横纵坐标之和是i,其中一个的横坐标的j,另一个的横坐标是k 那为什么k可以从j+1开始呢?
- j!=k
- 就拿这个样例来模拟一下
- f[3][1][2] = max(f[2][1][2],f[2][0][2],f[2][0][1],f[2][1][1]) + g[1][3-1] + g[2][3-2]
- f[3][2][1] = max(f[2][2][1],f[2][1][1],f[2][1][0],f[2][2][0]) + g[1][3-1] + g[2][3-2]
- 可以看到后面两项完全相同,只是前面的项不同而已,但是前面的项全部为0,
- 这就推出f[3][1][2]和f[3][2][1]两项是等同的
- 而f[4]又使用的f[3]的结果所以f[4]也是可以的以此类推
这样说似乎有点抽象,画个图来说明
在计算f[4][1][3]的时候 会从5的上下来枚举,9的上下来枚举 然后 + 5 + 9 f[4][3][1]的时候也同样会,这样想似乎更好理解
最长上升子序列模型
1014. 登山
五一到了,ACM队组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。
同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。
队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
输入格式
第一行包含整数N,表示景点数量。
第二行包含N个整数,表示每个景点的海拔。
输出格式
输出一个整数,表示最多能浏览的景点数。
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1010;int a[N];int f[N],g[N];int n;void get(int b[N]){for (int i = 1; i <= n; i ++ ){b[i] = 1;for (int j = 1; j < i; j ++ ){if(a[i]>a[j]){b[i] = max(b[i],b[j]+1);}}}}int main(){cin >> n;for (int i = 1; i <= n; i ++ ){cin >> a[i];}get(f);reverse(a+1,a+n+1);get(g);reverse(g+1,g+n+1); // 这里的g也要反转int res = 0;for (int i = 1; i <= n; i ++ ){res = max(f[i]+g[i]-1,res);}cout << res;}
482. 合唱队形
N 位同学站成一排,音乐老师要请其中的 (N−K) 位同学出列,使得剩下的 K 位同学排成合唱队形。
合唱队形是指这样的一种队形:设 K 位同学从左到右依次编号为 1,2…,K,他们的身高分别为 T1,T2,…,TK, 则他们的身高满足 T1<…Ti+1>…>TK(1≤i≤K)。
你的任务是,已知所有 N 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
输入的第一行是一个整数 N,表示同学的总数。
第二行有 N 个整数,用空格分隔,第 i 个整数 Ti 是第 i 位同学的身高(厘米)。
输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4
只需要将1014登山的最后输出改为cout<< n- res;即可
1012. 友好城市
Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。
北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。
每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。
编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。
输入格式
第1行,一个整数N,表示城市数。
第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。
输出格式
仅一行,输出一个整数,表示政府所能批准的最多申请数。
输入样例:
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
输出样例:
4
// 只需要对一方的城市排序// 然后对另一方求最长递增子序列#include<iostream>#include<algorithm>#include<cstring>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 5010;PII a[N];int n,f[N];int main(){cin >> n;for(int i=1;i<=n;i++){cin >> a[i].x >> a[i].y;}sort(a+1,a+1+n);int res = 0;for(int i=1; i<=n;i++){f[i] = 1;for (int j = 1; j < i; j ++ ){if(a[i].y>a[j].y){f[i] = max(f[i],f[j]+1);}res = max(res,f[i]);}}cout << res;}
1010. 拦截导弹
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
共一行,输入导弹依次飞来的高度。
输出格式
第一行包含一个整数,表示最多能拦截的导弹数。
第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。
输入样例:
389 207 155 300 299 170 158 65
输出样例:
6
2
#include <iostream>#include <cstring>#include <algorithm>#include <sstream>using namespace std;const int N = 1010;int a[N],n;int f[N],g[N];int main(){string str;getline(cin,str);stringstream ssin(str);while(ssin>>a[n]){n++;}int res = 0;for (int i = 0; i < n; i ++ ){f[i] = 1;for (int j = 0; j < i; j ++ ){if(a[i]<=a[j]){f[i] = max(f[i],f[j]+1);}res = max(f[i],res);}}// 贪心解法求,// 从头开始遍历,每次将a[i] 加入当前所有导弹中最小的后面// g[i]中是一个递增序列int cnt = 0;for (int i = 0; i < n; i ++ ){int k=0;while(k<cnt&&a[i]>g[k])k++;g[k] = a[i];if(k>=cnt){cnt++;}}cout << res <<endl;cout << cnt;}
187. 导弹防御系统
为了对抗附近恶意国家的威胁,R 国更新了他们的导弹防御系统。
一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。
例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 4 的导弹。
给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包含整数 n,表示来袭导弹数量。
第二行包含 n 个不同的整数,表示每个导弹的高度。
当输入测试用例 n=0 时,表示输入终止,且该用例无需处理。
输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。
输入样例:
5
3 5 2 4 1
0
输出样例:
2
样例解释
对于给出样例,最少需要两套防御系统。
一套击落高度为 3,4 的导弹,另一套击落高度为 5,2,1 的导弹。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 55;int a[N];int up[N],down[N];int n,ans;void dfs(int u,int su,int sd){ // 上升子序列,下降子序列if(su+sd>=ans){return;}if(u==n){ans = su+sd;return;}// 枚举将点u放到上升子序列int k=0;while(k<su&&up[k]>=a[u]){k++;}int t = up[k];up[k] = a[u];if(k>=su){dfs(u+1,su+1,sd);}else{dfs(u+1,su,sd);}// 恢复现场up[k] = t;// 枚举将点u放到下降子序列k=0;while(k<sd&&down[k]<=a[u]){k++;}t = down[k];down[k] = a[u];if(k>=sd){dfs(u+1,su,sd+1);}else{dfs(u+1,su,sd);}// 恢复现场down[k] = t;}int main(){while(cin>>n&&n){for (int i = 0; i < n; i ++ ){cin >> a[i];}ans = n;dfs(0,0,0);cout << ans <<endl;}}、‘
272. 最长公共上升子序列
熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。
小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。
小沐沐说,对于两个数列 A 和 B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。
奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。
不过,只要告诉奶牛它的长度就可以了。
输入格式
第一行包含一个整数 N,表示数列 A,B 的长度。
第二行包含 N 个整数,表示数列 A。
第三行包含 N 个整数,表示数列 B。
输出格式
输出一个整数,表示最长公共上升子序列的长度。
输入样例:
4
2 2 1 3
2 1 2 3
输出样例:
2
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 3010;int a[N],b[N];int f[N][N]; // 只考虑a的前i个字符,以b[j]结尾的最长公共子序列int main(){int n;cin >> n;for (int i = 1; i <= n; i ++ ){cin >> a[i];}for (int i = 1; i <= n; i ++ ){cin >> b[i];}int res = 0;for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= n; j ++ ){// 相当于不考虑a[i], 就是说如果a[i]!=b[j]的话,不用考虑a[i]// 那么如果考虑a[i]的话以b[j]结尾的方案一定是大于等于不考虑a[i]的方案的f[i][j] = f[i-1][j];if(a[i]==b[j]){// 他们俩相等了,那么就以b[j]结尾的公共子序列最小是1f[i][j] = max(f[i][j],1);for (int k = 0; k < j; k ++ ){// 因为是上升子序列,a[i]和b[j]也是相等的,那么前面的就一定得// 比他们小if(b[k]<b[j]){f[i][j] = max(f[i][j],f[i][k]+1);}}}res = max(res,f[i][j]);}}cout << res;}// 优化版本#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 3010;int a[N],b[N];int f[N][N]; // 只考虑a的前i个字符,以b[j]结尾的最长公共子序列int main(){int n;cin >> n;for (int i = 1; i <= n; i ++ ){cin >> a[i];}for (int i = 1; i <= n; i ++ ){cin >> b[i];}int res = 0;for (int i = 1; i <= n; i ++ ){int maxv = 1;for (int j = 1; j <= n; j ++ ){// 不考虑a[i]f[i][j] = f[i-1][j];if(a[i]==b[j]){f[i][j] = max(f[i][j],maxv);}if(a[i]>b[j]){maxv = max(maxv,f[i][j]+1);}res = max(res,f[i][j]);}}cout << res;}
背包模型
1022. 宠物小精灵之收服
宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。
一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。
小智也想收服其中的一些小精灵。
然而,野生的小精灵并不那么容易被收服。
对每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。
当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。
当小智的精灵球用完时,狩猎也宣告结束。
我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。
如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。
小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。
现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。
请问,小智该如何选择收服哪些小精灵以达到他的目标呢?
输入格式
输入数据的第一行包含三个整数:N,M,K,分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。
之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。
输出格式
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。
输入样例1:
10 100 5
7 10
2 40
2 50
1 20
4 20
输出样例1:
3 30
输入样例2:
10 100 5
8 110
12 10
20 10
5 200
1 110
输出样例2:
0 100
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1010,M= 510,K=110;int f[N][M];int a[K],b[K];int main(){int n,m,z;cin >> n >> m >> z; // 精灵球数量、皮卡丘初始的体力值for (int i = 1; i <= z; i ++ ){cin >> a[i] >> b[i];// 收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。}for (int i = 1; i <= z; i ++ ){for (int j = n; j>=a[i] ; j-- ){for (int k = m-1; k>=b[i] ; k -- ){ // 注意这里是m-1 不能到mf[j][k] = max(f[j][k],f[j-a[i]][k-b[i]]+1);}}}cout << f[n][m-1];int used=0;for (int i = 0; i <m; i ++ ){if(f[n][i]==f[n][m-1]){used = i;break;}}cout << " "<<m - used;}
4378. 选取数对

输入格式
第一行包含三个整数 n,m,k。
第二行包含 n 个整数 a1,a2,…,an。
输出格式
一个整数,表示 sum 的最大可能值。
数据范围
输入样例1:
5 2 1
1 2 3 4 5
输出样例1:
9
输入样例2:
7 1 3
2 10 7 18 5 33 0
输出样例2:
61
#include <iostream>#include <cstring>#include <algorithm>using namespace std;typedef long long LL;int n,m,k;const int N = 5010;LL s[N];LL f[N][N];int main(){cin >> n >> m >> k;for (int i = 1; i <= n; i ++ ){cin >> s[i];s[i]+=s[i-1];}for (int i = m; i <= n; i ++ ){for (int j = 1; j <= k; j++ ){ // 这里的i-m太妙了f[i][j]= max(f[i-1][j],f[i-m][j-1]+s[i]- s[i-m]);}}cout << f[n][k];}
278. 数字组合
给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。
输入格式
第一行包含两个整数 N 和 M。
第二行包含 N 个整数,表示 A1,A2,…,AN。
输出格式
包含一个整数,表示可选方案数。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 110,M = 1e4+10;int f[M];int a[N];int main(){int n,m;cin >> n >> m;for (int i = 1; i <= n; i ++ ){cin >> a[i];}f[0]=1;for (int i = 1; i <= n; i ++ ){for (int j = m; j >= a[i] ; j -- ){f[j] += f[j-a[i]];}}cout << f[m] ;}
1023. 买书
小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。
问小明有多少种买书方案?(每种书可购买多本)
输入格式
一个整数 n,代表总共钱数。
输出格式
一个整数,代表选择方案种数。
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1010;int f[N];int a[5]={10,20,50,100};int main(){int n;cin >> n;f[0]=1;for (int i = 0; i < 4; i ++ ){for (int j = a[i]; j <= n; j ++ ){f[j] += f[j-a[i]];}}cout << f[n];}
1021. 货币系统
给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。
输入格式
第一行,包含两个整数n和m。
接下来n行,每行包含一个整数,表示一种货币的面值。
输出格式
共一行,包含一个整数,表示方案数。
输入样例:
3 10
1
2
5
输出样例:
10
#include <iostream>#include <cstring>#include <algorithm>using namespace std;typedef long long LL;const int N = 3010;int a[N];LL f[N];int main(){int n,m;cin >> n >> m;for (int i = 1; i <= n; i ++ ){cin >> a[i];}f[0] = 1;for (int i = 1; i <= n; i ++ ){for (int j = a[i]; j <= m; j ++ ){f[j] += f[j-a[i]];}}cout << f[m];}
532. 货币系统
在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。
为了方便,我们把货币种数为 n、面额数组为 a[1..n] 的货币系统记作 (n,a)。
在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]×t[i] 的和为 x。
然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。
例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。
两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。
现在网友们打算简化一下货币系统。
他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。
他们希望你来协助完成这个艰巨的任务:找到最小的 m。
输入格式
输入文件的第一行包含一个整数 T,表示数据的组数。
接下来按照如下格式分别给出 T 组数据。
每组数据的第一行包含一个正整数 n。
接下来一行包含 n 个由空格隔开的正整数 a[i]。
输出格式
输出文件共有 T 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。
输入样例:
2
4
3 19 10 6
5
11 29 13 19 17
输出样例:
2
5
// 先排序从前向后找,如果当前数不能被前边得数表示出来,那么就一定得选当前数#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 110,M=25010;int a[N];bool f[M];int main(){int T;cin >> T;while(T--){int n;cin >> n;for (int i = 0; i < n; i ++ ){cin >> a[i];}sort(a,a+n);memset(f, 0, sizeof f);f[0]=true;int res=0;for (int i = 0; i < n; i ++ ){if(f[a[i]]){res++;}for (int j = a[i]; j <=a[n-1]; j ++ ){f[j]|=f[j-a[i]];}}cout << n-res<<endl;}}
1019. 庆功会
为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。
期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
输入格式
第一行二个数n,m,其中n代表希望购买的奖品的种数,m表示拨款金额。
接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可)。
输出格式
一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
输入样例:
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
输出样例:
1040
#include <iostream>#include <cstring>#include <algorithm>using namespace std;typedef long long LL;const int N = 60010,M=6010;LL w[N],v[N];LL f[M];int main(){int n,m;cin >> n >> m;int cnt=0;for (int i = 0; i < n; i ++ ){int ww,vv,s; // 价格,价值,最大数量cin >> ww >> vv >> s;int k=1;while(k<=s){w[cnt]=ww*k;v[cnt]=vv*k;s-=k;k*=2;cnt++;}if(s>0){w[cnt]=ww*s;v[cnt]=vv*s;cnt++;}}for (int i = 0; i < cnt; i ++ ){for (int j = m; j >= w[i]; j -- ){f[j] = max(f[j],f[j-w[i]]+v[i]);}}cout << f[m];}
7. 混合背包问题
有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8
#include <iostream>#include <cstring>#include <algorithm>using namespace std;typedef long long LL;const int N = 60010,M=6010;LL w[N],v[N];LL f[M];int w1[N],v1[N];int main(){int n,m;cin >> n >> m;int cnt=0,sum=0;for (int i = 0; i < n; i ++ ){int ww,vv,s; // 价格,价值,最大数量cin >> ww >> vv >> s;if(s==-1){w[cnt]=ww;v[cnt]=vv;cnt++;}if(s==0){w1[sum]=ww;v1[sum]=vv;sum++;}if(s>0){int k=1;while(k<=s){w[cnt]=ww*k;v[cnt]=vv*k;s-=k;k*=2;cnt++;}if(s>0){w[cnt]=ww*s;v[cnt]=vv*s;cnt++;}}}for (int i = 0; i < cnt; i ++ ){for (int j = m; j >= w[i]; j -- ){f[j] = max(f[j],f[j-w[i]]+v[i]);}}for (int i = 0; i < sum; i ++ ){for (int j = w1[i]; j<=m; j ++ ){f[j] = max(f[j],f[j-w1[i]]+v1[i]);}}cout << f[m];}
8. 二维费用的背包问题
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行三个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出样例:
8
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1010,V = 110,M=110;int vv[N],mm[N],ww[N];int f[V][M];int main(){int n,v,m;cin >> n >> v >> m;for (int i = 0; i < n; i ++ ){cin >> vv[i] >> mm[i] >> ww[i];}for (int i = 0; i < n; i ++ ){for(int j=v;j>=vv[i];j--){for(int k=m;k>=mm[i];k--){f[j][k] = max(f[j][k],f[j-vv[i]][k-mm[i]]+ww[i]);}}}cout << f[v][m];}
1020. 潜水员
潜水员为了潜水要使用特殊的装备。
他有一个带2种气体的气缸:一个为氧气,一个为氮气。
让潜水员下潜的深度需要各种数量的氧和氮。
潜水员有一定数量的气缸。
每个气缸都有重量和气体容量。
潜水员为了完成他的工作需要特定数量的氧和氮。
他完成工作所需气缸的总重的最低限度的是多少?
例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。
你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
输入格式
第一行有2个整数 m,n。它们表示氧,氮各自需要的量。
第二行为整数 k 表示气缸的个数。
此后的 k 行,每行包括ai,bi,ci,3个整数。这些各自是:第 i 个气缸里的氧和氮的容量及气缸重量。
输出格式
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
输入样例:
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
输出样例:
249
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N=1010;int a[N],b[N],c[N]; //氧和氮的容量及气缸重量。int f[25][85];int main(){int n,m,z;cin >> n >> m >> z;for (int i = 0; i < z; i ++ ){cin >> a[i] >> b[i] >> c[i];}memset(f, 0x3f, sizeof f);f[0][0] = 0;for (int i = 0; i < z; i ++ ){for (int j = n; j>=0 ; j -- ){for (int k = m; k>=0 ; k -- ){ // 注意是大于等于0f[j][k] = min(f[j][k],f[max(0,j-a[i])][max(0,k-b[i])]+c[i]);// 这里是关键}}}cout << f[n][m];}
1013.机器分配
总公司拥有M台 相同 的高效设备,准备分给下属的N个分公司。
各分公司若获得这些设备,可以为国家提供一定的盈利。盈利与分配的设备数量有关。
问:如何分配这M台设备才能使国家得到的盈利最大?
求出最大盈利值。
分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数M。
输入格式
第一行有两个数,第一个数是分公司数N,第二个数是设备台数M;
接下来是一个NM的矩阵,矩阵中的第 i 行第 j 列的整数表示第 i 个公司分配 j 台机器时的盈利。
输出格式
第一行输出最大盈利值;
接下N行,每行有2个数,即分公司编号和该分公司获得设备台数。
答案不唯一,输出任意合法方案即可。
输入样例:
3 3
30 40 50
20 30 50
20 25 30
*输出样例:
70
1 1
2 1
3 1
#include <iostream>#include <cstring>#include <algorithm>const int N = 12,M = 17;using namespace std;int g[N][M];int res[N],tmp[N];int maxv;int n,m;void dfs(int x,int last,int value){ // 当前分配到第i家公司,剩余可分配数,当前最大价值if(x==n+1){if(value>maxv){for (int i = 1; i <= n; i ++ ){res[i] = tmp[i];}maxv = value;}return;}for (int i = 0; i <= last; i ++ ){tmp[x] = i;value+=g[x][i];dfs(x+1,last-i,value);value-=g[x][i];}}int main(){cin >> n >> m;for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= m; j ++ ){cin >> g[i][j];}}dfs(1,m,0);cout << maxv<<endl;for (int i = 1; i <= n; i ++ ){cout << i << " "<<res[i]<<endl;}}

#include <iostream>#include <cstring>#include <algorithm>const int N = 12,M = 17;using namespace std;int g[N][M],f[N][M];int res[N],tmp[N];int maxv;int n,m;int main(){cin >> n >> m;for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= m; j ++ ){cin >> g[i][j];}}for (int i = 1; i <= n; i ++ ){ // 物品for (int j = 1; j <= m; j ++ ){ // 体积f[i][j] = f[i-1][j];for (int k = 1; k <= j; k ++ ){ // j决策 选物品组的第几个物品f[i][j] = max(f[i][j],f[i-1][j-k]+g[i][k]);}}}int i=n,j=m;while(i&&j){for (int k = 0; k <= m; k ++ ){if(f[i][j]==f[i-1][j-k]+g[i][k]){res[i] = k;j = j-k;i--;break;}}}cout << f[n][m] <<endl;for (int i = 1; i <= n; i ++ ){cout << i <<" "<<res[i]<<endl;}}
426. 开心的金明
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 N 元钱就行”
今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的 N 元。
于是,他把每件物品规定了一个重要度,分为 5 等:用整数 1∼5 表示,第 5 等最重要。
他还从因特网上查到了每件物品的价格(都是整数元)。
他希望在不超过 N 元(可以等于 N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第 j 件物品的价格为 v[j],重要度为 w[j],共选中了 k 件物品,编号依次为 j1,j2,…,jk,则所求的总和为:
v[j1]×w[j1]+v[j2]×w[j2]+…+v[jk]×w[jk]
请帮助金明设计一个满足要求的购物单。
输入格式
输入文件的第 1 行,为两个正整数 N 和 m,用一个空格隔开。(其中 N 表示总钱数,m 为希望购买物品的个数)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j−1 的物品的基本数据,每行有 2 个非负整数 v 和 p。(其中 v 表示该物品的价格,p 表示该物品的重要度)
输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(数据保证结果不超过 108)。
输入样例:
1000 5
800 2
400 5
300 5
400 3
200 2
输出样例:
3900
#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 3e5+10,M = 30;int f[N];int n,m;int ww[M],vv[M]; // 价值,体积int main(){cin >> n >> m;for (int i = 1; i <= m; i ++ ){int v,p;cin >> v >> p;ww[i] = v*p;vv[i] = v;}for (int i = 1; i <=m; i ++ ){for (int j = n; j >=vv[i]; j -- ){f[j] = max(f[j],f[j-vv[i]]+ww[i]);}}cout << f[n];}
10. 有依赖的背包问题
有 NN 个物品和一个容量是 VV 的背包。
物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如下图所示:
如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。
每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品个数和背包容量。
接下来有 N 行数据,每行数据表示一个物品。
第 i 行有三个整数 vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 pi=−1,表示根节点。 数据保证所有物品构成一棵树。
输出格式
输出一个整数,表示最大价值。
输入样例
5 72 3 -12 2 13 5 14 7 23 6 2
输出样例:
11
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 110;
vector<int>h[N];
int v[N],val[N];
int n,V;
int f[N][N]; // 以i为根的子树当中选,体积是j的 最大价值
void dfs(int u){
// 递归子树,不考虑u节点,但是要给u节点留出位置来
for (int i = 0; i < h[u].size(); i ++ ){
int son = h[u][i];
dfs(son);
for (int j = V-v[u]; j >=0; j -- ){ // 体积只能在大于v[u]当中选
for (int k = 0; k <= j; k ++ ){
f[u][j] = max(f[u][j],f[u][j-k]+f[son][k]);
}
}
}
// 把u节点加进去
for(int i = V; i >= v[u]; i--){
f[u][i] = f[u][i-v[u]]+val[u];
}
// 因为有了u节点,所以只要体积小于v[u]的全部为0
for (int i = 0; i < v[u]; i ++ ){
f[u][i] = 0;
}
}
int main()
{
cin >> n >> V;
int root;
for (int i = 1; i <= n; i ++ ){
int a,b,c;
cin >> a >> b >> c;
v[i] = a;
val[i] = b;
if(c==-1){
root=i;
}else{
h[c].push_back({i});
}
}
dfs(root);
cout << f[root][V];
}
11. 背包问题求方案数
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 109+7 的结果。
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2
/*
先g[i] 求体积恰好为 i 的方案数
同时求出f[i] 体积为i的容量,可放下的最大价值
最后从0~V 找出看看哪些f[i] == f[V] ,方案数相加即可
这个不太好理解为什么是正确的,这个代码下面还有一个好理解的
- 这样其实可以理解
- 因为如果f[i] == f[V] ,那么他肯定是取到了最大价值
- 如果体积不恰好是i的话 g[i]就一定为0
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1010,mod = 1e9+7;
int v[N],val[N];
int f[N],g[N];
int main()
{
int n,V;
cin >> n >> V;
for (int i = 1; i <= n; i ++ ){
cin >> v[i] >> val[i];
}
g[0] = 1;
for (int i = 1; i <= n; i ++ ){
for (int j = V; j >= v[i]; j -- ){
int maxv = max(f[j],f[j-v[i]]+val[i]);
int s=0;
if(maxv == f[j]){
s = g[j];
}
if(maxv == f[j-v[i]]+val[i]){
s = (s + g[j-v[i]])%mod;
}
f[j] = maxv;
g[j] = s;
}
}
LL res=0;
// 这里要从0开始枚举,不然上面第二个数据中的结果计算会是0
for (int i = 0; i <= V; i ++ ){
if(f[i]==f[V]){
res = (res + g[i])%mod;
}
}
cout << res;
}
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int f[N], g[N];
int main()
{
cin >> n >> m;
// f[j] 体积恰好是j的最大价值,注意这种情况要初始化为 负无穷
// g[j] 体积恰好是j的方案数
memset(f, -0x3f, sizeof f);
f[0] = 0;
g[0] = 1;
for (int i = 0; i < n; i ++ )
{
int v, w;
cin >> v >> w;
for (int j = m; j >= v; j -- )
{
int maxv = max(f[j], f[j - v] + w);
int s = 0;
if (f[j] == maxv) s = g[j];
if (f[j - v] + w == maxv) s = (s + g[j - v]) % mod;
f[j] = maxv, g[j] = s;
}
}
int res = 0;
for (int i = 1; i <= m; i ++ )
if (f[i] > f[res])
res = i;
int sum = 0;
for (int i = 0; i <= m; i ++ )
if (f[i] == f[res])
sum = (sum + g[i]) % mod;
cout << sum << endl;
return 0;
}
12. 背包问题求具体方案
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。
物品编号范围是 1…N。
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
1 4
// 因为要求字典序最小方案
/*
直接从n开始倒着求,f[1][V]就是最优方案,那么直接从前向后推就是字典序最小方案
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1010,mod = 1e9+7;
int v[N],val[N];
int f[N][N],g[N];
int main()
{
int n,V;
cin >> n >> V;
for (int i = 1; i <= n; i ++ ){
cin >> v[i] >> val[i];
}
for (int i = n; i >= 1; i -- ){ //倒着来
for (int j = 0; j <= V; j ++ ){
f[i][j] = f[i+1][j];
if(j>=v[i]){
f[i][j] = max(f[i][j],f[i+1][j-v[i]]+val[i]);
}
}
}
int j=V;
for (int i = 1; i <= n; i ++ ){
if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+val[i]){
cout << i <<" ";
j-=v[i];
}
}
}
734. 能量石
岩石怪物杜达生活在魔法森林中,他在午餐时收集了 N 块能量石准备开吃。
由于他的嘴很小,所以一次只能吃一块能量石。
能量石很硬,吃完需要花不少时间。
吃完第 i 块能量石需要花费的时间为 Si 秒。
杜达靠吃能量石来获取能量。
不同的能量石包含的能量可能不同。
此外,能量石会随着时间流逝逐渐失去能量。
第 i 块能量石最初包含 Ei 单位的能量,并且每秒将失去 Li 单位的能量。
当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。
能量石中包含的能量最多降低至 0。
请问杜过吃能量石可以获得的最大能量是多少?
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含整数 N,表示能量石的数量。
接下来 N 行,每行包含三个整数 Si,Ei,Li。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y,其中 x 是组别编号(从 1 开始),y 是可以获得的最大能量值。
输入样例:
3
4
20 10 1
5 30 5
100 30 1
5 80 60
3
10 4 1000
10 3 1000
10 8 1000
2
12 300 50
5 200 0
输出样例:
Case #1: 105
Case #2: 8
Case #3: 500
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, M = 10010;
int n;
struct Stone
{
int s, e, l;
}stones[N];
bool cmp(Stone a, Stone b)
{
return a.s * b.l < b.s * a.l;
}
int f[N][M];
int main()
{
int T;
cin >> T;
for (int C = 1; C <= T; C ++ )
{
cin >> n;
int m = 0;
for (int i = 1; i <= n; i ++ )
{
int s, e, l;
cin >> s >> e >> l;
stones[i] = {s, e, l};
m += s;
}
sort(stones + 1, stones + 1 + n, cmp);
for (int i = 1; i <= n; i ++ )
for (int j = 0; j <= m; j ++ )
{
f[i][j] = f[i - 1][j];
if (j >= stones[i].s)
{
int s = stones[i].s, e = stones[i].e, l = stones[i].l;
f[i][j] = max(f[i][j], f[i - 1][j - s] + max(0, e - l * (j - s)));
}
}
int res = 0;
for (int i = 0; i <= m; i ++ ) res = max(res, f[n][i]);
printf("Case #%d: %d\n", C, res);
}
return 0;
}
487. 金明的预算方案
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。
今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
如果要买归类为附件的物品,必须先买该附件所属的主件。
每个主件可以有0个、1个或2个附件。
附件不再有从属于自己的附件。
金明想买的东西很多,肯定会超过妈妈限定的N元。
于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。
他还从因特网上查到了每件物品的价格(都是10元的整数倍)。
他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,…,jkj1,j2,…,jk,则所求的总和为:
v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk]v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk](其中为乘号)
请你帮助金明设计一个满足要求的购物单。
输入格式
输入文件的第1行,为两个正整数,用一个空格隔开:N m,其中N表示总钱数,m为希望购买物品的个数。
从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数v p q,其中v表示该物品的价格,p表示该物品的重要度(1~5),q表示该物品是主件还是附件。
如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号。
输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。
数据范围
N<32000,m<60,v<10000N<32000,m<60,v<10000
输入样例:
1000 5 800 2 0 400 5 1 300 5 1 400 3 0 500 2 0
*输出样例:
2200
// 有依赖的#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 32010,M=65;
int n,m;
int v[N],val[N];
vector<int>h[N];
int f[M][N];
int last=0;
void dfs(int u){
// 不考虑当前节点
for (int i = 0; i < h[u].size(); i ++ ){ // 物品
int son = h[u][i];
dfs(son);
for (int j = m-v[u]; j>=0 ; j -=10 ){ // 体积
for (int k = 0; k <= j; k +=10 ) {// 决策
f[u][j] = max(f[u][j],f[u][j-k]+f[son][k]);
}
}
}
for (int i = m; i >= v[u]; i -=10 ){
f[u][i] = f[u][i-v[u]] + val[u];
}
for (int i = 0; i < v[u]; i +=10 ){
f[u][i] = 0;
}
}
int main()
{
cin >> m >> n;
int cnt=1;
for (int i = 1; i <= n; i ++ ){
int a,p,q;
cin >> a >> p >> q; //价格,重要度(1~5),主件还是附件。
if(q>0){
h[q].push_back(cnt);
v[cnt] = a;
val[cnt] = a*p;
cnt++;
}else{
v[cnt] = a;
val[cnt] = a*p;
h[0].push_back(i);
cnt++;
}
}
dfs(0);
cout <<f[0][m];
}背包代码 10个数据过了9个
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 32010,M=65;
int n,m;
int v[N],val[N];
vector<int>h[N];
int f[M][N];
int last=0;
void dfs(int u){
// 不考虑当前节点
for (int i = 0; i < h[u].size(); i ++ ){ // 物品
int son = h[u][i];
dfs(son);
for (int j = m-v[u]; j>=0 ; j -=10 ){ // 体积
for (int k = 0; k <= j; k +=10 ) {// 决策
f[u][j] = max(f[u][j],f[u][j-k]+f[son][k]);
}
}
}
for (int i = m; i >= v[u]; i -=10 ){
f[u][i] = f[u][i-v[u]] + val[u];
}
for (int i = 0; i < v[u]; i +=10 ){
f[u][i] = 0;
}
}
int main()
{
cin >> m >> n;
int cnt=1;
for (int i = 1; i <= n; i ++ ){
int a,p,q;
cin >> a >> p >> q; //价格,重要度(1~5),主件还是附件。
if(q>0){
h[q].push_back(cnt);
v[cnt] = a;
val[cnt] = a*p;
cnt++;
}else{
v[cnt] = a;
val[cnt] = a*p;
h[0].push_back(i);
cnt++;
}
}
dfs(0);
cout <<f[0][m];
}
487. 金明的预算方案
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。
今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
如果要买归类为附件的物品,必须先买该附件所属的主件。
每个主件可以有0个、1个或2个附件。
附件不再有从属于自己的附件。
金明想买的东西很多,肯定会超过妈妈限定的N元。
于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。
他还从因特网上查到了每件物品的价格(都是10元的整数倍)。
他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,…,jkj1,j2,…,jk,则所求的总和为:
v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk]v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk](其中为乘号)
请你帮助金明设计一个满足要求的购物单。
输入格式
输入文件的第1行,为两个正整数,用一个空格隔开:N m,其中N表示总钱数,m为希望购买物品的个数。
从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数v p q,其中v表示该物品的价格,p表示该物品的重要度(1~5),q表示该物品是主件还是附件。
如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号。
输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。
数据范围
N<32000,m<60,v<10000N<32000,m<60,v<10000
输入样例:
1000 5 800 2 0 400 5 1 300 5 1 400 3 0 500 2 0
*输出样例:
2200
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 32010,M = 65;
vector<int>h[M];
int v[N],val[N];
int f[N];
bool st[N];
int main()
{
int m,n;
cin >> m >> n;
for (int i = 1; i <= n; i ++ ){
int a,p,q;
cin >> a >> p >> q; // 价格,重要度,对应的主件
v[i] = a;
val[i] = a*p;
if(q){
h[q].push_back(i);
}else{
st[i] = true; // 此物品为主件
}
}
for (int i = 1; i <= n; i ++ ){ // 寻找物品组
if(st[i]){
auto t = h[i];
if(t.size()>0){ // 只要h[i].size()大于0,说明i是一个有附件的主件
for(int l = m; l ; l--){ // 体积
for (int j = 0; j < t.size(); j ++ ){ // 对于每一个物品组
for (int k = 0; k < 1 <<t.size() ; k ++ ){ // 枚举物品组选哪几个
int a = v[i],b = val[i]; // 主件的体积,价值
for(int s=0; s < t.size(); s++ ){
if(k>>s&1){
a += v[t[s]];
b += val[t[s]];
}
}
if(l>=a){
f[l] = max(f[l],f[l-a]+b);
}
}
}
}
}else{
for(int l = m; l>=v[i] ; l--){ // 体积
int a = v[i],b = val[i]; // 主件的体积,价值
f[l] = max(f[l],f[l-a]+b);
}
}
}
}
cout << f[m];
}
1049. 大盗阿福
阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。
这条街上一共有 N 家店铺,每家店中都有一些现金。
阿福事先调查得知,只有当他同时洗劫了两家相邻的店铺时,街上的报警系统才会启动,然后警察就会蜂拥而至。
作为一向谨慎作案的大盗,阿福不愿意冒着被警察追捕的风险行窃。
他想知道,在不惊动警察的情况下,他今晚最多可以得到多少现金?
输入格式
输入的第一行是一个整数 T,表示一共有 T 组数据。
接下来的每组数据,第一行是一个整数 N ,表示一共有 N 家店铺。
第二行是 N 个被空格分开的正整数,表示每一家店铺中的现金数量。
每家店铺中的现金数量均不超过1000。
输出格式
对于每组数据,输出一行。
该行包含一个整数,表示阿福在不惊动警察的情况下可以得到的现金数量。
输入样例:
2
3
1 8 2
4
10 7 6 14
输出样例:
8
24
样例解释
对于第一组样例,阿福选择第2家店铺行窃,获得的现金数量为8。
对于第二组样例,阿福选择第1和4家店铺行窃,获得的现金数量为10+14=24。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int a[N];
int f[N][2]; // f[i][0] 第i家不选 f[i][1] 第i家选
int main()
{
int T;
cin >> T;
while (T -- ){
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
}
f[1][0] = 0;
f[1][1] = a[1];
for (int i = 2; i <= n; i ++ ){
f[i][0] = max(f[i-1][0],f[i-1][1]);
f[i][1] = f[i-1][0] + a[i];
}
cout << max(f[n][0],f[n][1])<<endl;
}
}
状态机模型
1057. 股票买卖 IV
给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润,你最多可以完成 k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。
输入格式
第一行包含整数 N 和 k,表示数组的长度以及你可以完成的最大交易数量。
第二行包含 N 个不超过 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
输入样例1:
3 2
2 4 1
输出样例1:
2
输入样例2:
6 2
3 2 6 5 0 3
输出样例2:
7
样例解释
样例1:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
样例2:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。共计利润 4+3 = 7.
下面代码的关键在于,
f[i][j][0]、f[i][j][1]表示到第i天为止最多进行j次交易,手中有股票,而不是恰好进行j次 正所谓是最多进行j次交易,所以初始化工作要满足f[1][j][1] = a[1],因为这样的话即使后面进行一次交易,f[n][k][0]也可以由f[n][k-1][0或1]推出来;
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int f[N][110][2];
int a[N];
int n,k;
/*
f[i][j][0] 最多进行了j次交易手中无货
- 上次就无货:f[i-1][j][0]
- 上次有货这次卖了 f[i-1][j-1][1] + a[i]
f[j][j][1] 最多进行了j次交易手中有货
- 上次没货,这次买进了 f[i-1][j][0] - a[i]
- 上次有货 f[i-1][j][1]
*/
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
}
// 第0天持有股票是不合法的
for (int i = 0; i <= k; i ++ ){
f[0][i][1] = -N;
}
// 因为是最多k次,所以任何一次的交易都可以从f[i-1][k-1][0]=0,推出来
for (int i = 1; i <= n; i ++ ){
for (int j = 0; j <= k; j ++ ){
f[i][j][0] = f[i-1][j][0];
f[i][j][1] = max(f[i-1][j][1],f[i-1][j][0]-a[i]);
if(j){ // 因为下面要j-1所以判断不越界
f[i][j][0] = max(f[i][j][0],f[i-1][j-1][1]+a[i]);
}
}
}
cout << f[n][k][0];
}
下面代码是
f[i][j][0]、f[i][j][1]表示到第i天为止恰好进行j次交易,手中有股票,而不是最多进行j次 因此只需要初始化f[1][0][0]和f[1][0][1]即可
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int f[N][110][2];
int a[N];
int n,k;
/*
f[i][j][0] 最多进行了j次交易手中无货
- 上次就无货:f[i-1][j][0]
- 上次有货这次卖了 f[i-1][j-1][1] + a[i]
f[j][j][1] 最多进行了j次交易手中有货
- 上次没货,这次买进了 f[i-1][j][0] - a[i]
- 上次有货 f[i-1][j][1]
*/
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
}
memset(f,-0x3f,sizeof f); // 因为是恰好k次,所以其他的状态都不合法
int ans=0;
f[1][0][0]=0;
f[1][0][1]=-a[1];
for(int i=2; i<=n; i++){
for(int j=0;j<=k;j++){
f[i][j][0] = f[i-1][j][0];
if(j){
f[i][j][0] = max(f[i][j][0],f[i-1][j-1][1]+a[i]);
}
ans = max(f[i][j][0],ans);
f[i][j][1] = max(f[i-1][j][1],f[i-1][j][0]-a[i]);
}
}
cout<<ans;
}
恰好K次也可以下面这种写法
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int f[N][110][2];
int a[N];
int n,k;
/*
f[i][j][0] 最多进行了j次交易手中无货
- 上次就无货:f[i-1][j][0]
- 上次有货这次卖了 f[i-1][j-1][1] + a[i]
f[j][j][1] 最多进行了j次交易手中有货
- 上次没货,这次买进了 f[i-1][j][0] - a[i]
- 上次有货 f[i-1][j][1]
*/
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
}
memset(f,-0x3f,sizeof f); // 因为是恰好k次,所以其他的状态都不合法
int ans=0;
f[0][0][0]=0;
for(int i=1; i<=n; i++){
for(int j=0;j<=k;j++){
f[i][j][0] = f[i-1][j][0];
if(j){
f[i][j][0] = max(f[i][j][0],f[i-1][j-1][1]+a[i]);
}
ans = max(f[i][j][0],ans);
f[i][j][1] = max(f[i-1][j][1],f[i-1][j][0]-a[i]);
}
}
cout<<ans;
}
1058. 股票买卖 V
给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
输入格式
第一行包含整数 N,表示数组长度。
第二行包含 N 个不超过 10000 的正整数,表示完整的数组。
输出格式
输出一个整数,表示最大利润。
输入样例:
5
1 2 3 0 2
输出样例:
3
样例解释
对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出],第一笔交易可得利润 2-1 = 1,第二笔交易可得利润 2-0 = 2,共得利润 1+2 = 3。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int f[N][4];
int a[N];
int n,k;
/*
f[i][0] 手中无货,也不在冷冻期
- 上次就无货:f[i-1][0]
- 上次在冷冻期 f[i-1][3]
f[i][1] 手中有货
- 上次就有货
- 上次没货,这次买进了 f[i-1][0] - a[i]
- 上次在冷冻期 f[i-1][3] - a[i]
f[i][2] 手中无货,刚卖
- 上次有货这次卖了 f[i-1][1] + a[i]
f[i][3] 正在冷冻期
- f[i-1][2]
*/
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
}
// 第0天持有股票是不合法的
for (int i = 0; i <= k; i ++ ){
f[0][1] = -N;
}
for (int i = 1; i <= n; i ++ ){
f[i][0] = max(f[i-1][0],f[i-1][3]);
f[i][1] = max(max(f[i-1][0],f[i-1][3])-a[i],f[i-1][1]);
f[i][2] = f[i-1][1] + a[i];
f[i][3] = f[i-1][2];
}
cout << max(max(f[n][0],f[n][2]),f[n][3]);
}
831. KMP字符串
给定一个模式串 S,以及一个模板串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 P 在模式串 S 中多次作为子串出现。
求出模板串 P 在模式串 S 中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P 的长度。
第二行输入字符串 P。
第三行输入整数 M,表示字符串 S 的长度。
第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
输入样例:
3
aba
5
ababa
输出样例:
0 2
#include <iostream>
using namespace std;
const int N = 100010, M = 1000010;
int n, m;
int ne[N];
char s[M], p[N];
int main()
{
cin >> n >> p + 1 >> m >> s + 1;
for (int i = 2, j = 0; i <= n; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
/* 上面为什么要j+1
那看看假设取j的话,不用j+1代码怎么写
*/
for (int i = 1, j = 0; i <= m; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == n)
{
printf("%d ", i - n);
j = ne[j];
}
}
return 0;
}
1052. 设计密码
你现在需要设计一个密码 S,S 需要满足:
S 的长度是 N;
S 只包含小写英文字母;
S 不包含子串 T;
例如:abc 和 abcde 是 abcde 的子串,abd 不是 abcde 的子串。
请问共有多少种不同的密码满足要求?
由于答案会非常大,请输出答案模 109+7 的余数。
输入格式
第一行输入整数N,表示密码的长度。
第二行输入字符串T,T中只包含小写字母。
输出格式
输出一个正整数,表示总方案数模 109+7 后的结果。
输入样例1:
2
a
输出样例1:
625
输入样例2:
4
cbc
输出样例2:
456924
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 55,mod = 1e9+7;
int ne[N],f[N][N];
int n,m;
string p;
int main()
{
cin >> n >> p;
p = " "+p;
m = p.size()-1;
for (int i = 2, j=0 ; i <= m; i ++ ){
while(j&&p[i]!=p[j+1]){
j=ne[j];
}
if(p[i]==p[j+1]){
j++;
}
ne[i] = j;
}
f[0][0] = 1;
for (int i = 0; i < n; i ++ ){ // 长度
for (int j = 0; j < m; j ++ ){ // 走到p(模板串)哪个位置上
for (int k = 0; k < 26; k ++ ){ // 枚举下一个位置的字母
int u = j;
while(u&&p[u+1]!=k+'a'){
u = ne[u];
}
if(p[u+1]==k+'a'){
u++;
}
f[i+1][u] = (f[i+1][u] + f[i][j])%mod;
}
}
}
int res=0;
for (int i = 0; i < m; i ++ ){
res = (res+f[n][i])%mod;
}
cout << res;
}
其实是可以加一个下面的优化的
1053. 修复DNA
生物学家终于发明了修复DNA的技术,能够将包含各种遗传疾病的DNA片段进行修复。
为了简单起见,DNA看作是一个由’A’, ‘G’ , ‘C’ , ‘T’构成的字符串。
修复技术就是通过改变字符串中的一些字符,从而消除字符串中包含的致病片段。
例如,我们可以通过改变两个字符,将DNA片段”AAGCAG”变为”AGGCAC”,从而使得DNA片段中不再包含致病片段”AAG”,”AGC”,”CAG”,以达到修复该DNA片段的目的。
需注意,被修复的DNA片段中,仍然只能包含字符’A’, ‘G’ , ‘C’ , ‘T’。
请你帮助生物学家修复给定的DNA片段,并且修复过程中改变的字符数量要尽可能的少。
输入格式
输入包含多组测试数据。
每组数据第一行包含整数N,表示致病DNA片段的数量。
接下来N行,每行包含一个长度不超过20的非空字符串,字符串中仅包含字符’A’, ‘G’ , ‘C’ , ‘T’,用以表示致病DNA片段。
再一行,包含一个长度不超过1000的非空字符串,字符串中仅包含字符’A’, ‘G’ , ‘C’ , ‘T’,用以表示待修复DNA片段。
最后一组测试数据后面跟一行,包含一个0,表示输入结束。
输出格式
每组数据输出一个结果,每个结果占一行。
输入形如”Case x: y”,其中x为测试数据编号(从1开始),y为修复过程中所需改变的字符数量的最小值,如果无法修复给定DNA片段,则y为”-1”。
输入样例:
2
AAA
AAG
AAAG
2
A
TG
TGAATG
4
A
G
C
T
AGT
0
输出样例:
Case 1: 1
Case 2: 4
Case 3: -1
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int tr[N][4], cnt[N], idx,q[N],ne[N];
int f[N][N];
int get(char c){
if(c=='A')return 0;
if(c=='G')return 1;
if(c=='C')return 2;
return 3;
}
void insert(char str[]) // 将str插入Trie中
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = get(str[i]);
if (!tr[p][u]) tr[p][u] = ++ idx;
p = tr[p][u];
}
cnt[p] ++ ; // 记录单词出现次数
}
void build() // 创建AC自动机
{
int hh = 0, tt = -1;
for (int i = 0; i < 4; i ++ )
if (tr[0][i])
q[ ++ tt] = tr[0][i];
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = 0; i < 4; i ++ )
{
int p = tr[t][i];
if (!p) tr[t][i] = tr[ne[t]][i];
else
{
ne[p] = tr[ne[t]][i];
cnt[p] += cnt[ne[p]];
q[ ++ tt] = p;
}
}
}
}
int main()
{
int n,T=1;
while(cin>>n&&n){
memset(tr,0,sizeof tr);
memset(cnt,0,sizeof cnt);
memset(q,0,sizeof q);
memset(ne,0,sizeof ne);
idx=0;
for (int i = 0; i < n; i ++ ){
char str[N];
cin >> str;
insert(str);
}
build();
string s;
cin >> s;
s = " "+s;
memset(f,0x3f,sizeof f);
f[0][0]=0;
for (int i = 0; i < s.size()-1; i ++ ){ // 枚举长度
for (int j = 0; j <= idx; j ++ ){ // 枚举当前走到哪个节点
for (int k = 0; k < 4; k ++ ){ // 接下来我可以选择哪个节点
int u = tr[j][k];
int t = get(s[i+1])!=k;
if(!cnt[u]){
f[i+1][u] = min(f[i+1][u],f[i][j]+t);
}
}
}
}
int res=0x3f3f3f3f;
for (int i = 0; i <=idx; i ++ ){
res = min(res,f[s.size()-1][i]);
}
if(res==0x3f3f3f3f){
cout << "Case "<<T<<": -1"<<endl;
}else{
cout << "Case "<<T<<": "<<res<<endl;
}
T++;
}
}
状态压缩DP
1064. 小国王
在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。


#include <iostream>
#include <cstring>
#include <algorithm>
typedef long long LL;
using namespace std;
const int N = 1<<10,M=12,K=110;
LL f[M][K][N]; // 已摆好前i行, 摆了j个国王,第i行状态是s
vector<int>st;
vector<int>h[N];
int cnt[N];
int n,m;
bool check(int x){
for (int i = 0; i < n; i ++ ){
if(x>>i&1 && (x>>i+1)&1){
return false;
}
}
return true;
}
int count(int x){
int res=0;
while(x){
res++;
x-=(x&(-x));
}
return res;
}
int main()
{
cin >> n >> m;
// 预处理所有状态
for (int i = 0; i < 1<<n; i ++ ){
if(check(i)){
st.push_back(i);
cnt[i] = count(i);
}
}
// 处理哪些状态之间能够转移
for (int i = 0; i < st.size(); i ++ ){
for (int j = 0; j < st.size(); j ++ ){
int a = st[i],b=st[j];
if((a&b)==0&&check(a|b)){
h[i].push_back(j);
}
}
}
f[0][0][0] = 1;
for (int i = 1; i <= n+1; i ++ ){ // 行号
for (int j = 0; j <= m; j ++ ) {// 国王
for (int k = 0; k < st.size(); k ++ ){ // 状态
int a = st[k],b;
for(auto s:h[k]){
b = st[s];
int t = j - cnt[a];
if(t>=0){
f[i][j][a] += f[i-1][t][b];
}
}
}
}
}
cout << f[n+1][m][0];
}
327. 玉米田
农夫约翰的土地由 M×N 个小方格组成,现在他要在土地里种植玉米。
非常遗憾,部分土地是不育的,无法种植。
而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
现在给定土地的大小,请你求出共有多少种种植方法。
土地上什么都不种也算一种方法。
输入格式
第 1 行包含两个整数 M 和 N。
第 2..M+1 行:每行包含 N 个整数 0 或 1,用来描述整个土地的状况,1 表示该块土地肥沃,0 表示该块土地不育。
输出格式
输出总种植方法对 10^8 取模后的值。
输入样例:
2 3
1 1 1
0 1 0
输出样例:
9
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 14,M = 1<<12,mod = 1e8;
int n,m;
int g[N][N],f[N][M];
vector<int>h[N]; // 每一行的合法状态
bool check(int row,int s){
for (int i = 0; i < m; i ++ ){
if(s>>i&1&&(s>>i+1)&1){ // 相邻
return false;
}
if(s>>i&1&&g[row][i+1]==0){ // 不育地
return false;
}
}
return true;
}
int main()
{
cin >> n>>m;
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= m; j ++ ){
cin >> g[i][j];
}
}
for (int i = 1; i <= n; i ++ ){
for (int j = 0; j < 1<<m; j ++ ){
if(check(i,j)){
h[i].push_back(j);
}
}
}
h[0].push_back(0);
f[0][0]=1;
for (int i = 1; i <= n+1; i ++ ){ // 第i行
for(auto j: h[i]){ // 第i行合法状态
for(auto k: h[i-1]){ // 第i-1行合法状态
if((j&k)==0){
f[i][j] = (f[i][j]+f[i-1][k])%mod;
}
}
}
}
int res=0;
for(auto k:h[n]){
res = (res+f[n][k])%mod;
}
cout << res;
}
int main()
{
// 上面代码也可以改成如下
h[0].push_back(0); // 因为下面要用合法状态转移,所以第0行的合法状态是0
h[n+1].push_back(0); // 最后一行的合法状态也是0
f[0][0]=1;
for (int i = 1; i <= n+1; i ++ ){ // 第i行
for(auto j: h[i]){ // 第i行合法状态
for(auto k: h[i-1]){ // 第i-1行合法状态
if((j&k)==0){
f[i][j] = (f[i][j]+f[i-1][k])%mod;
}
}
}
}
cout << f[n+1][0];
}
292. 炮兵阵地
司令部的将军们打算在 N×M 的网格地图上部署他们的炮兵部队。
一个 N×MN×M 的地图由 NN 行 MM 列组成,地图的每一格可能是山地(用 H 表示),也可能是平原(用 P 表示),如下图。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
图上其它白色网格均攻击不到。
从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入格式
第一行包含两个由空格分割开的正整数,分别表示 NN 和 MM;
接下来的 NN 行,每一行含有连续的 MM 个字符(P 或者 H),中间没有空格。按顺序表示地图中每一行的数据。
输出格式
仅一行,包含一个整数 KK,表示最多能摆放的炮兵部队的数量。
输入样例:
5 4 PHPP PPHH PPPP PHPP PHHP
输出样例:
6
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 110,M = 1<<10;
int f[N][M][M];
vector<int>h[N];
int cnt[M];
char g[N][20];
int n,m;
bool check(int row,int st){
for (int i = 0; i < m; i ++ ){
if(st>>i&1 && g[row][i]=='H'){ // 地方不行
return false;
}
if(st>>i&1 && ((st>>i+1)&1||(st>>i+2)&1)){
return false;
}
}
return true;
}
int count(int x){
int res=0;
while(x){
res++;
x-=(x&(-x));
}
return res;
}
int main()
{
cin >> n >> m;
for (int i = 2; i <= n+1; i ++ ){
cin >> g[i];
}
for (int i = 2; i <= n+1; i ++ ){
for (int j = 0; j < 1<<m; j ++ ){
if(check(i,j)){
h[i].push_back(j);
cnt[j] = count(j);
}
}
}
h[0].push_back(0);
h[1].push_back(0);
h[n+2].push_back(0);
h[n+3].push_back(0);
for (int i = 2; i <= n+3; i ++ ){ // 行
for(auto j:h[i]) { // 合法状态
for(auto k: h[i-1]){ // i-1 行合法状态
if(j&k){
continue;
}
for(auto l:h[i-2]){ // i-2行合法状态
if(l&k||j&l){
continue;
}
f[i&1][j][k] = max(f[i&1][j][k],f[(i-1)&1][k][l]+cnt[j]);
}
}
}
}
cout << f[(n+3)&1][0][0];
}
524. 愤怒的小鸟
Kiana 最近沉迷于一款神奇的游戏无法自拔。
简单来说,这款游戏是在一个平面上进行的。
有一架弹弓位于 (0,0) 处,每次 Kiana 可以用它向第一象限发射一只红色的小鸟, 小鸟们的飞行轨迹均为形如 y=ax2+bx 的曲线,其中 a,b 是 Kiana 指定的参数,且必须满足 a<0。
当小鸟落回地面(即 x 轴)时,它就会瞬间消失。
在游戏的某个关卡里,平面的第一象限中有 n 只绿色的小猪,其中第 i 只小猪所在的坐标为 (xi,yi)。
如果某只小鸟的飞行轨迹经过了 (xi, yi),那么第 i 只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;
如果一只小鸟的飞行轨迹没有经过 (xi, yi),那么这只小鸟飞行的全过程就不会对第 i 只小猪产生任何影响。
例如,若两只小猪分别位于 (1,3) 和 (3,3),Kiana 可以选择发射一只飞行轨迹为 y=−x2+4x 的小鸟,这样两只小猪就会被这只小鸟一起消灭。
而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。
这款神奇游戏的每个关卡对 Kiana 来说都很难,所以 Kiana 还输入了一些神秘的指令,使得自己能更轻松地完成这个这个游戏。
这些指令将在输入格式中详述。
假设这款游戏一共有 T 个关卡,现在 Kiana 想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。
输入格式
第一行包含一个正整数 T,表示游戏的关卡总数。
下面依次输入这 T 个关卡的信息。
每个关卡第一行包含两个非负整数 n,m,分别表示该关卡中的小猪数量和 Kiana 输入的神秘指令类型。
接下来的 n 行中,第 i 行包含两个正实数 (xi,yi),表示第 i 只小猪坐标为 (xi,yi),数据保证同一个关卡中不存在两只坐标完全相同的小猪。
输出格式
输出的每一行包含一个正整数,表示相应的关卡中,消灭所有小猪最少需要的小鸟数量。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef pair<double, double> PDD;
const int N = 20,M = 1<<18;
PDD g[N];
int path[N][N];
int f[M];
int n,m;
int cmp(double x1,double x2){
if(fabs(x1-x2)<1e-6){
return 0;
}
if(x1>x2){
return 1;
}
return -1;
}
int main()
{
int T;
cin >> T;
while(T--){
cin >> n >> m;
for (int i = 0; i < n; i ++ ){
cin >> g[i].x >> g[i].y;
}
memset(path,0,sizeof path);
for (int i = 0; i < n; i ++ ){
path[i][i] = 1<<i;
for (int j = 1; j < n; j ++ ){
if(i==j){
continue;
}
if(cmp(g[i].x,g[j].x)==0){
continue;
}
double x1 = g[i].x;
double y1 = g[i].y;
double x2 = g[j].x;
double y2 = g[j].y;
// 解方程组
double a = (y1/x1-y2/x2)/(x1-x2);
double b = (y1/x1)-a*x1;
if(cmp(a,0)>=0){
continue;
}
// 选择x1,x2这两个点组成的抛物线能覆盖的点
for (int k = 0; k < n; k ++ ){
double x = g[k].x;
double y = g[k].y;
if(cmp(a*x*x+b*x,y)==0){
path[i][j]|=1<<k;
}
}
}
}
memset(f,0x3f,sizeof f);
f[0]=0;
for (int i = 0; i < 1<<n; i ++ ){
for (int j = 0; j < n; j ++ ){
if((i>>j&1)==0){
for (int k = 0; k < n; k ++ ){
f[i|path[j][k]] = min(f[i|path[j][k]],f[i]+1);
}
}
}
}
cout << f[(1<<n)-1]<<endl;
}
}
树形DP
1072. 树的最长路径
给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。
现在请你找到树中的一条最长路径。
换句话说,要找到一条路径,使得使得路径两端的点的距离最远。
注意:路径中可以只包含一个点。
输入格式
第一行包含整数 n。
接下来 n−1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。
输出格式
输出一个整数,表示树的最长路径的长度。
数据范围
1≤n≤10000,
1≤ai,bi≤n,
−105≤ci≤105
输入样例:
6
5 1 6
1 4 5
6 3 9
2 6 8
6 1 7
输出样例:
22
// 如果没有负权边,下面这种两遍dfs是可以的但是对于下面这个样例(存在负权边)算出来就不对了
// 从任意一个点x出发,寻找距离x最远的点y
// 再从点y出发,寻找距离y最远的点z,那么点y到点z即为树的直径
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define x first
#define y second
const int N = 1e5+10;
using namespace std;
typedef pair<int, int> PII;
int n;
int dist[N];
vector<PII>h[N];
void dfs(int u,int fa){
for (int i = 0; i <h[u].size(); i ++ ){
int j = h[u][i].x;
if(j==fa){
continue;
}
dist[j] = dist[u] + h[u][i].y;
dfs(j,u);
}
}
int main()
{
cin >> n;
for (int i = 0; i < n-1; i ++ ){
int a,b,c;
cin >> a >> b >> c;
h[a].push_back({b,c});
h[b].push_back({a,c});
}
dfs(1,-1);
int maxv = -1,root=1;
for (int i = 1; i <= n; i ++ ){
if(dist[i]>maxv){
root = i;
maxv = dist[i];
}
}
memset(dist, 0, sizeof dist);
dfs(root,-1);
maxv = -1;
for (int i = 1; i <= n; i ++ ){
if(dist[i]>maxv){
maxv = dist[i];
}
}
cout << maxv <<endl;
}
// 这种方法对于有负权边也可以正确处理
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define x first
#define y second
const int N = 1e5+10;
using namespace std;
typedef pair<int, int> PII;
int n,ans;
vector<PII>h[N];
int dfs(int u,int fa){
int dist = 0; // 从当前节点往下走的最大长度
int d1 = 0, d2 = 0; // 把u节点拎起来,的最大值,次大值
for (int i = 0; i < h[u].size(); i ++ ){
int j = h[u][i].x;
if(j==fa){
continue;
}
int d = dfs(j,u)+h[u][i].y;
dist = max(dist,d);
if(d>=d1){
d2 = d1;
d1 = d;
}else if(d>d2){
d2 = d;
}
}
ans = max(ans,d1+d2);
return dist;
}
int main()
{
cin >> n;
for (int i = 0; i < n-1; i ++ ){
int a,b,c;
cin >> a >> b >> c;
h[a].push_back({b,c});
h[b].push_back({a,c});
}
dfs(1,-1);
cout << ans;
}
846.树的重心
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式
第一行包含整数 n,表示树的结点数。
接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。
输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5+10;
vector<int>h[N];
int n,ans=N;
int dfs(int u,int fa){
int sum=1; // 以u为根的子树中节点数量(包括u)
int maxv=0; // 以u为根的子树当中最大的节点数量
for (int i = 0; i < h[u].size(); i ++ ){
int j = h[u][i];
if(j==fa){
continue;
}
int t = dfs(j,u);
sum += t;
maxv = max(maxv,t);
}
int t = max(maxv,n-sum); //
ans = min(ans,t);
return sum;
}
int main()
{
cin >> n ;
for (int i = 0; i < n-1; i ++ ){ // 注意这里是n-1
int a,b;
cin >> a >> b;
h[a].push_back(b);
h[b].push_back(a);
}
dfs(1,-1);
cout << ans;
}
1073. 树的中心
给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。
请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。
输入格式
第一行包含整数 n。
接下来 n−1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。
输出格式
输出一个整数,表示所求点到树中其他结点的最远距离。
输入样例:
5
2 1 1
3 2 1
4 3 1
5 1 1
输出样例:
2
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5+10;
vector<PII>h[N];
int n;
int d1[N],pid[N];
// d1[u]以节点u为根的子树当中,距离u最远的距离
// pid[u]取得最远距离得子节点是哪个
int d2[N];// d2[u]以节点u为根的子树当中,距离u的次大距离
int up[N];
int dfs1(int u,int fa){
for (int i = 0; i < h[u].size(); i ++ ){
int j = h[u][i].x;
if(j==fa){
continue;
}
int t = dfs1(j,u)+h[u][i].y;
if(t>=d1[u]){
d2[u] = d1[u];
d1[u] = t;
pid[u] = j;
}else if(t>=d2[u]){
d2[u] = t;
}
}
return d1[u];
}
int dfs2(int u,int fa){
for (int i = 0; i < h[u].size(); i ++ ){
int j = h[u][i].x;
if(j==fa){
continue;
}
if(pid[u]==j){
up[j] = max(d2[u],up[u]) + h[u][i].y;
}else{
up[j] = max(d1[u],up[u]) + h[u][i].y;
}
dfs2(j,u);
}
}
int main()
{
cin >> n ;
for (int i = 0; i < n-1; i ++ ){
int a,b,c;
cin >> a >> b >> c;
h[a].push_back({b,c});
h[b].push_back({a,c});
}
dfs1(1,-1);
dfs2(1,-1);
int res = d1[1];
for (int i = 2; i <= n; i ++ ){
res = min(res,max(d1[i],up[i]));
}
cout << res;
}
1075. 数字转换
如果一个数 x 的约数之和 y(不包括他本身)比他本身小,那么 x 可以变成 y,y 也可以变成 x。
例如,4 可以变为 3,1 可以变为 7。
限定所有数字变换在不超过 n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。
输入格式
输入一个正整数 n。
输出格式
输出不断进行数字变换且不出现重复数字的最多变换步数。
输入样例:
7
输出样例:
3
样例解释
一种方案为:4→3→1→7。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 5e5+10;
vector<int>h[N];
bool st[N];
int sum[N],n,ans;
int dfs(int u){
int d1=0,d2=0;
st[u] = true;
for (int i = 0; i < h[u].size(); i ++ ){
int j = h[u][i];
int t = dfs(j) + 1;
if(t>=d1){
d2 = d1;
d1 = t;
}else if(t>=d2){
d2 = t;
}
}
ans = max(ans,d1+d2);
return d1;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ){ // sum[i] 表示i的约数的和
for (int j = 2; j <= n/i; j ++ ){ // 因为不能包含本身,所以j从2开始
sum[i*j] +=i;
}
}
for (int i = 2; i <= n; i ++ ){ // 1到0是不能连边的, 因为题目要求正数
if(sum[i]<i){ // i的约数是唯一的,所以连一条从sum[i] 到i的边
h[sum[i]].push_back(i);
}
}
for (int i = 1; i <= n; i ++ ){ // 是森林不是树,所以得这样搜
if(!st[i]){
dfs(i);
}
}
cout << ans;
}
1074. 二叉苹果树
有一棵二叉苹果树,如果树枝有分叉,一定是分两叉,即没有只有一个儿子的节点。
这棵树共 N 个节点,编号为 1 至 N,树根编号一定为 1。
我们用一根树枝两端连接的节点编号描述一根树枝的位置。
一棵苹果树的树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留住多少苹。
这里的保留是指最终与1号点连通。
输入格式
第一行包含两个整数 N 和 Q,分别表示树的节点数以及要保留的树枝数量。
接下来 N−1 行描述树枝信息,每行三个整数,前两个是它连接的节点的编号,第三个数是这根树枝上苹果数量。
输出格式
输出仅一行,表示最多能留住的苹果的数量。
每根树枝上苹果不超过 30000 个。
输入样例:
5 2
1 3 1
1 4 10
2 3 20
3 5 20
输出样例:
21
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
vector<PII>h[N];
int n,m;
int f[N][N];
void dfs(int u,int fa){
for (int i = 0; i < h[u].size(); i ++ ){ // 物品
int son = h[u][i].x;
int w = h[u][i].y;
if(son==fa){
continue;
}
dfs(son,u);
for (int j = m; j ; j -- ){ // 体积 这里写j>=0也能过
for (int k = 0; k <= j-1; k ++ ){ // 决策
f[u][j] = max(f[u][j],f[u][j-k-1]+f[son][k]+w);
}
}
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n-1; i ++ ){
int a,b,c;
cin >> a >> b >> c;
h[a].push_back({b,c});
h[b].push_back({a,c});
}
dfs(1,-1);
cout << f[1][m];
}
323. 战略游戏
鲍勃喜欢玩电脑游戏,特别是战略游戏,但有时他找不到解决问题的方法,这让他很伤心。
现在他有以下问题。
他必须保护一座中世纪城市,这条城市的道路构成了一棵树。
每个节点上的士兵可以观察到所有和这个点相连的边。
他必须在节点上放置最少数量的士兵,以便他们可以观察到所有的边。
你能帮助他吗?
例如,下面的树:
只需要放置 11 名士兵(在节点 11 处),就可观察到所有的边。
输入格式
输入包含多组测试数据,每组测试数据用以描述一棵树。
对于每组测试数据,第一行包含整数 NN,表示树的节点数目。
接下来 NN 行,每行按如下方法描述一个节点。
节点编号:(子节点数目) 子节点 子节点 …
节点编号从 0 到 N−1,每个节点的子节点数量均不超过 1010,每个边在输入数据中只出现一次。
输出格式
对于每组测试数据,输出一个占据一行的结果,表示最少需要的士兵数。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1510;
vector<int>h[N];
bool st[N];
int root;
int dist[N][2];
void dfs(int u){
dist[u][0] = 0;
dist[u][1] = 1;
for (int i = 0; i < h[u].size(); i ++ ){
int son = h[u][i];
dfs(son);
dist[u][0] += dist[son][1];
dist[u][1] += min(dist[son][0],dist[son][1]);
}
}
int main()
{
int n;
while(cin>>n&&n){
memset(dist, 0, sizeof dist);
memset(st, 0, sizeof st);
for (int i = 0; i < n; i ++ ){
int a,b,cnt;
scanf("%d:(%d)", &a,&cnt); // 格式化读入
h[a].clear();
for (int j = 0; j < cnt; j ++ ){
cin >> b;
h[a].push_back(b);
st[b] = true;
}
}
for (int i = 0; i < n; i ++ ){
if(!st[i]){
root = i;
break;
}
}
dfs(root);
cout << min(dist[root][0],dist[root][1])<<endl;
}
}
1077. 皇宫看守
太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。
皇宫各个宫殿的分布,呈一棵树的形状,宫殿可视为树中结点,两个宫殿之间如果存在道路直接相连,则该道路视为树中的一条边。
已知,在一个宫殿镇守的守卫不仅能够观察到本宫殿的状况,还能观察到与该宫殿直接存在道路相连的其他宫殿的状况。
大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。
可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。
帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。
输入格式
输入中数据描述一棵树,描述如下:
第一行 n,表示树中结点的数目。
第二行至第 n+1 行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号 i,在该宫殿安置侍卫所需的经费 k,该结点的子结点数 m,接下来 m 个数,分别是这个结点的 m 个子结点的标号 r1,r2,…,rm。
对于一个 n 个结点的树,结点标号在 1 到 n 之间,且标号不重复。
输出格式
输出一个整数,表示最少的经费。
输入样例:
6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0
输出样例:
25
样例解释:
在2、3、4结点安排护卫,可以观察到全部宫殿,所需经费最少,为 16 + 5 + 4 = 25。

/*
这道题不能下面这样做,不然就会出现上面的错误
*/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
int n;
const int N = 1510;
vector<int>h[N];
int w[N];
bool st[N];
int d[N][2];
void dfs(int u){
d[u][0] = 0;
d[u][1] = w[u];
for (int i = 0; i < h[u].size(); i ++ ){
int son = h[u][i];
dfs(son);
d[u][0]+=d[son][1];
d[u][1]+=min(d[son][0],d[son][1]);
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ){
int a,k,cnt;
cin >> a >> k >> cnt;
w[a] = k;
for (int j = 0; j < cnt; j ++ ){
int x;
cin >> x;
st[x] = true;
h[a].push_back(x);
}
}
int root;
for (int i = 1; i <= n; i ++ ){
if(!st[i]){
root = i;
}
}
dfs(root);
cout << min(d[root][0],d[root][1]);
}
正确解法如下:
#include <iostream>
#include <cstring>
using namespace std;
/*
以下注释为早期笔记,希望对你有所帮助
状态机 + 树形Dp问题
状态表示:
f(i, 0):第i号结点被他的父结点安排的守卫看住的方案数
f(i, 1):第i号结点被他的子结点安排的守卫看住的方案数
f(i, 2):第i号结点自己安排守卫看住的方案数
状态计算:(j是i的子结点)
f(i, 0) = sum{min(f(j,1), f(j,2))}
i是被他父结点看住的,那他的子结点要么自己看自己,要么被自己的子结点看住
f(i, 1) = min{w(k) + f(k, 2) - sum{min(f(j,1), f(j,2))}}
i如果是被子结点看住的,那么就要枚举他是被哪个子结点看住的所有方案,对所有方案求最小值
这里的sum不包括j==k的情况,因此需要手动额外减去
f(i, 2) = sum{min(f(j,0), f(j,1), f(j,2))} + w(u)
i是被自己看住的,那他的子结点可以被父结点看住,可以自己看自己,也可以被自己的子结点看住
*/
const int N = 1510;
int n;
int h[N], w[N], e[N], ne[N], idx;
int f[N][3];
bool st[N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u) {
f[u][0] = 0;
f[u][1] = 1e9; //f[u][1]求最小值,初始化为最大值
f[u][2] = w[u];//初始化放置自己的代价
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
dfs(j);
f[u][0] += min(f[j][1], f[j][2]);
f[u][2] += min(min(f[j][0], f[j][1]), f[j][2]);
}
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
//f[u][0]中记录了sum{min(f(j,1), f(j,2))},再从中减去对应的贡献即可得到remain ver
f[u][1] = min(f[u][1], f[u][0] + f[j][2] - min(f[j][1], f[j][2]));
}
}
int main() {
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i <= n; ++i) {
int id, cnt, cost;
cin >> id >> cost >> cnt;
w[id] = cost;
while (cnt--) {
int ver;
cin >> ver;
add(id, ver);
st[ver] = true;
}
}
int root = 1;
while (st[root]) ++root;
dfs(root);
printf("%d\n", min(f[root][1], f[root][2]));
return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
int n;
const int N = 1510;
vector<int>h[N];
int w[N];
bool st[N];
int d[N][3];
/*
状态机 + 树形Dp问题
状态表示:
f(i, 0):第i号结点被他的父结点安排的守卫看住的方案数
f(i, 1):第i号结点被他的子结点安排的守卫看住的方案数
f(i, 2):第i号结点自己安排守卫看住的方案数
状态计算:(j是i的子结点)
f(i, 0) = sum{min(f(j,1), f(j,2))}
i是被他父结点看住的,那他的子结点要么自己看自己,要么被自己的子结点看住
f(i, 1) = min{w(k) + f(k, 2) - sum{min(f(j,1), f(j,2))}}
i如果是被子结点看住的,那么就要枚举他是被哪个子结点看住的所有方案,对所有方案求最小值
这里的sum不包括j==k的情况,因此需要手动额外减去
算sum就要枚举其所有的儿子,所以得有两次循环,第一次循环求出sum,第二次循环
再枚举选哪个节点看住
f(i, 2) = sum{min(f(j,0), f(j,1), f(j,2))} + w(u)
i是被自己看住的,那他的子结点可以被父结点看住,可以自己看自己,也可以被自己的子结点看住
*/
void dfs(int u){
d[u][0] = 0;
d[u][1] = 1e9;
d[u][2] = w[u];
for (int i = 0; i < h[u].size(); i ++ ){
int son = h[u][i];
dfs(son);
d[u][0]+=min(d[son][1],d[son][2]);
d[u][2]+=min(min(d[son][1],d[son][0]),d[son][2]);
}
for (int i = 0; i < h[u].size(); i ++ ){
int son = h[u][i];
d[u][1] = min(d[u][1],d[u][0]+d[son][2]-min(d[son][1],d[son][2]));
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ){
int a,k,cnt;
cin >> a >> k >> cnt;
w[a] = k;
for (int j = 0; j < cnt; j ++ ){
int x;
cin >> x;
st[x] = true;
h[a].push_back(x);
}
}
int root;
for (int i = 1; i <= n; i ++ ){
if(!st[i]){
root = i;
break;
}
}
dfs(root);
cout << min(d[root][1],d[root][2]);
}
区间DP
1068. 环形石子合并
将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。
第二行包含 n 个整数,分别表示每堆石子的数量。
输出格式
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
输入样例:
4
4 5 9 4
输出样例:
43
54
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 410;
int a[N],sum[N];
int f[N][N]; // 最大和
int g[N][N]; // 最小和
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
a[i+n]=a[i];
}
for (int i = 1; i <= 2*n; i ++ ){
sum[i] = sum[i-1]+a[i];
}
/*
考虑边界情况
f[i][i+1] = f[i][i] + f[i+1][i+1] + sum[i+1]-sum[i-1]
所以在下面求最小值的时候g[i][i]要初始化为0
*/
memset(g, 0x3f, sizeof g);
for (int len = 1; len <= 2*n; len ++ ){
for (int i = 1; i+len-1 <=2*n; i ++ ){
g[i][i]=0; // 这里一定要初始化为0
int j = i+len-1;
for (int k = i; k < j; k ++ ){
f[i][j] = max(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
g[i][j] = min(g[i][j],g[i][k]+g[k+1][j]+sum[j]-sum[i-1]);
}
}
}
int maxv = 0;
int minv = 0x3f3f3f3f;
for (int i = 1; i+n-1 <= 2*n; i ++ ){
maxv = max(maxv,f[i][i+n-1]);
minv = min(minv,g[i][i+n-1]);
}
cout << minv <<endl;
cout << maxv;
}
320. 能量项链
在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链,在项链上有 N 颗能量珠。
能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。
并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。
因为只有这样,通过吸盘(吸盘是 Mars 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。
如果前一颗能量珠的头标记为 m,尾标记为 r,后一颗能量珠的头标记为 r,尾标记为 n,则聚合后释放的能量为 m×r×n(Mars 单位),新产生的珠子的头标记为 m,尾标记为 n。
需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。
显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设 N=4,4 颗珠子的头标记与尾标记依次为 (2,3)(3,5)(5,10)(10,2)。
我们用记号 ⊕ 表示两颗珠子的聚合操作,(j⊕k) 表示第 j,k 两颗珠子聚合后所释放的能量。则
第 4、1 两颗珠子聚合后释放的能量为:(4⊕1)=10×2×3=60。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为 ((4⊕1)⊕2)⊕3)=10×2×3+10×3×5+10×5×10=710。
输入格式
输入的第一行是一个正整数 N,表示项链上珠子的个数。
第二行是 N 个用空格隔开的正整数,所有的数均不超过 1000,第 i 个数为第 i 颗珠子的头标记,当 i
输出格式
输出只有一行,是一个正整数 E,为一个最优聚合顺序所释放的总能量。
数据范围
4≤N≤100,
1≤E≤2.1×109
输入样例:
4
2 3 5 10
输出样例:
710
这里解释一下为什么是枚举到 i+len-1 < 2n
可以看到只要右边界枚举到2n-1就可以了,如果枚举到2*n相等于重复枚举了一遍2-12
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210;
int sum[N];
int a[N];
int f[N][N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
a[i+n] = a[i];
}
for (int len = 1; len <=2* n; len ++ ){
for (int i = 1; i+len-1 <=2*n-1; i ++ ){ // 这里枚举到2*n-1
f[i][i]=0;
int j = i+len-1;
for (int k = i; k < j; k ++ ){
f[i][j] = max(f[i][j],f[i][k]+f[k+1][j]+a[i]*a[k+1]*a[j+1]);
// 注意这里乘的是a[j+1]
}
}
}
int res=0;
for (int i = 1; i+n-1 <= 2*n; i ++ ){
res = max(res,f[i][i+n-1]);
}
cout << res;
}
479. 加分二叉树
设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。
每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:
subtree的左子树的加分 × subtree的右子树的加分 + subtree的根的分数
若某个子树为空,规定其加分为 1。
叶子的加分就是叶节点本身的分数,不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树 tree。
要求输出:
(1)tree的最高加分
(2)tree的前序遍历
输入格式
第 1 行:一个整数 n,为节点个数。
第 2 行:n 个用空格隔开的整数,为每个节点的分数(0<分数<100)。
输出格式
第 1 行:一个整数,为最高加分(结果不会超过int范围)。
第 2 行:n 个用空格隔开的整数,为该树的前序遍历。如果存在多种方案,则输出字典序最小的方案。
输入样例:
5
5 7 1 2 10
输出样例:
145
3 1 2 4 5
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 35;
int a[N],n;
int f[N][N],g[N][N];
void dfs(int l,int r){
if(l>r){
return;
}
int root = g[l][r];
cout << root <<" ";
dfs(l,root-1);
dfs(root+1,r);
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
}
for (int len = 1; len <= n; len++ ){
for (int i = 1; i+len-1 <= n; i ++ ){
int j = i+len-1;
for (int k = i; k <= j; k ++ ){ // 以K为根节点
int l = max(f[i][k-1],1); // 左子树的最大值
int r = max(f[k+1][j],1); // 右子树的最大值
int score = l*r + a[k];
if(i==j){
score = a[i];
}
if(score>f[i][j]){
f[i][j] = score;
g[i][j] = k;
}
}
}
}
cout << f[1][n]<<endl;;
dfs(1,n);
}

这道题最需要注意的地方就是 权值是在点上,而不是在格子里面

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 16,M = 10,INF = 1e9;
int s[M][M];
double f[M][M][M][M][N];
int n,m=8;
double X; // 均值
int get_sum(int x1,int y1,int x2,int y2){
return s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1];
}
double get(int x1,int y1,int x2,int y2){
double sum = get_sum(x1,y1,x2,y2)-X;
return sum*sum/n;
}
double dp(int x1,int y1,int x2,int y2,int k){
auto &v = f[x1][y1][x2][y2][k];
if(v>=0){
return v;
}
if(k==1){
return v = get(x1,y1,x2,y2);
}
v = INF;
for (int i = x1; i < x2; i ++ ){ // 横着切
v = min(v, dp(x1,y1,i,y2,k-1) + get(i+1,y1,x2,y2)); // 分割上边
v = min(v, dp(i+1,y1,x2,y2,k-1) + get(x1,y1,i,y2)); // 分割下边
}
for (int j = y1; j < y2; j ++ ){
v = min(v, dp(x1,y1,x2,j,k-1) + get(x1,j+1,x2,y2));
v = min(v, dp(x1,j+1,x2,y2,k-1)+get(x1,y1,x2,j));
}
return v;
}
int main()
{
cin >> n;
for (int i = 1; i <= m; i ++ ){
for (int j = 1; j <= m; j ++ ){
cin >> s[i][j];
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
}
}
memset(f, -1, sizeof f);
X = (double)s[m][m]/n;
printf("%.3lf",sqrt(dp(1,1,8,8,n)));
}
单调队列优化DP
135. 最大子序和
——— 选择不超过m的连续子序列
输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。
注意: 子序列的长度至少是 1。
输入格式
第一行输入两个整数 n,m。
第二行输入 n 个数,代表长度为 n 的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。
数据范围
1≤n,m≤300000
输入样例:
6 4
1 -3 5 1 -2 3
输出样例:
7
这个单调增的队列,一开始一定要push一个0进去

#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 3e5+10;
int n,m;
int s[N];
int res=-1e9;
deque<int>q;
// 单调增队列
void push(int x){ // 单调队列存的是下标
while(!q.empty() && s[x] <= s[q.back()]){
q.pop_back();
}
q.push_back(x);
if(x - q.front()>=m){ // 队列里面最多只能有m-1个元素
q.pop_front();
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ){
cin >> s[i];
s[i]+=s[i-1];
}
push(0); // 这里得写,因为是前缀和s[r]-s[l-1]
for (int i = 1; i <= n; i ++ ){
res =max(res,s[i]-s[q.front()]);
push(i);
}
cout << res;
}
1087. 修剪草坪
——— 选择不能超过连续m
在一年前赢得了小镇的最佳草坪比赛后,FJ 变得很懒,再也没有修剪过草坪。
现在,新一轮的最佳草坪比赛又开始了,FJ 希望能够再次夺冠。
而,FJ 的草坪非常脏乱,因此,FJ 只能够让他的奶牛来完成这项工作。
FJ 有 N 只排成一排的奶牛,编号为 1 到 N。
每只奶牛的效率是不同的,奶牛 i 的效率为 Ei。
编号相邻的奶牛们很熟悉,如果 FJ 安排超过 K 只编号连续的奶牛,那么这些奶牛就会罢工去开派对。
因此,现在 FJ 需要你的帮助,找到最合理的安排方案并计算 FJ 可以得到的最大效率。
注意,方案需满足不能包含超过 K 只编号连续的奶牛。
输入格式
第一行:空格隔开的两个整数 N 和 K;
第二到 N+1 行:第 i+1 行有一个整数 Ei。
输出格式
共一行,包含一个数值,表示 FJ 可以得到的最大的效率值。
数据范围
1≤N≤105,
0≤Ei≤109
输入样例:
5 2
1
2
3
4
5
输出样例:
12
样例解释
FJ 有 5 只奶牛,效率分别为 1、2、3、4、5。
FJ 希望选取的奶牛效率总和最大,但是他不能选取超过 2 只连续的奶牛。
因此可以选择第三只以外的其他奶牛,总的效率为 1 + 2 + 4 + 5 = 12。
// 暴力解法
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int a[N];
LL f[2][N];
int n,m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
}
LL t=0;
/*
f[i][j]
考虑前i头牛,最后一段最多选了j头牛的最大效率
f[i][j] = 考虑前i-1头牛,最后一段最多选了j-1头的最大效率 + 当前牛的效率
f[i][0] = 考虑前i-1头牛的最大效率
*/
for (int i = 1; i <= n; i ++ ){
f[i&1][0] = t;
for (int j = 1; j <= m; j ++ ){
f[i&1][j] = f[(i-1)&1][j-1] + a[i];
t = max(t,f[i&1][j]);
}
}
LL res=0;
for (int j = 0; j <= m; j ++ ){
res = max(f[n&1][j],res);
}
cout << res ;
}

#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
LL s[N];
LL f[N];
LL g[N];
int n,m;
deque<LL>q;
void push(int x){ // 下标,单调减队列
while(!q.empty()&&g[x]>=g[q.back()]){
q.pop_back();
}
q.push_back(x);
if(x - q.front()>=m){
q.pop_front();
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ){
cin >> s[i];
s[i] += s[i-1];
}
LL t=0;
push(0);
for (int i = 1; i <= n; i ++ ){
int j = q.front();
f[i] = max(f[i-1],g[j]+s[i]);
g[i] = f[i-1]-s[i];
push(i);
}
cout << f[n];
}
1088. 旅行问题
——— 起点固定,在m个数中选最小
John 打算驾驶一辆汽车周游一个环形公路。
公路上总共有 n 个车站,每站都有若干升汽油(有的站可能油量为零),每升油可以让汽车行驶一千米。
John 必须从某个车站出发,一直按顺时针(或逆时针)方向走遍所有的车站,并回到起点。
在一开始的时候,汽车内油量为零,John 每到一个车站就把该站所有的油都带上(起点站亦是如此),行驶过程中不能出现没有油的情况。
任务:判断以每个车站为起点能否按条件成功周游一周。
输入格式
第一行是一个整数 n,表示环形公路上的车站数;
接下来 n 行,每行两个整数 pi,di,分别表示表示第 i 号车站的存油量和第 i 号车站到 顺时针方向 下一站的距离。
输出格式
输出共 n 行,如果从第 i 号车站出发,一直按顺时针(或逆时针)方向行驶,能够成功周游一圈,则在第 i 行输出 TAK,否则输出 NIE。
输入样例:
5
3 1
1 2
5 2
0 1
5 4
输出样例:
TAK
NIE
TAK
NIE
TAK
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <cmath>
const int N = 2e6+10;
using namespace std;
typedef long long LL;
LL s[N];
int value[N],cost[N];
bool st[N];
int n;
deque<int>q;
// 找最小值,单调增队列
void push(int x){
while(!q.empty()&&s[x]<=s[q.back()]){
q.pop_back();
}
//cout << endl;
q.push_back(x);
if(abs(x - q.front())>=n){
q.pop_front();
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ){
cin >> value[i] >> cost[i];
value[i+n] = value[i];
cost[i+n] = cost[i];
}
// 顺时针
for (int i = 1; i <= 2*n; i ++ ){
s[i] = s[i-1] + value[i] - cost[i];
}
for (int i = 1; i <= n; i ++ ){
push(i);
}
for (int i = 1; i <= n; i ++ ){
int j = q.front();
if(s[j]-s[i-1]>=0){
st[i] = true;
}
push(i+n);
}
// 逆时针
q.clear();
cost[0] = cost[n];
for (int i = 2*n; i ; i -- ){
s[i] = s[i+1] + value[i] - cost[i-1];
}
for (int i = 2*n; i > n; i -- ){
push(i);
}
for (int i = 2*n; i > n; i -- ){
int j = q.front();
if(s[j]-s[i+1]>=0){
st[i-n] = true;
}
push(i-n);
}
for (int i = 1; i <= n; i ++ ){
if(st[i]){
puts("TAK");
}else{
puts("NIE");
}
}
}
1089. 烽火传递
———连续m个中必须选一个
烽火台是重要的军事防御设施,一般建在交通要道或险要处。
一旦有军情发生,则白天用浓烟,晚上有火光传递军情。
在某两个城市之间有 n 座烽火台,每个烽火台发出信号都有一定的代价。
为了使情报准确传递,在连续 m 个烽火台中至少要有一个发出信号。
现在输入 n,m 和每个烽火台的代价,请计算在两城市之间准确传递情报所需花费的总代价最少为多少。
输入格式
第一行是两个整数 n,m,具体含义见题目描述;
第二行 n 个整数表示每个烽火台的代价 ai。
输出格式
输出仅一个整数,表示最小代价。
输入样例:
5 3
1 2 5 6 2
输出样例:
4
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 2e5+10;
int a[N];
int f[N];
int n,m;
deque<int>q;
void push(int x){ // 寻找最小值,单调增队列
while(!q.empty()&&f[x]<=f[q.back()]){
q.pop_back();
}
q.push_back(x);
if(x - q.front() >= m){
q.pop_front();
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
}
push(0);
for (int i = 1; i <= n; i ++ ){
int j=q.front();
f[i] = f[j] + a[i];
push(i);
}
int res = f[n];
for (int i = n-m+1; i <= n; i ++ ){
res = min(res,f[i]);
}
cout << res;
}
1090. 绿色通道
———m固定,求间隔的最大值最小
高二数学《绿色通道》总共有 n 道题目要抄,编号 1,2,…,n,抄第 i 题要花 ai 分钟。
小 Y 决定只用不超过 t 分钟抄这个,因此必然有空着的题。
每道题要么不写,要么抄完,不能写一半。
下标连续的一些空题称为一个空题段,它的长度就是所包含的题目数。
这样应付自然会引起马老师的愤怒,最长的空题段越长,马老师越生气。
现在,小 Y 想知道他在这 t 分钟内写哪些题,才能够尽量减轻马老师的怒火。
由于小 Y 很聪明,你只要告诉他最长的空题段至少有多长就可以了,不需输出方案。
输入格式
第一行为两个整数 n,t。
第二行为 n 个整数,依次为 a1,a2,…,an。
输出格式
输出一个整数,表示最长的空题段至少有多长。
输入样例:
17 11
6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5
输出样例:
3
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 5e4+10;
int a[N];
int f[N];
int n,t;
deque<int>q;
void push(int x,int num){ // 找最小值,单调增队列
while(!q.empty()&&f[x]<=f[q.back()]){
q.pop_back();
}
q.push_back(x);
if(x - q.front()>=num){
q.pop_front();
}
}
bool check(int mid){ // 间隔是mid 连续mid+1个至少选一个
q.clear();
memset(f, 0, sizeof f);
push(0,mid+1);
for (int i = 1; i <= n; i ++ ){
int j = q.front();
f[i] = f[j] + a[i];
push(i,mid+1);
}
int mind = f[n];
for (int i = n - mid; i <= n; i ++ ){
mind = min(f[i],mind);
}
return mind<=t;
}
int main()
{
cin >> n >> t;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
}
int l = 0,r = n; // 二分最长空题段
while(l<r){
int mid = l + r >> 1;
if(check(mid)){
r = mid;
}else{
l = mid+1;
}
}
cout << l ;
}
1091. 理想的正方形
——— 二维单调队列
有一个 a×b 的整数组成的矩阵,现请你从中找出一个 n×n 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
输入格式
第一行为三个整数,分别表示 a,b,n 的值;
第二行至第 a+1 行每行为 b 个非负整数,表示矩阵中相应位置上的数。
输出格式
输出仅一个整数,为 a×b 矩阵中所有“n×n 正方形区域中的最大整数和最小整数的差值”的最小值。
数据范围
2≤a,b≤1000,
n≤a,n≤b,n≤100,
矩阵中的所有数都不超过 109。
输入样例:
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
输出样例:
1
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int g[N][N];
int f[N][N][2];
int c[N][N][2];
deque<PII>q1; // 单调增队列
deque<PII>q2; // 单调减队列
int ans = 1e9;
int a,b,n;
// 单调增队列,在x位置,插入value,滑动窗口放几个元素
void push(deque<PII>&q,int x, int value,int len){
while(!q.empty()&&value<=q.back().y){
q.pop_back();
}
q.push_back({x,value});
if(x - q.front().x >= len){
q.pop_front();
}
}
// 单调减队列,在x位置,插入value,滑动窗口放几个元素,重载
void push(deque<PII>&q,int x, int value,int len,int ){
while(!q.empty()&&value>=q.back().y){
q.pop_back();
}
q.push_back({x,value});
if(x - q.front().x >= len){
q.pop_front();
}
}
int main()
{
cin >> a >> b >> n;
for (int i = 1; i <=a ; i ++ ){
for (int j = 1; j <= b; j ++ ){
scanf("%d",&g[i][j]);
}
}
// 求每一行中每一列对应的最大值
for (int i = 1; i <= a; i ++ ){
q1.clear();
q2.clear();
for (int j = 1; j < n; j ++ ){
push(q1,j,g[i][j],n-1); // 单调增队列
push(q2,j,g[i][j],n-1,0);// 单调减队列
}
for (int j = n; j <= b; j ++ ){
auto t1 = q1.front();
auto t2 = q2.front();
f[i][j][0] = max(g[i][j],t2.y); // 最大值
f[i][j][1] = min(g[i][j],t1.y); // 最小值
push(q1,j,g[i][j],n-1); // 单调增队列
push(q2,j,g[i][j],n-1,0);// 单调减队列
}
}
// 求每一列当中每一行对应的最大值
for (int j = n; j <= b; j ++ ){ //
q1.clear();
q2.clear();
for (int i = 1; i < n; i ++ ){
push(q1,i,f[i][j][1],n-1); // 单调增队列
push(q2,i,f[i][j][0],n-1,0);// 单调减队列
}
for (int i = n; i <= a; i ++ ){
auto t1 = q1.front();
auto t2 = q2.front();
c[i][j][0] = max(f[i][j][0],t2.y);
c[i][j][1] = min(f[i][j][1],t1.y);
ans = min(ans,c[i][j][0]-c[i][j][1]);
push(q1,i,f[i][j][1],n-1); // 单调增队列
push(q2,i,f[i][j][0],n-1,0);// 单调减队列
}
}
cout << ans;
}
斜率优化DP
300. 任务安排1
有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。
机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。
从时刻 0 开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti。
另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。
也就是说,同一批任务将在同一时刻完成。
每个任务的费用是它的完成时刻乘以一个费用系数 Ci。
请为机器规划一个分组方案,使得总费用最小。
输入格式
第一行包含整数 N。
第二行包含整数 S。
接下来 N 行每行有一对整数,分别为 Ti 和 Ci,表示第 i 个任务单独完成所需的时间 Ti 及其费用系数 Ci。
输出格式
输出一个整数,表示最小总费用。
数据范围
1≤N≤5000,
0≤S≤50,
1≤Ti,Ci≤100
输入样例:
5
1
1 3
3 2
4 3
2 3
1 4
输出样例:
153
#include <iostream>
#include <cstring>
#include <algorithm>
typedef long long LL;
using namespace std;
const int N = 5010;
int n,s;
LL sumt[N];
LL sumc[N];
LL f[N];
int main()
{
cin >> n >> s;
for (int i = 1; i <= n; i ++ ){
cin >> sumt[i] >> sumc[i];
sumt[i]+=sumt[i-1];
sumc[i]+=sumc[i-1];
}
memset(f, 0x3f, sizeof f);
f[0] = 0;
for (int i = 1; i <= n; i ++ ){
for (int j = i-1; j >=0; j -- ){ //划分的是上一批任务的结点
LL t = f[j] + sumt[i] * (sumc[i] - sumc[j]) + s * (sumc[n] - sumc[j]);
f[i] = min(f[i],t);
}
}
cout << f[n];
}
301. 任务安排2
——— 斜率优化





#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3e5 + 10;
typedef long long LL;
LL t[N], c[N];
LL f[N];
LL q[N];
int n, s;
int main() {
cin >> n >> s;
for (int i = 1; i <= n; i++) {
cin >> t[i] >> c[i];
t[i] += t[i - 1];
c[i] += c[i - 1];
}
int hh = 0, tt = 0;
for (int i = 1; i <= n; i++) {
while (hh < tt && f[q[hh + 1]] - f[q[hh]] <= (t[i] + s) * (c[q[hh + 1]] - c[q[hh]])) {
hh++;
}
int j = q[hh];
f[i] = f[j] + t[i] * (c[i] - c[j]) + s * (c[n] - c[j]);
while (hh < tt &&
(f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt - 1]]) >= (f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]])) {
tt--;
}
q[++tt] = i;
}
cout << f[n];
}

在计算f[4][1][3]的时候 会从5的上下来枚举,9的上下来枚举 然后 + 5 + 9
f[4][3][1]的时候也同样会,这样想似乎更好理解


可以看到只要右边界枚举到2n-1就可以了,如果枚举到2*n相等于重复枚举了一遍2-12