ZOJ Monthly, March 2013 简单解题报告

 

A. ZOJ 3686 A Simple Tree Problem

题意:给出一颗有根树,树的所有节点有一个值(0或1),最初这些值都为0

定义另类操作:

指定一棵子树,将它的子树的所有节点的值取反。

指定一颗子树,求出它的子树的所有节点的值的和。

比赛开始时先看的A题。一开始的想法是Splay。后来想到其实可以转化为线段树处理,206分钟2Y。

做法不难想到,通过一个类先序遍历的方法,把所有节点子树的所有叶子节点找到(一边深搜一边将找到的节点哈希),回溯的时候记录下每个子树的最远叶子节点的hash值。实际上就是转化为区间问题了。

View Code
  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背包了。

View Code
 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矩阵快速幂的做法了:

View Code
 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输出(最小单位是分)

posted @ 2013-04-01 22:35  dgsrz  阅读(947)  评论(1编辑  收藏  举报