#include <iostream> #include <stack> using namespace std; int boomNum[1000]; bool isAccess[100][100];//[i][j]=true 表示 地窖i、j间是连接的 int m[1000];//表示从第i个地窖开始挖的最多地雷数 int main() { int n;//n个地窖 cin >> n; for (int i = 1; i <= n; i++) { cin >> boomNum[i]; //输入每个地窖有多少个雷 } int start = -1, end = -1; //填m表 初始化 输入若干行 最后一行00表示结束 while (!(start == 0 && end == 0)) { cin >> start >> end; isAccess[start][end] = true; } //从最大的num开始 //初始化 int max = -1; m[n] = boomNum[n];//固定末尾 int way[1000];//记录后继 way[i] 表示i的后继 int k = 0;//记录后继 //动态方程 for (int i = n - 1; i >= 1; i--) { //找谁和i相连 max = 0;//先初始化max k = 0; for (int j = i + 1; j <= n; j++) { //如果i 和 j 相连 尝试更新m[i] if (isAccess[i][j] && m[j] > max) { max = m[j]; k = j; } } m[i] = max + boomNum[i]; way[i] = k;//更新后继 记录路径 } max = -1; int max_i; //找出最大 for (int i = 1; i < n; i++) { //cout << way[i]; if (m[i] > max) { max = m[i]; max_i = i; } } //输出路径 cout << max_i; int t = max_i; while (way[t] > 0) { cout << "-" << way[t]; t = way[t]; } cout << endl; cout << max; }
这题突破口在题目设置 “并规定路径都是单向的,且保证都是小序号地窖指向大序号地窖” ,那么最大的那个地窖不指向任何地窖,则可以固定最大号的那个地窖的d,从大往小递推。
设d[i]为从i号地窖开始挖最大的地雷数,默认值为a[i]
令a[i] 为第i号地窖的地雷数
递归方程 : d[i] = max { d[j] + a[i] | i 与 j 之间相连} (i : 从大到小)
设way[i] 为i号地窖的后继地窖,默认为-1
way[i] = { j | i 与 j 相连 并且 d[j] + a[i] 取得最大 }
分割线----------------------------------------------------------------------------------------------------
合唱队形问题
题目描述
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。
合唱队形定义:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK,
则他们的身高满足T1 < T2 < … < Ti, Ti > Ti+1 > … > TK (1 <= i <= K)。
要求:已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入
输入的第一行是一个整数N,表示同学的总数。
第一行有n个整数,用空格分隔,第i个整数Ti是第i位同学的身高(厘米)。
输出
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
分析:这一题是最长单调递增子序列的拓展题,
设有n个人,数列为a
设d1[i]为从第一个开始到第i个人的最长单调递增子序列的大小,d2[i]为从第i歌人到最后一个的最长单调递减子序列的大小,d1,d2默认值大小为1,
那么合唱队形最长大小为 : longest = max( 1<=i <= n){ d1[i] + d2[i] - 1}
出列人数为 ans = n - longest
递归方程 :
d1[i] = max(1 <= j < = i-1 ) { d[j] + 1 | a[i] > a[j] }
d2[i] = max(从n到i+1) { d[j] + 1 | a[i] < a[j] }
分割线----------------------------------------------------------------------------------------------------
描述
政府在某山区修建了一条道路,恰好穿越总共m个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往。已知任意两个相邻的村庄之间的距离为di(为正整数),其中,0 < i < m。为了提高山区的文化素质,政府又决定从m个村中选择n个村建小学(设 0 < n < = m < 500 )。请根据给定的m、n以及所有相邻村庄的距离,选择在哪些村庄建小学,才使得所有村到最近小学的距离总和最小,计算最小值。
输入
第1行为m和n,其间用空格间隔
第2行为(m-1) 个整数,依次表示从一端到另一端的相邻村庄的距离,整数之间以空格间隔。
例如
10 3
2 4 6 5 2 4 3 1 3
表示在10个村庄建3所学校。第1个村庄与第2个村庄距离为2,第2个村庄与第3个村庄距离为4,第3个村庄与第4个村庄距离为6,...,第9个村庄到第10个村庄的距离为3。
输出
各村庄到最近学校的距离之和的最小值。
样例输入
10 2
3 1 3 1 1 1 1 1 3
样例输出
18
分析:这一题有一点难度,一时半会可能是看不懂的,问题不大,反正都看不懂,我懂了就行,2333,希望大家也能看懂。
设:
f[i][j]=从第1个到第i个村庄里修j个学校的最短距离和,先把所有f[i][j]初始成一个较大的数如999999
s[i][j]:从第i个村到第j个村修一个学校,区内村庄到学校的距离和的最小值。(这个学校建在(i+j)/2的地方是最近的,数学证明就不证了,因为我也不会)
a[i]:第i个村离起点(第一个村)的距离。(用来计算s[i][j]用的, s[i][j] = ∑(k从i到j) abs(a[k] - a[mid]))
递归方程:
f[i][j]= min (f[i][j] , f[k][j-1] + s[k + 1][i]) (k的范围是"已有学校数"~m-1),这个容易看不懂,没关系具体化一下就好懂了
令i=10, j=1,那么当前f[10][1] = min (f[10][1] , f[k][0] + s[k + 1][10]),现在就是将 【从第1个到第10个村庄里修1个学校的最短距离和】,与 min {【从第1个到第k个村庄里修个0学校(就是不修这个学校,把这个学校放到k+1到10这一段中),从第1个村到第10个村中修一个学校(k从0(已有学校数)到9,),区内村庄到学校的距离和的最小值。】}进行比较,取较小的那个作为f[i][j]。其他情况 类推就好
考虑初始值设置:
f[1][1] f[2][2] = 0,前i个村里修i个学校的最短距离和一定为0 所以 f[i][i] = 0
f[2][1] f[3][1] = s[1][i] ,前i个村里修1个学校的最短距离和等于s[1][i](从第1个村到第i个村修一个学校,区内村庄到学校的距离和的最小值。)
递归方程
for(i=2;i<=m;i++){//村庄 for(j=2;j<=min(i,n);j++){//学校 for(int k=j-1;k<=i-1;k++){//枚举已有的学校管辖的范围 if(i!=j)f[i][j]=min(f[i][j],f[k][j-1]+s[k+1][i]); } } }
分割--------------------------------------线线
剑客决斗,(这题有点难度)
描述
在路易十三和红衣主教黎塞留当权的时代,发生了一场决斗。n个人站成一个圈,依次抽签。抽中的人和他右边的人决斗,负者出圈。这场决斗的最终结果关键取决于决斗的顺序。现书籍任意两决斗中谁能胜出的信息,但“A赢了B”这种关系没有传递性。例如,A比B强,B比C强,C比A强。如果A和B先决斗,C最终会赢,但如果B和C决斗在先,则最后A会赢。显然,他们三人中的第一场决斗直接影响最终结果。
假设现在n个人围成一个圈,按顺序编上编号1~n。一共进行n-1场决斗。第一场,其中一人(设i号)和他右边的人(即i+1号,若i=n,其右边人则为1号)。负者被淘汰出圈外,由他旁边的人补上他的位置。已知n个人之间的强弱关系(即任意两个人之间输赢关系)。如果存在一种抽签方式使第k个人可能胜出,则我们说第k人有可能胜出,我们的任务是根据n个人的强弱关系,判断可能胜出的人数。
输入
第一行是一个整数N(1<=N<=20)表示测试数据的组数。
第二行是一个整数n表示决斗的总人数。(2<=n<=500)
随后的n行是一个n行n列的矩阵,矩阵中的第i行第j列如果为1表示第i个人与第j个人决斗时第i个人会胜出,为0则表示第i个人与第j个人决斗时第i个人会失败。
输出
对于每组测试数据,输出可能胜出的人数,每组输出占一行
样例输入
1
3
0 1 0
0 0 1
1 0 0
样例输出
3
思路:只要本人能和本人决斗,则说明此人胜出
编号为i的人能从所有人中胜出,必要条件是他能与自己相遇,即把环看成链,i点拆成两个在这条链的两端(i开头,j结尾,j也是自己的话那么i胜出),中间的人全部被淘汰出局,i保持不败。这样,在连续几个人的链中,只须考虑头尾两个人能否胜利会师,中间的则不予考虑,从而少了一维状态表示量。
设:
meet[i][j]记录i和j能否相遇,能相遇则为true,否则为false。默认为false,初始值meet[i,(i+1) % n] = true。
fight[i][j]为i和j打,true为i胜出,false为j胜出,(这里fight就是输入样例里的矩阵,1为true,0为false)
问题转化成了是否能在这条链中找到一个k,使得 i 和 k , k 和 j 均能相遇,且 i 或者 j 能打败 k ,这样最终使得i,j可以相遇
if(meet[i][k] && meet[k][j]) && (fight[i][k] || fight[j][k]=true) && i < k < j)
meet[i][j] = true;
else
meet[i][j] = false;
初始值meet[i,i+1] = true,计算顺序是沿对角线的顺序。
最后计算meet[i][i] = true的数量 (i从1到n)
这题难点2在于如何填这个表,我看一下别人的博客也是不明不白的,首先是肯定有i,j,k的三重循环,k是从i+1到 j-1这点都没有疑问。问题在于他们为什么设了一个int d = 2,画了画图发现是因为是填表是沿对角线的顺序的,假设n=4,那么初始值meet[0][1](meet[i][j]后面简称m[i][j]), m[1][2] ,m[2][3],m[3][0] 为true,第一次填表的地方在m[0][2],因为m[0][1]和m[1][2]为true,再根据fight表就能确定m[0][2]填什么,第二次填表位置是m[1][3],是由m[1][2]和m[2][3]和他们对应的fight决定,所以计算顺序是沿对角线的顺序,后面依次类推。
所以有
for(int d=2;d<=n;d++) for(int i=0;i<n;i++) { int j = i+d; for(int k=i+1;k<j;k++) { if(meet[i][k%n] && meet[k%n][j%n] && (beat[i][k%n] || beat[j%n][k%n])) { meet[i][j%n] = true; break; } } }
最后计算meet[i][i] = true的个数为答案。
分割----------------------------------------------线
石子合并问题,(不能用贪心算,容易踩坑)
有 n 堆石子堆放在路边,现要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这 N 堆石子合并成一堆的总花费(最小或最大)。
Min [i][j] 代表从第 i 堆石子到第 j 堆石子合并的最小花费,初始化 i=j时 min[i][j] = 0
w ( i , j )代表从 i 堆到 j 堆的石子数量之和。
列出递归式:
Min [ i ][ j ] = min ( Min [ i ][ k ] + Min [ k + 1][ j ] + w ( i , j )) , i < j( i ≤ k < j)
重点在:列出如何 Min [ i ][ k ] + Min [ k + 1][ j ] + w ( i , j ) , 若只有两堆,合并花费为两堆个数之和(min[1][1]=0 + w(1,2)),若有三堆,合并 1/2合并花费 (见前分析)+ (1/2)/3合并花费,其中(1/2)/3合并花费为w(1,3),若有四堆合并(1/2),合并(3/4),合并(1/2)/(3/4)花费为w(1,4),总之要加上这个w(i,j)
Max把min改成max就好
填表方式:沿正对角线
-----------------------------------------------分割
加分二叉树
设一个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的前序遍历
Input
第1行:一个整数n(n<30),为节点个数。
第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。
Output
第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。
第2行:n个用空格隔开的整数,为该树的前序遍历。
Sample Input
5
5 7 1 2 10
Sample Output
145
3 1 2 4 5
分析:首先中序遍历有个特点,在中序遍历这个序列上,某个点左边的序列一定是这个点的左子树,右边的序列,一定在这个点的右子树。先搞清楚左右子树的概念
设:
root[i][j]表示[i,j]这段序列最大加分的根结点
dp[i][j]为从i到j节点的加分最大值 , 默认先都设为0,后面有初始化
value[i]为结点i的分数
我们可以枚举[i,j]之间的一个点k为根节点,得到状态转移方程 :
dp[i][j] = max(dp[i][j] , dp[i][k-1] * dp[k+1][j]+ value[k])同时更新root[i][j],最后的结果就是dp[1][n]
初始化:
dp[i][i-1] = 1,初始化成1,就不用考虑k没有左或者右子树的情况了
dp[i][i] = value[i],
root[i][i] = i,
填表顺序 自低向上,从左往右,k从i到j
for (int i=n;i>=1;i--) { for (int j=i+1;j<=n;j++) { for (int k=i;k<=j;k++) if (dp[i][k-1] * dp[k+1][j] + value[k]> dp[i][j]) { dp[i][j] = dp[i][k-1] * dp[k+1][j] + value[k]; root[i][j]=k; } } }
递归输出前序遍历
void Outp(int l,int r) { if (l>r) return; cout<<root[l][r]<<' '; //前序遍历,所以先输出根节点 if (l==r) return; Outp(l,root[l][r]-1);//递归调用 Outp(root[l][r]+1,r); return; }
---------------------------------------------------
给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一 定为20个)。要求将此字母串分成k份(1<k<=40),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠。当选用一 个单词之后,其第一个字母不能再用。例如字符串this中可包含this和is,选用this之后就不能包含th)(管理员注:这里的不能再用指的是位 置,不是字母本身。比如thisis可以算做包含2个is)。
单词在给出的一个不超过6个单词的字典中。
要求输出最大的个数。
第一行为一个正整数(0<n<=5)表示有n组测试数据
每组的第一行有二个正整数(p,k)
p表示字串的行数;
k表示分为k个部分。
接下来的p行,每行均有20个字符。
再接下来有一个正整数s,表示字典中单词个数。(1<=s<=6)
接下来的s行,每行均有一个单词。
每行一个整数,分别对应每组测试数据的相应结果。
1
1 3
thisisabookyouareaoh
4
is
a
ok
sab
7
void pre(){ memset(a,0,sizeof a); for(int i=1;i<=len;i++){ if(match(i,i)) a[i][i]=1; for(int j=i-1;j>=1;j--){ if(match(j,i)) a[j][i]=a[j+1][i]+1; else a[j][i]=a[j+1][i]; } } }
执行步骤 : i = 1 // i = 2 j =1 // i = 3 j = 2,1 // i = 4 j = 3,2,1
for(int k=2;k<=K;k++) for(int i=k;i<=len;i++) for(int j=k-1;j<i;j++) dp[i][k]=max(dp[i][k],dp[j][k-1]+a[j+1][i]);
k从2到K段,i从k到到字符串长度。