九月题目泛做总结。
Keywords
Problem On Tree, DFS & Optimization, Heap, Greedy, List,  Interval&Backpack Dynamic Programming
Table of Contents
- Keywords
- BZOJ 2525 Dynamite- Description
- Solution
 
BZOJ 1306 循环赛- Description
- Solution
BZOJ 2151 种树, BZOJ 1150 数据备份- Description
- Solution
BZOJ 1296 粉刷匠- Description
- Solution
BZOJ 2525 Dynamite
Description
一颗 $n$ 个点的树, 有一些关键点, 需要选择关键点, 最小化选点到所有关键点距离的最大值.
Solution
直接做肯定是不好做, 我们转验证.
那么怎么验证距离是否小于等于 $mid$ 呢? 一开始以为要再套个DP, 其实是贪心, 需要记录两种信息, 来确定是子树内部可以解决掉所有关键点, 还是需要外面的帮助.也就是树内已选点到根的最小距离 $d_1$ 和树内未被覆盖的关键点的最大深度 $d_2$.
如果两者相加小于 $mid$, 说明可以自己deal. 直接把 $d_1$置0, $d_2$置 $-\infty$.
如果 $d_2 = mid$, 树根强制选, 记录下答案.
需要注意的是 $d_1 > mid$ 的情况, 这时候要先修正一下 $d_2$. 就算子树中的炸弹都搞定了, 自己也有可能是炸弹, $d_2 = max(d_2, 0)$.
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 
 | #include <iostream>#include <algorithm>
 #include <cstring>
 #include <cstdio>
 #include <cctype>
 
 using namespace std;
 
 const int maxn = 300005;
 const int inf = 0x3f3f3f3f;
 
 struct edge
 {
 int to, nxt;
 }e[maxn << 1];
 int n, m, ptr, lnk[maxn], d[maxn], cnt;
 int f[maxn], g[maxn], tot;
 
 inline void add(int bgn, int end)
 {
 e[++ptr].to = end; e[ptr].nxt = lnk[bgn];
 lnk[bgn] = ptr;
 }
 void dfs(int x, int fa, int mid)
 {
 f[x] = -inf; g[x] = inf;
 for(int p = lnk[x]; p; p = e[p].nxt)
 {
 int y = e[p].to;
 if(y == fa) continue;
 dfs(y, x, mid);
 f[x] = max(f[x], f[y] + 1);
 g[x] = min(g[x], g[y] + 1);
 }
 if(d[x] && g[x] > mid) f[x] = max(f[x], 0);
 if(f[x] + g[x] <= mid) f[x] = -inf;
 if(f[x] == mid) ++tot, f[x] = -inf, g[x] = 0;
 }
 inline bool chk(int mid)
 {
 tot = 0;
 dfs(1, 0, mid);
 if(f[1] >= 0) tot++;
 return tot <= m;
 }
 inline int rd()
 {
 register int x = 0; char c = getchar();
 while(!isdigit(c)) c = getchar();
 while(isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
 return x;
 }
 
 int main()
 {
 scanf("%d%d", &n, &m);
 for(register int i = 1; i <= n; ++i) d[i] = (rd() == 1);
 int x, y;
 for(register int i = 1; i < n; ++i)
 {
 x = rd(); y = rd();
 add(x, y); add(y, x);
 }
 int l = 0, r = n, ans = 0;
 while(l <= r)
 {
 int mid = (l + r) >> 1;
 if(chk(mid)) ans = mid, r = mid - 1;
 else l = mid + 1;
 }
 printf("%d\n", ans);
 return 0;
 }
 
 | 
BZOJ 1306 循环赛
Description
若干支队伍互相比赛, 胜+3, 负0, 平各+1.
给出比分情况, 求可能局面方案数.
$n \le 8$.
Solution
一看这个数据范围就比较适合乱搞.
剪枝三连: 全胜无解, 全负无解, 当前队伍最后一场比赛如果分差为2无解.
加强版就没这么简单了, 有机会做一下.
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | #pragma GCC optimize(3)#include <cstdio>
 
 int n, scr[10], ans, tot;
 int f[4] = {3, 1, -1, 0};
 int tmp[10];
 
 inline void dfs(int x, int y)
 {
 if(tmp[x] + (n - y + 1) * 3 < scr[x]) return;
 if(tmp[x] > scr[x]) return;
 if(x == n) {ans++; return;}
 if(y == n)
 {
 int delt = scr[x] - tmp[x];
 if(delt == 2) return;
 tmp[y] += f[delt];
 dfs(x + 1, x + 2);
 tmp[y] -= f[delt];
 }
 else
 {
 tmp[x] += 3; dfs(x, y + 1); tmp[x] -= 3;
 tmp[y] += 3; dfs(x, y + 1); tmp[y] -= 3;
 tmp[x]++; tmp[y]++; dfs(x, y + 1); tmp[x]--; tmp[y]--;
 }
 }
 
 int main()
 {
 scanf("%d", &n);
 for(int i = 1; i <= n; ++i) scanf("%d", &scr[i]);
 dfs(1, 2);
 printf("%d\n", ans);
 return 0;
 }
 
 | 
BZOJ 2151 种树, BZOJ 1150 数据备份
Description
给定每个位置的权, 不能选择相邻位置, 最大化权值和.
Solution
考虑选择一个的代价, 来修改相邻元素, 以便在更优答案时修正.
这是堆的题的一个典型性质. 
这道题中, 将相邻的都删除, 变成一个大点, 点权是 $l + r - v$.
洛谷上还有个上环的, 也同理.
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 
 | #include <iostream>#include <algorithm>
 #include <cstring>
 #include <cstdio>
 #include <queue>
 #include <vector>
 #include <cctype>
 
 using namespace std;
 
 const int maxn = 200005;
 
 struct node
 {
 int val, id;
 node(int v_ = 0, int i_ = 0):
 val(v_), id(i_){}
 bool operator < (const node &rhs) const
 {
 return val < rhs.val;
 }
 };
 int ptr, cnt, ans, lst[maxn], nxt[maxn];
 int n, k, d[maxn], tag[maxn];
 priority_queue<node> q;
 
 inline int rd()
 {
 register int x = 0, f = 0; char c = getchar();
 while(!isdigit(c)){if(c == '-') f = 1; c = getchar();}
 while(isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
 return f ? -x : x;
 }
 inline void remove(int x)
 {
 tag[x] = 1;
 lst[nxt[x]] = lst[x];
 nxt[lst[x]] = nxt[x];
 }
 
 int main()
 {
 n = rd(); k = rd();
 if(k > n / 2)
 {
 puts("Error!");
 return 0;
 }
 for(int i = 1; i <= n; ++i) d[i] = rd();
 lst[1] = n; nxt[n] = 1;
 for(int i = 2; i <= n; ++i) nxt[i-1] = i, lst[i] = i - 1;
 for(int i = 1; i <= n; ++i) q.push(node(d[i], i));
 cnt = k;
 while(cnt--)
 {
 
 while(tag[q.top().id]) q.pop();
 node u = q.top(); q.pop();
 ans += u.val;
 
 int va = d[lst[u.id]], vb = d[nxt[u.id]];
 remove(lst[u.id]); remove(nxt[u.id]);
 d[u.id] = va + vb - u.val;
 q.push(node(d[u.id], u.id));
 }
 printf("%d\n", ans);
 return 0;
 }
 
 | 
BZOJ 1296 粉刷匠
Description
有若干条木板, 每一格只能刷一种给定的颜色, 不刷或刷错都是错. 颜色只有两种.
刷一次只能在一条木板上刷连续区间, 给定刷 $T$ 次, 问最多刷正确多少块.
Solution
一眼区间DP套背包, 遂上状态 $f[i][j][k]$, 肯定都能明白.
发现跪了. 那$f[i][j][k][0/1]$. 还是很明白.
怎么又跪了的说!!! $f[i][j][k][0/1][0/1]$!
算了算了转移太长了.
其实是在一条上做 $n^2$, 每次枚举断点, 前缀和优化一下.
然后再背包.
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 
 | #include <iostream>#include <algorithm>
 #include <cstring>
 #include <cstdio>
 
 using namespace std;
 
 int n, m, t;
 int c[55][55], sum[55];
 char board[55][55];
 int dp[55][2505];
 
 int main()
 {
 scanf("%d%d%d", &n, &m, &t);
 for(int i = 1; i <= n; ++i)
 {
 scanf("%s", board[i] + 1);
 for(int j = 1; j <= m; ++j)
 sum[j] = sum[j - 1] + (board[i][j] ^ 48);
 for(int T = 1; T <= m; ++T)
 for(int j = 1; j <= m; ++j)
 {
 c[j][T] = 0;
 for(int k = 0; k < j; ++k)
 {
 int cnt = sum[j] - sum[k];
 c[j][T] = max(c[j][T], c[k][T - 1] + max(cnt, j - k - cnt));
 }
 }
 for(int j = 1; j <= t; ++j)
 for(int k = 1; k <= min(m, j); ++k)
 dp[i][j] = max(dp[i][j], dp[i - 1][j - k] + c[m][k]);
 }
 int ans = 0;
 for(int i = 1; i <= t; ++i)
 ans = max(ans, dp[n][i]);
 printf("%d\n", ans);
 return 0;
 }
 
 |