带权并查集。
有两个人,A先写一个数组(1~N),然后B在不看的前提下挑一个子串,让A回答子串的和,这么来M次,其中A会回答一些假话,但这些假话都是可以通过前面的话推断出来真假的那种假话,没法判别真假的话都是真话。问你这些话里面总共多少假话。
首先,对这道题而言,能够判断真假的前提一定是已知子串A和B(或B和C),判断子串C(或A)的和的真假(A=B+C)。(实际这两种情况是一起实现的,因为可以互相转化)
和HDU 3047类似,首尾互相链起来的子串在一个圈子里面,然而这里有一个问题,给定的查询都是全闭区间,这样相邻区间的首尾并不重合,我们统统换成左开右闭区间。
然后,我们拿这个权值表示的是两点间的子串的和,但是,细想一下会发现,我们不仅要知道上下结点之间子串的和,还要知道上下结点的大小关系(否则传递会乱套的)。和上道题不一样(而且上道题本身就是环形关系),这个权值关系本身是不带方向的!
所以,就要想办法加上这个方向信息,有三种方法。
- 第一种比较厉害,就是靠这个权值的正负来表示方向,绝对值表示和。其中,若当前结点大于父节点,权值为负;若小于,为正。这样想的时候肯定要分情况,但是写出来代码却是一句搞定(对没错,
12种情况就是可以用一句归纳,惊不惊喜?)(为什么是12种?看下图。19.6.6更新),这样写出来看起来很玄妙,实则不可能一眼看出来,必须分情况归纳总结。(数轴上向量计算就完事了) - 第二种很傻瓜,因为我们可以利用结点本身的值来判断大小关系,那么我们只允许值较小的结点成为值较大的结点的父节点。但这样我们放弃了性能(平衡规则),但是省了编写时间。
- 当然,还有第三种,还按照第一种那么分情况,但是因为不利用权值正负了,所以分情况没法合并。
这种方法下f1连到f2与f2连到f1的关系式是一样了。。
(也可以看成是8种情况(根据f1,f2,a,f1,b,f2的关系,2^3=8)。。。但麻烦的是,这8种情况中有一种是不可能出现的(a>f1,f1>f2,f2>b),剩下7种的式子各不相同,其中有一种对应①②;有一种对应⑤⑥;有一种对应⑧⑨⑩⑪)

第一种
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 200000 + 5;
int N, M;
int pre[MAXN];
int dis[MAXN];
int counter;
int f(int x)
{
if (pre[x] < 0) return x;
int t = f(pre[x]);
dis[x] = dis[x] + dis[pre[x]];
return pre[x] = t;
}
void u(int a, int b, int s)
{
int f1 = f(a);
int f2 = f(b);
if (f1 == f2)
{
if (dis[b] + s != dis[a]) counter++;
return;
}
if (pre[f1] <= pre[f2])
{
pre[f1] += pre[f2];
pre[f2] = f1;
dis[f2] = dis[a] - s - dis[b];
}
else
{
pre[f2] += pre[f1];
pre[f1] = f2;
dis[f1] = s + dis[b] - dis[a];
}
}
int main()
{
int a, b, s;
for (; ~scanf("%d%d", &N, &M);) // 没说多组数据
{
memset(pre, -1, sizeof pre);
memset(dis, 0, sizeof dis);
counter = 0;
for (; M--;)
{
scanf("%d%d%d", &a, &b, &s);
u(a - 1, b, s);
}
printf("%d\n", counter);
}
return 0;
}
第二种
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 200000 + 5;
int N, M;
int pre[MAXN];
int dis[MAXN];
int counter;
int f(int x)
{
if (pre[x] < 0) return x;
int t = f(pre[x]);
dis[x] = dis[x] + dis[pre[x]];
return pre[x] = t;
}
void u(int a, int b, int s)
{
int f1 = f(a);
int f2 = f(b);
if (f1 == f2)
{
//if (dis[b] + s != dis[a]) counter++;
if (dis[b] - s != dis[a]) counter++;
return;
}
/*if (pre[f1] <= pre[f2])
{
pre[f1] += pre[f2];
pre[f2] = f1;
dis[f2] = dis[a] - s - dis[b];
}
else
{
pre[f2] += pre[f1];
pre[f1] = f2;
dis[f1] = s + dis[b] - dis[a];
}*/
if (f1 < f2)
{
//pre[f1] += pre[f2];
pre[f2] = f1;
dis[f2] = dis[a] + s - dis[b]; //
}
else
{
//pre[f2] += pre[f1];
pre[f1] = f2;
dis[f1] = dis[b] - s - dis[a]; //
}
}
int main()
{
int a, b, s;
for (; ~scanf("%d%d", &N, &M);) // 没说多组数据
{
memset(pre, -1, sizeof pre);
memset(dis, 0, sizeof dis);
counter = 0;
for (; M--;)
{
scanf("%d%d%d", &a, &b, &s);
u(a - 1, b, s);
}
printf("%d\n", counter);
}
return 0;
}//19.3.20 傻瓜版本
博客围绕带权并查集问题展开,描述了A写数组、B挑子串让A回答和,判断其中假话数量的问题。指出判断真假的前提,将查询区间换成左开右闭区间,还介绍了为权值关系添加方向信息的三种方法。
1888

被折叠的 条评论
为什么被折叠?



