zoukankan      html  css  js  c++  java
  • 狡猾的商人 题解

    题目描述

    刁姹接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的。

    账本上记录了n个月以来的收入情况,其中第i 个月的收入额为Ai(i=1,2,3...n-1,n)。当 Ai大于0时表示这个月盈利Ai 元,当 Ai小于0时表示这个月亏损Ai 元。所谓一段时间内的总收入,就是这段时间内每个月的收入额的总和。

    刁姹的任务是秘密进行的,为了调查商人的账本,她只好跑到商人那里打工。她趁商人不在时去偷看账本,可是她无法将账本偷出来,每次偷看账本时她都只能看某段时间内账本上记录的收入情况,并且她只能记住这段时间内的总收入。 现在,刁姹总共偷看了m次账本,当然也就记住了m段时间内的总收入,你的任务是根据记住的这些信息来判断账本是不是假的。

    输入格式

    第一行为一个正整数w,其中w < 100,表示有w组数据,即w个账本,需要你判断。

    每组数据的第一行为两个正整数n和m,其中n < 100,m < 1000,分别表示对应的账本记录了多少个月的收入情况以及偷看了多少次账本。

    接下来的m行表示刁姹偷看m次账本后记住的m条信息,每条信息占一行,有三个整数s,t和v,表示从第s个月到第t个月(包含第t个月)的总收入为v,这里假设s总是小于等于t。

    输出格式

    包含w行,每行是true或false。

    其中第i行为true当且仅当第i组数据,即第i个账本不是假的;第i行为false当且仅当第i组数据,即第i个账本是假的。

    样例输入

    2
    3 3
    1 2 10
    1 3 -5
    3 3 -15
    5 3
    1 5 100
    3 5 50
    1 2 51

    样例输出

    true
    false

    分析

    题目给出若干个区间的和,询问是否合法,即前后是否有冲突。

    比如 [3,5] 这三个月的和为 10,又给出 [3, 5] 的和为 11,显然不合法;
    又比如给出 [3, 6] 的和为 10,[5, 6] 的和为 7,又给出 [3, 4] 的和为 5,显然也是冲突的,我们可以通过前两组求得 [3, 4] 的和应该是 3。

    下面思路就简单了,带权并查集搞一下就 OK 了。但是不假思索地直接打肯定不行,为啥呢?
    如果给出一组 [3, 10] 的和为 100,又给出一组 [6, 10] 的和为 70,那么我们会直接把两组做一下并查集的合并操作,但是再给一组 [3, 6] 的和为 40 时,我们查询 3 和 6 在同一个集合,就得直接判断合法性了,这样我们得出 [3, 6] 的结果为 10 - 70 与 40 不相等,直接输出 false。
    但是我们仔细观察一下,用 100 - 70 真正得到的应该是 [3, 5] 的区间和,因为我们举例维护的都是闭区间。
    为了解决这个问题,我们可以用左闭右开或者左开右闭的区间来维护,这样可以避免出现上述问题。

    例如用左闭右开的区间的时候,那么根据给我的区间 [l, r],我们真正处理的时候是对 [l, r+1] 进行的操作。剩下的就是踏踏实实跑带权并查集即可。

    // 初始化多处理一个点,因为是左闭右开的区间,右边界会加 1
    // 多组测试数据,别忘了该初始化的都要处理好
    void init() {
    	for (int i = 1; i <= n + 1; ++i) {
    		fa[i] = i;
    		s[i] = 0;
    	}
    }
    
    // 找根结点,路径压缩,权值压缩的时候画图模拟向量即可
    int findroot(int x) {
    	if (x != fa[x]) {
    		int p = fa[x]; // 要先记录父结点,否则下一句压缩完路径找不到原来的父结点了
    		fa[x] = findroot(fa[x]);
    		s[x] += s[p]; // 更新权值
    	}
    	return fa[x];
    }
    
    // 主函数内部,a, b 为读入的区间,对有边界加一
    scanf("%d%d%d", &a, &b, &c);
    if (!flag) continue; // 初始值为true,如果之前判断出数据不合法,还是要把所有数据读完才行
    ++b;
    ra = findroot(a);
    rb = findroot(b);
    if (ra != rb) { // 根结点不同,需要合并
    	if (ra < rb) { // 我是把编号大的作为父结点,合并的时候模拟向量即可
    		fa[ra] = rb;
    		s[ra] = c + s[b] - s[a];
    	} else {
    		fa[rb] = ra;
    		s[rb] = s[a] - c - s[b];
    	}
    } else { // 原本在一个集合,判断吧
    	if (s[a]-s[b] != c) {
    		flag = false;
    	}
    }
    
  • 相关阅读:
    spring 实现测试解耦
    Java 反射实例 (二)
    Java 反射实例
    Thread.join()
    缓存输入/输出流
    文件的拷贝
    将键盘输入的数据保存到文件中:OutputStream
    InputStream中read方法各个参数的意义
    OutputStream
    InputStream中的主要的子类
  • 原文地址:https://www.cnblogs.com/kuangbiaopilihu/p/13271621.html
Copyright © 2011-2022 走看看