2017暑期集训Day 14 区间dp+二分图匹配
来源:互联网 发布:上海红歆财富 骗局知乎 编辑:程序博客网 时间:2024/05/29 13:37
题目链接
A Multiplication Puzzle
[Solution]
区间dp水题
[Code]
#include<cstdio>#include<iostream>#include<vector>#include<cstring>#include<algorithm>using namespace std;typedef long long ll;const int N = 1000 + 5;#define inf 0x3f3f3f3fvector<int > s[N];int f[N][N];int n, a[N];int main(){ scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) f[i][j] = inf; for(int i = 1; i <= n; i++) { f[i][i] = 0; f[i][i + 1] = 0; } for(int p = 2; p < n; p++) for(int i = 1; p + i <= n; i++) { int j = i + p; for(int k = i + 1; k < j; k++) f[i][j] = min(f[i][j], f[i][k] + f[k][j] + a[i] * a[j] * a[k]); } printf("%d", f[1][n]); return 0;}
B - Coloring Brackets
[Problem]
给定一个合法的括号序列,现在我们给括号染色,可以染成红色和蓝色,询问最终方案数。
[Solution]
使用f[i][j][l][r]代表(i,j)这个区间左端l状态,右端j状态的方案数,这样分i、j匹配和不匹配来分类讨论,注意为了简化编码复杂度,我们可以可以将不合法的状态方案数标记为0,这样累加的时候判断相对容易
[Code]
#include<cstdio>#include<iostream>#include<vector>#include<cstring>#include<algorithm>#include<stack>using namespace std;typedef long long ll;const int N = 700 + 50;const int mo = 1e9 + 7;#define inf 0x3f3f3f3fstring ss;int n, m, k, Case = 0, l[N];ll f[N][N][3][3];int main(){ // freopen("b.in", "r", stdin); cin>>ss; n = ss.length(); stack<int> s; for(int i = 1; i <= n; i++) if (ss[i - 1] == '(') s.push(i); else { int x = s.top(); l[x] = i; s.pop(); } for(int i = 1; i < n; i++) if (ss[i - 1] == '(' && ss[i] == ')') { f[i][i + 1][0][1] = 1; f[i][i + 1][1][0] = 1; f[i][i + 1][0][2] = 1; f[i][i + 1][2][0] = 1; } for(int p = 3; p < n; p++) if (p % 2 == 1) for(int i = 1; i + p <= n; i++) { int j = i + p; if (l[i] == j) { for(int ll = 0; ll < 3; ll++) for(int rr = 0; rr < 3; rr++) { if (rr != 1) f[i][j][0][1] = (f[i][j][0][1] + f[i + 1][j - 1][ll][rr]) % mo; if (ll != 1) f[i][j][1][0] = (f[i][j][1][0] + f[i + 1][j - 1][ll][rr]) % mo; if (ll != 2) f[i][j][2][0] = (f[i][j][2][0] + f[i + 1][j - 1][ll][rr]) % mo; if (rr != 2) f[i][j][0][2] = (f[i][j][0][2] + f[i + 1][j - 1][ll][rr]) % mo; } } else { int x = l[i]; // printf("%d %d %d %d\n", i, x, x + 1, j); for(int l1 = 0; l1 < 3; l1++) for(int r1 = 0; r1 < 3; r1++) for(int r2 = 0; r2 < 3; r2++) for(int l2 = 0; l2 < 3; l2++) if (!((l2 == 1 && r1 == 1) || (l2 == 2 && r1 == 2)) ) { f[i][j][l1][r2] = (f[i][j][l1][r2] + f[i][x][l1][r1] * f[x + 1][j][l2][r2]) % mo; // printf("(%d %d %d %d)- %d %d\n", l1, r1, l2, r2, f[i][x][l1][r1], f[x + 1][j][l2][r2]); } } } ll ans = 0; for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) ans = (ans + f[1][n][i][j]) % mo; cout<<ans; return 0;}
C - Halloween Costumes
[Solution]
如果从状态上去考虑此道题目的会gg,因为对于每一天,身上穿的衣服有好多种情况,是不能存储的
我们从每天的决策去考虑,可以选择穿一件新衣服,或者脱掉一些衣服,即我们假设现在是第i天,需要用第k天的衣服,这样(k + 1, j-1)这些天就不能用前i天的衣服了,这样我们用f[i][j]代表(i,j)这些天独立的最优解
f[i][j] = f[i][k] + f[k + 1][j - 1]
[Code]
#include<cstdio>#include<iostream>#include<vector>#include<cstring>#include<algorithm>#include<stack>using namespace std;typedef long long ll;const int N = 1000 + 50;const int mo = 1e9 + 7;#define inf 0x3f3f3f3fvector<int > s[N];typedef long long ll;int n, m, k, f[N][N], a[N], Case = 0;int main(){ // freopen("b.in", "r", stdin); int T; scanf("%d", &T); while(T--) { scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) f[i][j] = inf; for(int i = 1; i < n; i++) { f[i][i] = 1; if (a[i] == a[i + 1]) f[i][i + 1] = 1; else f[i][i + 1] = 2; } f[n][n] = 1; for(int p = 2; p < n; p++) for(int i = 1; i + p <= n; i++) { int j = i + p; int t = 1; if (a[j] == a[j - 1]) t = 0; f[i][j] = min(f[i][j], f[i][j - 1] + t); for(int k = i; k + 1 <= j - 1; k++) if (a[k] == a[j]) f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j - 1]); } printf("Case %d: %d\n", ++Case, f[1][n]); } return 0;}
D - Food Delivery
[Problem]
坐标轴上有n个点,初始值自己位于一个点,你需要经过每个点一次,每个点有个愤怒值,会随着时间的增加而线性增加,询问总愤怒值最小化的方案。
[Solution]
本题很容易想到部分贪心策略,即在初始点的同一侧的两个点x, y, 离初始点较近的点肯定优先的到满足,因此对于满足[l, r]区间后,肯定位于l位置或r位置,因此我们用f[i][j]代表处理区间(i, j)后,位于i位置,g[i][j]则代表位于j位置的最优解,但是[i][j]区间的最小代价的转移需要到现在位置花费的时间,而我们每次取最小代价维护的最小时间是不对的,因为可能此次尽可能地让时间小一些,虽然此次决策的代价稍大一些,但是下一步的决策要优很多
我们从另一个方向上去考虑吧,我们每次转移的时候不能同时保证两个状态最优,因此我们累加答案可以换一个姿势,先前我们通过点的方式来累加答案,但是时间这个变量难以维护,这下我们通过时间的方式,即此次转移,[i][j]区间之外的点都会增加愤怒值,这样转移就不会出现两个最优性方案了
[Code]
#include<cstdio>#include<iostream>#include<vector>#include<cstring>#include<algorithm>using namespace std;typedef long long ll;const int N = 1000 + 5;#define inf 0x3f3f3f3fvector<int > s[N];ll f[N][N], g[N][N];ll sum[N];struct node{ int x, y;};node point[N];int n, v, st, res;bool cmp(node a, node b){ return a.x < b.x;}int main(){// freopen("b.in", "r", stdin); while(~scanf("%d%d%d", &n, &v, &st)) { for(int i = 1; i <= n ;i++) scanf("%d%d", &point[i].x, &point[i].y); n++; point[n].x = st; point[n].y = 0; sort(point + 1, point + n + 1, cmp); for(int i = 1; i <= n; i++) if (point[i].x == st) { res = i; break; } sum[0] = 0LL; for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + point[i].y; for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) { f[i][j] = inf; g[i][j] = inf; } f[res][res] = 0; g[res][res] = 0; for(int i = res; i >= 1; i--) for(int j = res; j <= n; j++) { if (i == j && i == res) continue; f[i][j] = min(f[i][j], f[i + 1][j] + 1LL * abs(point[i + 1].x - point[i].x) * (sum[n] - sum[j] + sum[i])); f[i][j] = min(f[i][j], g[i + 1][j] + 1LL * abs(point[j].x - point[i].x) * (sum[n] - sum[j] + sum[i])); g[i][j] = min(g[i][j], g[i][j - 1] + 1LL * abs(point[j].x - point[j - 1].x) * (sum[n] - sum[j - 1] + sum[i - 1])); g[i][j] = min(g[i][j], f[i][j - 1] + 1LL * abs(point[j].x - point[i].x) * (sum[n] - sum[j - 1] + sum[i - 1])); // printf("f[%d][%d] = %d\n", i, j, f[i][j]); // printf("g[%d][%d] = %d\n", i, j, g[i][j]); } cout<<min(f[1][n], g[1][n]) * v<<endl; } return 0;}
F - Asteroids
[Problem]
N * N 的网格中有k个路障,每次可以清除一列或一行的路障,询问最小次数(n < 500)
[Solution]
对于二分图,有最大匹配数等于最小点覆盖数,即我们选取选取尽可能少的点,使得每个边都有点覆盖,选取的最小点数即最小点覆盖数。
这样我们要清除掉所有的路障,因此我们把路障类比为边, 那什么类比成点呢,每条边连接的东西便是点,而路障连接的不是点数和列数吗?
因此我们把每一行的编号类比成点,每一行的列类比成二分图的另外一部分的点,这样是一个二分图,把路障类比成边,这样跑一遍匈牙利算法算出最小点覆盖数即可
[Code]
#include<cstdio>#include<iostream>#include<vector>#include<cstring>using namespace std;const int N = 505;vector<int > s[N];int girl[N];bool used[N];int n, m, k;bool found(int x){ for(int i = 0; i < s[x].size(); i++) { int y = s[x][i]; if (used[y]) continue; used[y] = true; if (girl[y] == 0 || found(girl[y])) { girl[y] = x; return 1; } } return 0;}int main(){ // freopen("b.in", "r", stdin); while(~scanf("%d%d", &n, &k)) { memset(girl, 0, sizeof(girl)); for(int i = 1; i <= n; i++) s[i].clear(); for(int i = 1; i <= k; i++) { int x, y; scanf("%d%d", &x, &y); s[x].push_back(y); } int ans = 0; for(int i = 1; i <= n; i++) { memset(used, 0, sizeof(used)); if (found(i)) ans++; } printf("%d\n", ans); } return 0;}
G - Chessboard
[Problem]
N* M的网格中有一些障碍物,现有1*2格纸若干,判断是否将格纸覆盖住网格,使得每一个网格上只有一层贴纸。
[Solution]
十分经典的二分图匹配,首先,同一个贴纸的两个点行数与列数和的奇偶性肯定相反,这样我们把行列数为奇数的为一类点,行列数为偶数的为一类点,这样把一个点与其四周的点,连边,最后跑一边二分图最大匹配即可
[Code]
#include<cstdio>#include<iostream>#include<vector>#include<cstring>#include<algorithm>#include<stack>using namespace std;typedef long long ll;const int N = 1000 + 50;const int mo = 1e9 + 7;#define inf 0x3f3f3f3fvector<int > s[N];typedef long long ll;int n, m, k, a[N][N], girl[N];bool used[N];int dx[] = {1, 0, -1, 0};int dy[] = {0, 1, 0, -1};bool found(int x){ for(int i = 0; i < s[x].size(); i++) { int y = s[x][i]; if (used[y]) continue; used[y] = true; if (girl[y] == 0 || found(girl[y])) { girl[y] = x; return 1; } } return 0;}int main(){ // freopen("b.in", "r", stdin); int n, m, k; scanf("%d%d%d", &n, &m, &k); int top1 = 0, top2 = 0; for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) if ((i + j) % 2 == 0) a[i][j] = ++top1; else a[i][j] = ++top2; if ((n * m - k) % 2 == 1) { printf("NO"); return 0; } for(int i = 1; i <= k; i++) { int x, y; scanf("%d%d", &y, &x); a[x][y] = -1; } for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) if (a[i][j] > 0 && (i + j) % 2 == 0) { int x = a[i][j]; for(int v = 0; v < 4; v++) { int xx = i + dx[v]; int yy = j + dy[v]; if (xx <= 0 || xx > n || yy <= 0 || yy > m) continue; if (a[xx][yy] <= 0) continue; int y = a[xx][yy]; s[x].push_back(y); // printf("---%d %d\n",x , y); } } int ans = 0; for(int i = 1; i <= top1; i++) { memset(used, 0, sizeof(used)); if (found(i)) { ans++; //printf("%d\n", i); } } // printf("%d\n", ans); int x = n * m - k; printf("%s", x == 2 * ans ? "YES" : "NO"); return 0;}
H - Book Club
[Problem]
n个人有n本书,每个人都有自己喜欢的书,判断是否存在一种交换方案,使得每个人获得一本自己喜爱的书。
[Solution]
很明显的二分图最大匹配
[Code]
#include<cstdio>#include<iostream>#include<vector>#include<cstring>using namespace std;const int N = 10000 + 5;vector<int > s[N];int girl[N];bool used[N];int n, m, k;bool found(int x){ for(int i = 0; i < s[x].size(); i++) { int y = s[x][i]; if (used[y]) continue; used[y] = true; if (girl[y] == 0 || found(girl[y])) { girl[y] = x; return 1; } } return 0;}int main(){ // freopen("b.in", "r", stdin); while(~scanf("%d%d", &n, &k)) { memset(girl, 0, sizeof(girl)); for(int i = 1; i <= n; i++) s[i].clear(); for(int i = 1; i <= k; i++) { int x, y; scanf("%d%d", &x, &y); x++; y++; s[x].push_back(y); } int ans = 0; for(int i = 1; i <= n; i++) { memset(used, 0, sizeof(used)); if (found(i)) { ans++; // printf("%d\n", i); } } if (ans == n) printf("YES"); else printf("NO"); } return 0;}
- 2017暑期集训Day 14 区间dp+二分图匹配
- 2017暑期集训Day 14 树形dp
- 2017暑期集训 Day 3
- 2017暑期集训Day 9 递推
- 2017暑期集训Day 11 背包
- 2017暑期集训Day 25 树状数组
- 暑期集训day1例题(最短路径、二分图匹配、拓扑排序)
- 2017暑期集训 Day 3 搜索与并查集
- 暑期集训-dp(1)
- 区间dp括号匹配
- 【集训】DP & 搜索 & 线段树 Day 2
- 暑期集训
- 中石油 暑期集训个人赛 DP部分
- pku 3189 网络流|二分图多重匹配+枚举区间
- poj2955,括号匹配,区间dp
- nyoj+区间dp括号匹配
- poj2955Brackets【区间dp 括号匹配】
- 2016暑期集训14A找朋友
- java中权限修饰符
- hdu4841圆桌问题(STL)
- MySql学习笔记
- SQL注入学习笔记(一)
- 动态规划入门级教学(leetcode)121.Best Time to Buy and Sell Stock
- 2017暑期集训Day 14 区间dp+二分图匹配
- poj 2318 TOYS
- hihocoder 1398 : 网络流五·最大权闭合子图
- G
- poj 3468 线段树区间更新,模板题
- Log4c的使用
- Neural Networks
- 生产者消费者(singlAll和await误用)
- HDU6038——Function(图论,tarjan)