[kuangbin带你飞]专题二十二 区间DP
ZOJ 3537 Cake
判断闭包+最优三角划分
注意是用求出的闭包来算转移
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int m,f[400][400],dp[400][400];
struct Point{
int x,y;
Point(){}
Point(int xx,int yy):x(xx),y(yy){}
void read(){
scanf("%d%d",&x,&y);
}
bool operator < (const Point &other)const{
if(x == other.x)
return y < other.y;
return x < other.x;
}
Point operator - (const Point &other)const{
return Point(x - other.x,y - other.y);
}
};
Point p[400],ch[400];
int n;
double Cross(Point A,Point B){
return A.x * B.y - A.y * B.x;
}
int ConvexHull(){
sort(p,p+n);
int cnt = 0;
for(int i = 0; i < n; i++){
while(cnt > 1 && Cross(ch[cnt-1] - ch[cnt-2],p[i] - ch[cnt-2]) <= 0) cnt--;
ch[cnt++] = p[i];
}
int k = cnt;
for(int i = n-2; i >= 0; i--){
while(cnt > k && Cross(ch[cnt-1] - ch[cnt-2],p[i] - ch[cnt-2]) <= 0) cnt--;
ch[cnt++] = p[i];
}
if(n > 1) cnt--;
return cnt;
}
int calc(Point a,Point b){
return (abs(a.x + b.x) * abs(a.y + b.y)) % m;
}
int main(){
while(scanf("%d%d",&n,&m) != EOF){
memset(p,0,sizeof(p));
memset(ch,0,sizeof(ch));
memset(f,0,sizeof(f));
for(int i = 0; i < n; i++){
p[i].read();
}
if(n == 3){
puts("0");
continue;
}
if(ConvexHull() < n){
printf("I can't cut.
");
}
else{
for(int i = 0; i < n; i++){
for(int j = i+2; j < n; j++){
f[i][j] = f[j][i] = calc(ch[i],ch[j]);
}
}
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
dp[i][j] = INF;
}
dp[i][(i+1)%n] = 0;
}
for (int len = 3; len <= n; len++) {
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
for (int k = i + 1; k <= j - 1; k++) {
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j]+f[i][k]+f[k][j]);
}
}
}
printf("%d
",dp[0][n-1]);
}
}
return 0;
}
LightOJ 1422 Halloween Costumes
大意:
n个宴会,对于每一个宴会,都要穿一种礼服,礼服可以套着穿,但是脱了的不能再用,参加宴会必须按顺序来,从第一个到第N个,问参加这些宴会最少需要几件礼服
思路:
当c[j-1] = c[j] 时显然有dp[ i ] [ j ] = dp[ i ] [j-1]
更新是更新当第k场舞会衣服与第j场衣服一致时,假设第k场衣服一直穿着
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5;
typedef long long LL;
int t, a[N], dp[N][N], cases = 0;
int main() {
cin >> t;
while (t--) {
cases++;
memset(dp, 0x3f, sizeof dp);
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < n; i++) {
dp[i][i] = 1;
}
for (int len = 2; len <= n; len++) {
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
if (a[j] == a[j - 1])
dp[i][j] = dp[i][j - 1];
else
dp[i][j] = dp[i][j - 1] + 1;
for (int k = i; k < j; k++) {
if (a[k] == a[j]) {
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j - 1]);
}
}
}
}
printf("Case %d: %d
", cases, dp[0][n - 1]);
}
return 0;
}
POJ 2955 Brackets
大意:
给出一个字符串,问满足括号匹配的最长区间是多少
满足括号匹配的有:
(), [], (()), ()[], ()[()]
思路:
当(s[i]==s[j])时,(dp[i][j]==dp[i+1][j-1]+2)
然后直接转移就行
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
using namespace std;
const int N = 1e2 + 5;
typedef long long LL;
int dp[N][N];
int main() {
string s;
while (cin >> s) {
if (s == "end") break;
int n = s.size();
memset(dp, 0, sizeof dp);
int res = 0;
for (int len = 2; len <= n; len++) {
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
if ((s[i] == '(' && s[j] == ')') ||
(s[i] == '[' && s[j] == ']'))
dp[i][j] = dp[i + 1][j - 1] + 2;
for (int k = i; k < j; k++) {
dp[i][j] = max(dp[i][k] + dp[k + 1][j], dp[i][j]);
}
res = max(res, dp[i][j]);
}
}
cout << res << endl;
}
return 0;
}
CodeForces 149D Coloring Brackets
大意:
给出一个能被匹配的括号字符串,现在对这些括号进行涂色,要求满足:
对于每一对配对的括号,有且只有一个括号染色
相邻的括号,如果都染色了,颜色不能相同
每个括号要么不染色,要么染红色,要么染绿色
思路:
首先利用栈对括号进行匹配
利用dfs写区间dp,枚举两侧的颜色,然后进行转移
如果当前区间的左右端点配对,那么直接进入下一层区间
否则枚举左端点对应的配对的右括号,枚举全部的颜色即可
#include <bits/stdc++.h>
using namespace std;
const int N = 7e2 + 5;
typedef long long LL;
LL dp[N][N][5][5];
string s;
const LL mod = 1e9 + 7;
int n, ne[N];
stack<int> st;
LL dfs(int l, int r, int c1, int c2) {
LL res = 0;
if (l >= r) return dp[l][r][c1][c2] = 0LL;
if (dp[l][r][c1][c2] != -1) return dp[l][r][c1][c2];
if (ne[l] == r) { //遇到这个区间左右端点恰好匹配,那么左右端点的方案已经固定了
if ((c1 == 0 && c2 != 0) || (c2 == 0 && c1 != 0)) { //当前匹配必须合法
if (l + 1 == r) return dp[l][r][c1][c2] = 1LL;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (((i == 0) || (c1 == 0) || (i != c1)) &&
((j == 0) || (c2 == 0) || (j != c2))) {
res = (res + dfs(l + 1, r - 1, i, j)) % mod;
}
}
}
}
} else {
int k = ne[l];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if ((i == 0) || (j == 0) || (i != j)) {
res = (res + dfs(l, k, c1, i) * dfs(k + 1, r, j, c2) % mod) %mod;
}
}
}
}
return dp[l][r][c1][c2] = res;
}
int main() {
cin >> s;
memset(dp, -1, sizeof dp);
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(')
st.push(i);
else {
ne[st.top()] = i;
st.pop();
}
}
n = s.size();
LL res = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
res = (res + dfs(0, n - 1, i, j)) % mod;
}
}
cout << res << endl;
return 0;
}
POJ 1651 Multiplication Puzzle
大意:
给出n个数,每次从没有取出的数中取出一个数,花费是这个数和两边的还没有取出来的数的乘积,最左边和最右边的数不能取,问最后花费最小是多少
思路:
枚举中点转移即可,注意转移的区间是 i,k 和 k , j,不是i,k 和 k+1 , j
区间dp就是要认真分析区间边界啊...
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
using namespace std;
const int N = 1e2 + 5;
typedef long long LL;
int n;
LL a[N], dp[N][N];
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
memset(dp, 0x3f, sizeof dp);
for (int i = 0; i < n - 1; i++) dp[i][i + 1] = 0;
for (int len = 3; len <= n; len++) {
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
for (int k = i + 1; k < j; k++) {
dp[i][j] =
min(dp[i][j], dp[i][k] + dp[k][j] + a[i] * a[k] * a[j]);
}
}
}
cout << dp[0][n - 1] << endl;
return 0;
}
ZOJ 3469 Food Delivery
大意:
在X-轴上有一个餐厅,以及有n个点要送外卖。餐厅坐标为x,工人的速度为(v^{-1}),也就是 1/v 米每分钟。给出n个点的x坐标和b,外卖没送到,客户就会不愉快,每一分钟的不愉快指数增加b。问怎么样的送货策略,使得所有客户的总不愉悦指数和最小。
思路:
可以推出,当需要送区间l到r的两端的外卖时,必然是从区间l到r-1或者l+1到r转移过来,也就是说区间内部的点已经送过了,不然他会先送区间内部的点
但是员工肯定要么是在区间左端点要么是在区间右端点,所以需要多开一维维护是在左边还是在右边
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
typedef long long LL;
int n, v, x;
struct node {
int pos, b;
} a[N];
int dp[N][N][2], sum[N];
bool cmp(node a, node b) { return a.pos < b.pos; }
int main() {
while (scanf("%d%d%d", &n, &v, &x) != EOF) {
for (int i = 1; i <= n; i++) scanf("%d%d", &a[i].pos, &a[i].b);
n++;
a[n].pos = x, a[n].b = 0;
sort(a+1, a + n + 1,cmp);
int pos = 0;
for (int i = 1; i <= n; i++) {
if (a[i].pos == x) {
pos = i;
break;
}
}
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i].b;
}
memset(dp, 0x3f, sizeof dp);
for (int i = pos; i >=1 ; i--) {
for (int j = pos; j <= n; j++) {
if (i == j) {
dp[i][j][0] = dp[i][j][1] = 0;
continue;
}
int mid = sum[i - 1] - sum[0] + sum[n] - sum[j];
dp[i][j][0] = min(dp[i][j][0],dp[i + 1][j][0] + (mid+a[i].b) * (a[i + 1].pos - a[i].pos));
dp[i][j][0] = min(dp[i][j][0],dp[i + 1][j][1] + (mid+a[i].b) * (a[j].pos - a[i].pos));
dp[i][j][1] = min(dp[i][j][1],dp[i][j-1][0] + (mid+a[j].b) * (a[j].pos - a[i].pos));
dp[i][j][1] = min(dp[i][j][1],dp[i][j-1][1] + (mid+a[j].b) * (a[j].pos - a[j-1].pos));
}
}
cout << min(dp[1][n][0], dp[1][n][1]) * v << endl;
}
return 0;
}
HDU 4283 You Are the One
大意:
有n个人,v [ i ] 表示每个人的愤怒值,现在他们依次上台表演,如果i是第k个上台,那么他的愤怒值为(k-1)* v[i] 现在有一个栈,可以让一些人进栈,让后面的人先上台表演,这样可以改变部分顺序,求所有人最小的愤怒值和
思路:
我们考虑区间[l,r],如果第一个人是该区间中第k个上场的,那么区间[l+1,r]中的人应当先于第一个上场,区间[l+k+1,r]中的人必定要在第一个人之后上场,这样一个区间就被划分成了两个区间。
dp[ i + 1 ] [ i + k - 1]表示前 k - 1个人的沮丧值,a[i] * (k-1)表示第i个人的沮丧值,而i+k到j的这些人由于出场位置都增加了K,所以总的沮丧值增加了k * (sum[j] - sum[i+k-1])
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5;
typedef long long LL;
int n, dp[N][N], t, a[N], sum[N], cases;
int main() {
cin >> t;
while (t--) {
cases++;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], sum[i] = sum[i - 1] + a[i];
memset(dp, 0, sizeof dp);
for (int len = 1; len <= n; len++) {
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
dp[i][j] = 0x3f3f3f3f;
if (len==1) {
dp[i][j] = 0;
continue;
}
for (int k = 1; k <= len; k++) {
dp[i][j] =
min(dp[i][j], dp[i + 1][i + k - 1] + a[i] * (k - 1) +dp[i+k][j]+
k * (sum[j] - sum[i + k-1]));
}
}
}
printf("Case #%d: %d
", cases, dp[1][n]);
}
return 0;
}
HDU 2476 String painter
大意:
给出两个字符串s1,s2。对于每次操作可以将 s1 串中的任意一个子段变成另一个字符。问最少需要多少步操作能将s1串变为s2串。
思路:
这个题先考虑怎么由空串转化s2,
f [ i ] [ j ] 表示从空串到s2最少的次数,
则有f[ i ] [ j ] = s [ i + 1 ] [ j ] +1,
若[i + 1, j ]存在一个 k ,使s2[ i ]==s2[ k ],则f[ i ] [ j ]=min{f[i+1] [k ]+f[ k+1] [ j ] },
也就是说i和k用同一次变换
然后再考虑把s1刷成s2的代价
设sum[i]表示把s1[1,i]刷成s2[1,i]的次数
当s1[i]==s2[i]时,可以不刷,显然sum[i]=sum[i-1]
否则,在区间内找最小次数sum[i]=min(sum[j] + f [j+1] [i])
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int n, m;
int f[N][N], sum[N];
char s[N], t[N];
int main() {
while (cin >> s) {
cin >> t;
memset(f, 0, sizeof f);
memset(sum, 0, sizeof sum);
int len = strlen(s);
for (int i = 0; i < len; ++i) f[i][i] = 1;
for (int i = 0; i < len; ++i)
for (int j = i - 1; j >= 0; --j) {
f[j][i] = f[j + 1][i] + 1;
for (int k = j + 1; k <= i; ++k)
if (t[j] == t[k])
f[j][i] = min(f[j][i], f[j + 1][k] + f[k + 1][i]);
}
for (int i = 0; i < len; ++i) sum[i] = f[0][i];
if (s[0] == t[0]) sum[0] = 0;
for (int i = 1; i < len; ++i) {
if (s[i] == t[i])
sum[i] = min(sum[i], sum[i - 1]);
else
for (int j = 0; j < i; ++j)
sum[i] = min(sum[i], sum[j] + f[j + 1][i]);
}
cout << sum[len - 1] << endl;
}
}