ZOJ Monthly, March 2013 简单解题报告
A. ZOJ 3686 A Simple Tree Problem
题意:给出一颗有根树,树的所有节点有一个值(0或1),最初这些值都为0
定义另类操作:
指定一棵子树,将它的子树的所有节点的值取反。
指定一颗子树,求出它的子树的所有节点的值的和。
比赛开始时先看的A题。一开始的想法是Splay。后来想到其实可以转化为线段树处理,206分钟2Y。
做法不难想到,通过一个类先序遍历的方法,把所有节点子树的所有叶子节点找到(一边深搜一边将找到的节点哈希),回溯的时候记录下每个子树的最远叶子节点的hash值。实际上就是转化为区间问题了。
1 #include <cmath> 2 #include <string> 3 #include <cstdio> 4 #include <cstdlib> 5 #include <cstring> 6 #include <stack> 7 #include <queue> 8 #include <vector> 9 #include <string> 10 #include <algorithm> 11 #include <functional> 12 using namespace std; 13 typedef long long LL; 14 const double EPS = 1e-8; 15 const int INF = 0x3f3f3f3f; 16 #define lson l,mid,id<<1 17 #define rson mid+1,r,id<<1|1 18 #define mm (l+r)>>1 19 20 const int N = 100005; 21 struct Node { 22 int val, flag; 23 } node[N << 2]; 24 25 int n, m, cur, tree[N], far[N]; 26 27 struct Edge { 28 int v, next; 29 } e[N]; 30 int head[N], edge; 31 32 void AddEdge(int u, int v) { 33 e[edge].v = v; 34 e[edge].next = head[u]; 35 head[u] = edge++; 36 } 37 38 void dfs(int now) { 39 far[now] = ++cur; 40 tree[now] = cur; 41 for (int i = head[now]; i != -1; i = e[i].next) { 42 dfs(e[i].v); 43 } 44 far[now] = cur; 45 } 46 47 void pushup(int id) { 48 node[id].val = node[id<<1].val + node[id<<1|1].val; 49 } 50 51 void pushdown(int id, int l, int r) { 52 if (node[id].flag) { 53 int mid = mm; 54 node[id<<1].flag ^= 1; 55 node[id<<1|1].flag ^= 1; 56 node[id<<1].val = (mid - l + 1) - node[id<<1].val; 57 node[id<<1|1].val = (r - mid) - node[id<<1|1].val; 58 node[id].flag = 0; 59 } 60 } 61 62 void build(int l, int r, int id) { 63 node[id].val = 0; 64 node[id].flag = 0; 65 if (l == r) { 66 return; 67 } 68 int mid = mm; 69 build(lson); 70 build(rson); 71 } 72 73 void update(int L, int R, int l, int r, int id) { 74 if (L <= l && r <= R) { 75 node[id].flag ^= 1; // 奇数次操作就等于没操作 76 node[id].val = (r - l + 1) - node[id].val; // 每次必须更新 77 return; 78 } 79 pushdown(id, l, r); 80 int mid = mm; 81 if (L <= mid) update(L, R, lson); 82 if (R > mid) update(L, R, rson); 83 pushup(id); 84 } 85 86 int find(int L, int R, int l, int r, int id) { 87 if (L <= l && r <= R) { 88 return node[id].val; 89 } 90 pushdown(id, l, r); 91 int ans = 0, mid = mm; 92 if (L <= mid) ans += find(L, R, lson); 93 if (R > mid) ans += find(L, R, rson); 94 return ans; 95 } 96 97 /* 98 给出n个节点和他的祖先节点。 99 定义操作 100 o num1:将以num1为根的子树的所有节点的值取反 101 p num1:求出以num1为根的子树的所有节点的值的和 102 103 看起来像splay的题目,实际可以通过类先序遍历的方法,得到每个节点子树中的所有叶子节点, 104 并转化成区间形式记录保存。之后就是线段树的事情了。 105 */ 106 107 int main() { 108 while (~scanf("%d%d", &n, &m)) { 109 memset(tree, 0, sizeof(tree)); 110 memset(head, -1, sizeof(head)); 111 memset(far, 0, sizeof(far)); 112 edge = 0; 113 cur = 0; 114 for (int i = 2; i <= n; i++) { 115 int v; 116 scanf("%d", &v); 117 AddEdge(v, i); 118 } 119 dfs(1); 120 build(1, n, 1); 121 while (m--) { 122 char op[5]; 123 int aim; 124 scanf("%s%d", op, &aim); 125 if (op[0] == 'o') { 126 update(tree[aim], far[aim], 1, n, 1); 127 } else { 128 printf("%d\n", find(tree[aim], far[aim], 1, n, 1)); 129 } 130 } 131 printf("\n"); 132 } 133 return 0; 134 }
D. ZOJ 3689 Digging
题意:N个坟墓,最多T天的工作时间。现在已知,在第 t 天开工修建坟墓 i,可以得到 t * si 的金币。任务不能重叠,没有完成的任务不计金币。
这道题实际是赛后补做的。先根据单位时间si从小到大排序,之后就是一个01背包了。
1 #include <cmath> 2 #include <string> 3 #include <cstdio> 4 #include <cstdlib> 5 #include <cstring> 6 #include <stack> 7 #include <queue> 8 #include <vector> 9 #include <string> 10 #include <algorithm> 11 #include <iostream> 12 #include <functional> 13 using namespace std; 14 typedef long long LL; 15 const double EPS = 1e-8; 16 const int INF = 0x3f3f3f3f; 17 const int N = 3005; 18 const int T = 10005; 19 20 struct Cham { 21 int s, t; 22 bool operator <(const Cham& next) const { 23 return t * next.s > next.t * s; // 单位时间收获价值最多的排前 24 } 25 } c[N]; 26 27 int dp[T]; 28 29 int main() { 30 int n, t; 31 while (~scanf("%d%d", &n, &t)) { 32 int ans = 0; 33 memset(dp, 0, sizeof(dp)); 34 for (int i = 1; i <= n; i++) 35 scanf("%d%d", &c[i].t, &c[i].s); 36 sort(c + 1, c + n + 1); 37 for (int i = 1; i <= n; i++) { 38 for (int j = t; j >= c[i].t; j--) { 39 dp[j] = max(dp[j], dp[j - c[i].t] + (j * c[i].s)); 40 ans = max(ans, dp[j]); 41 } 42 } 43 printf("%d\n", ans); 44 } 45 return 0; 46 }
E. ZOJ 3690 Choosing number
题意:N个人站成一行,有M个数可供任意选择。唯一要求是,当相邻两人取相同数时,这个数必须大于K。
又是一道赛后补的题。比赛中队里有人尝试用O(N)的DP提交结果TLE,后来就想歪到去找O(1)的方法了,到最后也没想出 TAT
实际上仔细观察那个DP方程,就可以想到矩阵优化的方法。
先是DP方程
dp[i][0] 表示第i个人取[1..k]的时候的排列种数,dp[i][1] 表示第i个人取[k+1..m]时候的排列种数,得到方程:
观察这个形式,就可以YY矩阵快速幂的做法了:
1 #include <cmath> 2 #include <string> 3 #include <cstdio> 4 #include <cstdlib> 5 #include <cstring> 6 #include <stack> 7 #include <queue> 8 #include <vector> 9 #include <string> 10 #include <algorithm> 11 #include <iostream> 12 #include <functional> 13 using namespace std; 14 typedef long long LL; 15 const double EPS = 1e-8; 16 const int INF = 0x3f3f3f3f; 17 const int MOD = 1000000007; 18 19 const int n = 2; 20 struct Matrix { 21 LL row[5][5]; 22 Matrix multiply(const Matrix& next) { 23 Matrix tmp = {0}; 24 for (int i = 1; i <= n; i++) { 25 for (int j = 1; j <= n; j++) { 26 for (int k = 1; k <= n; k++) { 27 tmp.row[i][j] = (tmp.row[i][j] % MOD + ((row[i][k] % MOD) * (next.row[k][j] % MOD)) % MOD) % MOD; 28 } 29 } 30 } 31 return tmp; 32 } 33 } init; 34 35 Matrix dfs(LL rank) { 36 if (rank == 1) 37 return init; 38 LL mid = rank >> 1; 39 Matrix mat = dfs(mid); 40 mat = mat.multiply(mat); 41 if (rank & 1) 42 mat = mat.multiply(init); 43 return mat; 44 } 45 46 int main() { 47 int n, m, k; 48 while (~scanf("%d%d%d", &n, &m, &k)) { 49 init.row[1][1] = (k - 1) % MOD; 50 init.row[1][2] = (k) % MOD; 51 init.row[2][1] = (m - k) % MOD; 52 init.row[2][2] = (m - k) % MOD; 53 Matrix ans = dfs(n - 1); 54 LL ans1 = ((ans.row[1][1] * k) % MOD + (ans.row[1][2] * (m - k)) % MOD) % MOD; 55 LL ans2 = ((ans.row[2][1] * k) % MOD + (ans.row[2][2] * (m - k)) % MOD) % MOD; 56 printf("%lld\n", (ans1 + ans2) % MOD); 57 } 58 return 0; 59 }
H. ZOJ 3693 Happy Great BG
本质是水题,但是比较坑。主要易错点在于:
算人数不要忘记加上2个教练
如果出现0.001这样的结果,要当做0.01输出(最小单位是分)