【BZOJ 2152 聪聪可可】【点分治 + 一个套路】
2017-12-11 11:37
597 查看
暴力
暴力做法:点分治求出线段,暴力枚举两两线段是否相加 % 3 = 0,会 TLE。#include <bits/stdc++.h> using namespace std; const int N = 2e4 + 5; struct Edge { int next, to, w; }e[N << 1]; struct seg1 { int dis, pos; }seg1[N << 1]; int sum, rt = 0; int ans = 0, tot = 0; bool judge(int x, int y) { return ((x + y) % 3 == 0) ? 1 : 0; } int gcd(int a, int b) { return (b == 0) ? a : gcd(b, a % b); } int cnt = 0; int head ; void add(int x, int y, int z) { e[++ cnt].to = y; e[cnt].w = z; e[cnt].next = head[x]; head[x] = cnt; } int cnt2 = 0; void add2(int dis, int pos) { seg1[++ cnt2].dis = dis; seg1[cnt2].pos = pos; } int son , size , vis ; void dfs1(int u, int fa) { son[u] = 1; size[u] = 0; for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (v == fa || vis[v]) continue; dfs1(v, u); son[u] += son[v]; size[u] = max(size[u], son[v]); } size[u] = max(size[u], sum - son[u]); if (size[u] < size[rt]) rt = u; } int dis ; void dfs2(int u, int fa, int num) { son[u] = 1; add2(dis[u], num); if (judge(dis[u], 0)) ans ++; tot ++; for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (v == fa || vis[v]) continue; dis[v] = dis[u] + e[i].w; dfs2(v, u, num); son[u] += son[v]; } } void cal(int u) { int temnum = 0; cnt2 = 0; for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (vis[v]) continue; dis[v] = e[i].w; dfs2(v, u, ++ temnum); } for (int i = 1; i < cnt2; i ++) for (int j = i + 1; j <= cnt2; j ++) if (seg1[i].pos != seg1[j].pos) { tot ++; if (judge(seg1[i].dis, seg1[j].dis)) ans ++; } } void solve(int u) { vis[u] = 1; cal(u); for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (vis[v]) continue; size[0] = sum = son[v]; dfs1(v, rt = 0); solve(rt); } } int main() { memset(vis, 0, sizeof(vis)); int n; scanf("%d", &n); for (int i = 1; i < n; i ++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c), add(b, a, c); } size[0] = sum = n; dfs1(1, 0); solve(rt); ans <<= 1; tot <<= 1; // a b 和 b a 是两种情况 ans += n; tot += n; // 两人选了相同点,距离为 0,有 n 钟可能 int tem = gcd(ans, tot); ans /= tem; tot /= tem; printf("%d/%d\n", ans, tot); return 0; }
优化
我们每次暴力枚举两两线段,是一个 n2 找答案的过程。我们思考一下,一条余数为
2的线段加一个余数为
1的线段。余数肯定为
0。
所以我们并不需要记录是哪一条线段,只要知道余数是
0/1/2的线段有几条就好了。
now[0/1/2]表示当前子树
% 3意义下的线段条数,
pre[0/1/2]表示之前子树
% 3意义下的线段条数。
根据乘法原理不同子树线段直接匹配就可以计算出答案。
一个套路
(这个套路是 小 ly 想出来想出来哒!喵呜一组数,不重复的两两相乘可以化简为相乘累加的形式。(实质 -> 代码中的表现形式)
例如:
a∗b+(a+b)∗c+(a+b+c)∗d=a∗b+a∗c+b∗c+a∗d+b∗d+c∗d=a∗(b+c+d)+b∗(c+d)+c∗d
这三个等式就是:代码中的表现形式 = 实质 = 通过乘法结合律化简实质后的表现形式
对于本题来说代码中的表现形式中的
a可以理解为
pre[],
b可以理解为
now[]-> 然后
a + b成为
pre[],
c成为新的
now[]-> 以此类推 …
实质就是我们暴力两两合并表示余数的线段的过程。
通过乘法结合律化简实质后的表现形式 很好理解嘛就是化简一下辣。
–
为什么说这是个套路呢
因为它在其他题里也有很好的体现比如:NOIP 2014 D1T2 联合权值
小 ly 的题解w
兄弟关系中算联合权值的和时所有儿子两两排列的乘积就可以按这个套路去做。
#include <bits/stdc++.h> using namespace std; const int N = 2e4 + 5; struct Edge { int next, to, w; }e[N << 1]; struct seg1 { int dis, pos; }seg1[N << 1]; int sum, rt = 0; int ans = 0, tot = 0; int now , pre ; int gcd(int a, int b) { return (b == 0) ? a : gcd(b, a % b); } int cnt = 0; int head ; void add(int x, int y, int z) { e[++ cnt].to = y; e[cnt].w = z; e[cnt].next = head[x]; head[x] = cnt; } int cnt2 = 0; void add2(int dis, int pos) { seg1[++ cnt2].dis = dis; seg1[cnt2].pos = pos; } int son , size , vis ; void dfs1(int u, int fa) { son[u] = 1; size[u] = 0; for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (v == fa || vis[v]) continue; dfs1(v, u); son[u] += son[v]; size[u] = max(size[u], son[v]); } size[u] = max(size[u], sum - son[u]); if (size[u] < size[rt]) rt = u; } int dis ; void dfs2(int u, int fa, int num) { son[u] = 1; dis[u] %= 3; now[dis[u]] ++; for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (v == fa || vis[v]) continue; (dis[v] = dis[u] + e[i].w) %= 3; dfs2(v, u, num); son[u] += son[v]; } } void cal(int u) { pre[0] = 1; // 表示自己这个点 ans ++; int temnum = 0; cnt2 = 0; for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (vis[v]) continue; (dis[v] = e[i].w) %= 3; dfs2(v, u, ++ temnum); for (int i = 0; i < 3; i ++) ans += pre[i] * now[(3 - i) % 3] * 2; for (int i = 0; i < 3; i ++) pre[i] += now[i], now[i] = 0; } for (int i = 0; i < 3; i ++) pre[i] = 0; } void solve(int u) { vis[u] = 1; cal(u); for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (vis[v]) continue; size[0] = sum = son[v]; dfs1(v, rt = 0); solve(rt); } } int main() { memset(vis, 0, sizeof(vis)); int n; scanf("%d", &n); for (int i = 1; i < n; i ++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c), add(b, a, c); } size[0] = sum = n; dfs1(1, 0); solve(rt); tot = n * n; int tem = gcd(ans, tot); ans /= tem; tot /= tem; printf("%d/%d\n", ans, tot); return 0; }
相关文章推荐
- [Bzoj2152]聪聪可可(点分治)
- 【点分治】bzoj2152 聪聪可可
- BZOJ2152 聪聪可可 点分治题解
- BZOJ2152 聪聪可可(点分治)
- 【BZOJ 2152】聪聪可可 点分治
- 【bzoj2152】【聪聪可可】【点分治】
- 【bzoj2152】聪聪可可 点分治
- BZOJ 2152 聪聪可可 | 树的点分治
- BZOJ2152[聪聪可可] 点分治
- BZOJ 2152: 聪聪可可 树分治
- [bzoj2152]聪聪可可(点分治)
- bzoj 2152: 聪聪可可 树的点分治
- 【bzoj2152】聪聪可可 点分治
- bzoj 2152: 聪聪可可(点分治)
- 【BZOJ2152】聪聪可可【点分治】
- 【树分治】 BZOJ 2152 聪聪可可
- Bzoj 2152: 聪聪可可(点分治)
- bzoj 2152 聪聪可可(点分治模板)
- 洛谷 P2634 BZOJ 2152 【模板】点分治(聪聪可可)
- [国家集训队][bzoj 2152] 聪聪可可 [点分治]