**第八周 acm刷题总结 区间DP**

关于区间DP还有好多问题没有解决,先把解决了的总结一下:
区间DP的模板代码: 我习惯先枚举区间长度,再枚举起点终点,然后枚举分割点。
今天看了一下下面说的分割点是用相邻的两个点遍历所有点,这仅限于下面这些例子,视情况需要,只需要遍历需要遍历的点,下面的例子是因为需要研究的最小区间长度为一个点,所以用相邻的两个点遍历所有点,达到枚举所有之前阶段最优状态最优解(前面阶段的最优状态的最优解。这里我不能说得很准确,只是自己理解了,如果强行套上“阶段”“状态”这些概念,我感觉区间长度是不同的阶段,每一个长度下的起止点是不同的状态,状态转移的过程,按照之前的认识,是从上一阶段的某一个状态的最优解转移到当前阶段当前状态的最优解,如果不转移更优,那就不转移。但是在区间dp里,不是从上一个阶段,而是从前面所有阶段在当前状态可以穷举的所有状态最优解的每一个阶段最优状态最优解)的目的。换个例子比如删数问题,删除一个数消耗这个数和相邻两个数的乘积,求最小消耗,这个就不用枚举所有区间长度,可以预处理包含一个点和两个点的时候,然后从区间长度为3 开始遍历阶段,而这个例子中分割点不需要两个,只需要一个k, 因为他不需要分割区间长度为1和2 的时候。

//遍历阶段:枚举区间长度,本例枚举的不是坐标差,是区间内点的个数
for (int len = 2; len <= n; len++) {
	//遍历每个阶段的状态:枚举起止点,起点先减去1,回到没有点的状态,然后加上点的个数,就是终点的位置,比写成i + len - 1 更友好。
	for (int i = 1, j; (j = i - 1 + len) <= n; i++) {
		//此处可能需要处理一下dp[i][j]
		/*寻找当前状态的最优解:枚举分割点,分割点不是分成了两块距离,
		而是用相邻的一对点把区间[i, j]分成了两组点集,
		这一对点分别属于这两个点集*/
		for(int k = i; k < j; k++) {
			dp[i][j] = 取最优(dp[i][k] + dp[k + 1][j] + 所需处理);
		}
	}
}

例题:
(1) 石子合并:
一条直线上摆放着一行共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。请编辑计算出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。
Input

输入有多组测试数据。

每组第一行为n(n<=100),表示有n堆石子,。

二行为n个用空格隔开的整数,依次表示这n堆石子的石子数量ai(0<ai<=100) 

Output
每组测试数据输出有一行。输出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。 中间用空格分开。
Sample Input

3

1 2 3 

Sample Output
9 11
分析:只能先两两合并,再不断从少往多合并,所以用区间DP,每次小区间合成大区间,都把得分作为大区间的值存起来, 每次合并的得分都是两个待合并区间的值相加再加上本次合并的得分,单独本次合并的得分是这两个待合并区间里石子的质量的和,这个和需要有一个前缀和数组去存储。

#include <iostream>
#include <algorithm>
using namespace std;
#define inf 0x3f3f3f3f

int dp1[105][105];
int dp2[104][104];
int main() {
	int n, a[105];
	while (cin >> n) {
		a[0] = 0;//给a[0]赋值为0是为了方便下面前缀和的表示以及当起点为1的时候dp的处理。
		for (int i = 1; i <= n; i++) {
			cin >> a[i];
			a[i] += a[i - 1];
		}
		//区间内点的个数从2开始遍历,这是处理1的时候,此时不合并,不得分。
		for (int i = 1; i <= n; i++) {
			dp1[i][i] = 0;
			dp2[i][i] = 0;
		}
		//遍历区间内点的个数
		for (int len = 2; len <= n; len++) {
		//遍历起止点
			for (int i = 1, j; (j = i + len - 1) <= n; i++) {
				dp1[i][j] = inf;
				dp2[i][j] = -inf;
				//遍历分割点,此时这一对分割点是k和k + 1
				for (int k = i; k < j; k++) {
					dp1[i][j] = min(dp1[i][j], dp1[i][k] + dp1[k + 1][j] + a[j] - a[i - 1]);
					dp2[i][j] = max(dp2[i][j], dp2[i][k] + dp2[k + 1][j] + a[j] - a[i - 1]);
				}
			}
		}
		cout << dp1[1][n] << ' ' << dp2[1][n] << endl;
	}

	return 0;
}

(2)环形石子合并:
在圆形操场上摆放着一行共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。请编辑计算出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。
Input
输入有多组测试数据。

每组第一行为n(n<=100),表示有n堆石子,。

二行为n个用空格隔开的整数,依次表示这n堆石子的石子数量ai(0<ai<=100) 

Output
每组测试数据输出有一行。输出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。 中间用空格分开。
Sample Input
3

1 2 3 

Sample Output
9 11

分析:和上一个题的不同是这里是个环,那么把数组延长一倍,模拟环。目的是可以考虑到分别以1 到n为起点的时候

#include <iostream>
using namespace std;
#define inf 0x3f3f3f3f

int dp1[205][205];
int dp2[204][204];
int main() {
	int n, a[205];
	while (cin >> n) {
		a[0] = 0;//仍然是为了前缀和和dp在起点为1的时候的处理
		for (int i = 1; i <= n; i++) {
			cin >> a[i];
			a[i + n] = a[i];
			a[i] += a[i - 1];
		}
		//补上没有存完的前缀和
		for (int i = n + 1; i < 2 * n; i++) {
			a[i] += a[i - 1];
		}
		/*处理只包含一个点的时候,由于研究不同起点只需要将起点从1枚举到n,那么所有区间的终点只需要到2 * n - 1就可以了,所以没有处理2 * n 这个点*/
		for (int i = 1; i < 2 * n; i++) {
			dp1[i][i] = 0;
			dp2[i][i] = 0;
		}
		for (int len = 2; len <= n; len++) {
			for (int i = 1, j; (j = i - 1 + len) < 2 * n; i++) {
				dp1[i][j] = inf;
				dp2[i][j] = -inf;
				for (int k = i; k < j; k++) {
					dp1[i][j] = min(dp1[i][j], dp1[i][k] + dp1[k + 1][j] + a[j] - a[i - 1]);
					dp2[i][j] = max(dp2[i][j], dp2[i][k] + dp2[k + 1][j] + a[j] - a[i - 1]);
				}
			}
		}
		int mi = inf, ma = -inf;
		for (int i = 1; i <= n; i++) {
			mi = min(mi, dp1[i][i - 1 + n]);
			ma = max(ma, dp2[i][i - 1 + n]);
		}
		cout << mi << ' ' << ma << endl;
	}

	return 0;
}

(3)能量项链:
在Mars星球上,每个Mars人都随身佩带着一串能量项链。在项链上有N颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是Mars人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为m,尾标记为r,后一颗能量珠的头标记为r,尾标记为n,则聚合后释放的能量为mrn(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。 

Input

有多组测试数据。

对于每组测试数据,输入的第一行是一个正整数N(4≤N≤100),表示项链上珠子的个数。第二行是N个用空格隔开的正整数,所有的数均不超过1000。第i个数为第i颗珠子的头标记(1≤i≤N),当i<N时,第i颗珠子的尾标记应该等于第i+1颗珠子的头标记。第N颗珠子的尾标记应该等于第1颗珠子的头标记。

至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。

    处理到文件结束。

Output

对于每组测试数据,输出只有一行,是一个正整数E(E≤2.1*109),为一个最优聚合顺序所释放的总能量。 

Sample Input

4
2 3 5 10

Sample Output
710

分析:每次两个相邻的珠子合并,能力释放为左面的 乘右面的乘右面的下一个,还是一样的道理,枚举起点最多枚举到n,那么终点就最多到 2 * n - 1 ,因为终点的下一个点2 n也是有值的,所以不会越界。好像一次性输入数据量不够大的情况不能用快读,不然超时了。

#include <iostream>
#include <stdio.h>
#include <cmath>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <map>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <set>
using namespace std;

#define IOS {\
    ios::sync_with_stdio(false);\
    cin.tie(0);\
    cout.tie(0);\
}
#define power(a, b)  fastPower(a, b)
#define inf 0x3f3f3f3f
#define ll long long
const long long INF = 1e18;
const int mod = 1e9 + 7;

long long fastPower(long long a, long long b) {
    long long ans = 1;
    while (b) {
        if (b & 1) ans *= a;
        b >>= 1;
        a *= a;
    }
    return ans;
}

const ll MOD = power(2, 64);

 inline ll read() {
     ll x = 0; int f = 1;
    char ch = getchar();
    while (ch < '0' || ch>'9') {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
 }

 inline void write(ll x) {
     char F[200];
     ll tmp = x > 0 ? x : -x;
     if (x < 0)putchar('-');
     int cnt = 0;
     while (tmp) {
         F[cnt++] = tmp % 10 + '0';
         tmp /= 10;
     }
     while (cnt) putchar(F[--cnt]);
 }

 ll a[206];
 ll dp[205][205];
int main() {
    IOS
    int n;
    while (cin >> n) {
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            a[i + n] = a[i];
        }
        ll ma = 0;
        for (int len = 2; len <= n; len++) {
            for (int i = 1, j; (j = i - 1 + len) < 2 * n; i++) {
                for (int k = i; k < j; k++) {
                    dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + a[i] * a[k + 1] * a[j + 1]);
                }
            }
        }
        for (int i = 1; i <= n; i++) {
            ma = max(ma, dp[i][i - 1 + n]);
        }
        cout << ma << endl;
    }
    return 0;
}

(4)万圣节穿衣服:
Gappu has a very busy weekend ahead of him. Because, next weekend is Halloween, and he is planning to attend as many parties as he can. Since it’s Halloween, these parties are all costume parties, Gappu always selects his costumes in such a way that it blends with his friends, that is, when he is attending the party, arranged by his comic-book-fan friends, he will go with the costume of Superman, but when the party is arranged contest-buddies, he would go with the costume of ‘Chinese Postman’.

Since he is going to attend a number of parties on the Halloween night, and wear costumes accordingly, he will be changing his costumes a number of times. So, to make things a little easier, he may put on costumes one over another (that is he may wear the uniform for the postman, over the superman costume). Before each party he can take off some of the costumes, or wear a new one. That is, if he is wearing the Postman uniform over the Superman costume, and wants to go to a party in Superman costume, he can take off the Postman uniform, or he can wear a new Superman uniform. But, keep in mind that, Gappu doesn't like to wear dresses without cleaning them first, so, after taking off the Postman uniform, he cannot use that again in the Halloween night, if he needs the Postman costume again, he will have to use a new one. He can take off any number of costumes, and if he takes off k of the costumes, that will be the last k ones (e.g. if he wears costume A before costume B, to take off A, first he has to remove B).

Given the parties and the costumes, find the minimum number of costumes Gappu will need in the Halloween night.

Input

Input starts with an integer T (≤ 200), denoting the number of test cases.

Each case starts with a line containing an integer N (1 ≤ N ≤ 100) denoting the number of parties. Next line contains N integers, where the ith integer ci (1 ≤ ci ≤ 100) denotes the costume he will be wearing in party i. He will attend party 1 first, then party 2, and so on.

Output

For each case, print the case number and the minimum number of required costumes.

Sample Input

2
4
1 2 1 2
7
1 2 1 1 3 2 1

Sample Output

Case 1: 3
Case 2: 4

分析:
参加一场聚会,需要一件衣服。如果参加两场,衣服相同则不用穿新的了,否则加穿一件,如果参加多场,首先默认比终点前移的那个区间多穿一件,然后可以枚举起止点之间的每一对分割点,如果终点的衣服和第一个分割点的衣服相同,那么可以把第二组点集全都脱了和当前的解比较一下,取一个较优的,他这一脱不一定是比当前的解优啊,因为他是保证从第二个分割点开始穿衣服,统计到终点前一个点,好处就是终点不需要穿新的了。再回来看当有两个点的时候,也 不用单独讨论了,代入也符合。那么如下

#include <iostream>
#include <stdio.h>
#include <cmath>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <map>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <set>
using namespace std;

#define IOS {\
    ios::sync_with_stdio(false);\
    cin.tie(0);\
    cout.tie(0);\
}
#define power(a, b)  fastPower(a, b)
#define inf 0x3f3f3f3f
#define ll long long

int main() {
    int T;
    cin >> T;
    for (int c = 1; c <= T; c++) {
        int n;
        cin >> n;
        ll a[105] = { 0 };
        ll dp[104][105] = { 0 };
        for (int i = 1; i <= n; i++) {
            a[i] = read();
        }
        for (int i = 1; i <= n; i++) {
            dp[i][i] = 1;
        }
        for (int len = 2; len <= n; len++) {
            for (int i = 1, j; (j = i - 1 + len) <= n; i++) {
            //默认加穿一件新的
                dp[i][j] = dp[i][j - 1] + 1;
                for (int k = i; k < j; k++) {
                    if (a[j] != a[k]) continue;
                    /*如果终点和第一个分割点衣服相同,那么多了一个选择,就是保证不看这个k点,把第二个分割点k + 1到终点前一个点的衣服独立地保证全乎了,再脱了去,露出k,达到终点不穿新衣服的目的。*/
                    dp[i][j] = min(dp[i][k] + dp[k + 1][j - 1], dp[i][j]);
                }
            }
        }
        cout << "Case " << c << ": " << dp[1][n] << endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值