您的位置:首页 > 其它

【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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: