HDU 3038 How Many Answers Are Wrong

传送门

带权并查集。
有两个人,A先写一个数组(1~N),然后B在不看的前提下挑一个子串,让A回答子串的和,这么来M次,其中A会回答一些假话,但这些假话都是可以通过前面的话推断出来真假的那种假话,没法判别真假的话都是真话。问你这些话里面总共多少假话。

首先,对这道题而言,能够判断真假的前提一定是已知子串AB(或BC),判断子串C(或A)的和的真假(A=B+C。(实际这两种情况是一起实现的,因为可以互相转化)
HDU 3047类似,首尾互相链起来的子串在一个圈子里面,然而这里有一个问题,给定的查询都是全闭区间,这样相邻区间的首尾并不重合,我们统统换成左开右闭区间

然后,我们拿这个权值表示的是两点间的子串的和,但是,细想一下会发现,我们不仅要知道上下结点之间子串的和,还要知道上下结点的大小关系(否则传递会乱套的)。和上道题不一样(而且上道题本身就是环形关系),这个权值关系本身是不带方向的!

所以,就要想办法加上这个方向信息,有三种方法。

  • 第一种比较厉害,就是靠这个权值的正负来表示方向,绝对值表示和。其中,若当前结点大于父节点,权值为负;若小于,为正。这样想的时候肯定要分情况,但是写出来代码却是一句搞定(对没错,12种情况就是可以用一句归纳,惊不惊喜?)(为什么是12种?看下图。19.6.6更新),这样写出来看起来很玄妙,实则不可能一眼看出来,必须分情况归纳总结。(数轴上向量计算就完事了)
  • 第二种很傻瓜,因为我们可以利用结点本身的值来判断大小关系,那么我们只允许值较小的结点成为值较大的结点的父节点。但这样我们放弃了性能(平衡规则),但是省了编写时间。
  • 当然,还有第三种,还按照第一种那么分情况,但是因为不利用权值正负了,所以分情况没法合并。
    这种方法下f1连到f2f2连到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 傻瓜版本
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值