@Pastoral Oddities@

  • @Pastoral Oddities@

    • @题目描述-English@
    • @题目大意@
    • @性质1@
    • @性质2@
    • @作者最初的做法(可忽略)@
    • @正确的算法@
    • @一个实现细节@
    • @代码实现@
    • @END@

@题目描述-English@

In the land of Bovinia there are n pastures, but no paths connecting the pastures. Of course, this is a terrible situation, so Kevin Sun is planning to rectify it by constructing m undirected paths connecting pairs of distinct pastures. To make transportation more efficient, he also plans to pave some of these new paths.
Kevin is very particular about certain aspects of path-paving. Since he loves odd numbers, he wants each pasture to have an odd number of paved paths connected to it. Thus we call a paving sunny if each pasture is incident to an odd number of paved paths. He also enjoys short paths more than long paths, so he would like the longest paved path to be as short as possible. After adding each path, Kevin wants to know if a sunny paving exists for the paths of Bovinia, and if at least one does, the minimum possible length of the longest path in such a paving. Note that “longest path” here means maximum-weight edge.

Input
The first line contains two integers n (2 ≤ n ≤ 100 000) and m (1 ≤ m ≤ 300 000), denoting the number of pastures and paths, respectively. The next m lines each contain three integers ai, bi and li, describing the i-th path. The i-th path connects pastures ai and bi (1 ≤ ai, bi ≤ n; ai ≠ bi) and has length li (1 ≤ li ≤ 10^9). Paths are given in the order in which they are constructed.
Output
Output m lines. The i-th line should contain a single integer denoting the minimum possible length of the longest path (maximum-weight edge) in a sunny paving using only the first i paths. If Kevin cannot pave a set of paths so that each pasture is incident to an odd number of paved paths, output  - 1.
Note that the paving is only hypothetical—your answer after adding the i-th path should not be affected by any of your previous answers.

Examples
input
4 4
1 3 4
2 4 8
1 2 2
3 4 3
output
-1
8
8
3

input
3 2
1 2 3
2 3 4
output
-1
-1

input
4 10
2 1 987
3 2 829
4 1 768
4 2 608
3 4 593
3 2 488
4 2 334
2 1 204
1 3 114
1 4 39
output
-1
-1
829
829
768
768
768
488
334
204

Note
For the first sample, these are the paths that Kevin should pave after building the i-th path:
No set of paths works.
Paths 1 (length 4) and 2 (length 8).
Paths 1 (length 4) and 2 (length 8).
Paths 3 (length 2) and 4 (length 3).
In the second sample, there never exists a paving that makes Kevin happy.

@题目大意@

给出n个点与m次加边操作,每个操作加入一条(u,v)长度为l的边。
对于每次操作后,在已经添加的边集中求出一个子集,使得每个点度数均为奇数,且子集的最大边最小。

@性质1@

题目要求的是每个节点的度数为奇数,但是这个东西并不能很好的用数据结构进行高效维护,我们需要对这个条件进行转化。

手算一些数据后,我们发现如果n为奇数,那么永远无法找到一个合法解。更进一步地,如果一个连通块包含奇数个节点,那么这个连通块就无法满足条件的方案。于是我们可以猜测:是不是条件等价于连通块大小的奇偶性?

事实上,的确是这样的:

当且仅当一个连通块包含着偶数个点时,存在一个满足题意的边集子集。

【简要证明一下,可以跳过这一段】:
当一个连通块包含奇数个点时,因为一条边会给两个点的度数+1,整个图的度数+2,所以整个图的度数始终为偶数。假如每个点的度数都为奇数,则整个图的度数就为奇数*奇数=奇数,矛盾。
当一个连通块包含偶数个点时,我们可以这样构造出一个合法方案:先随便找连通块的一个生成树,把它当作有根树。然后从叶子出发,删除它连向父亲的边,再将新增的树中度数为一的点进行迭代处理。(有点儿类似于拓扑排序)对于每一个正在处理的节点,如果它的儿子连它的边中被选中的边有奇数条,则不选它连向它父亲的边,否则就选中它连向它父亲的边。这样就可以保证除根以外的节点是奇数度点。因为整个图的度数为偶数,所以根节点的度数一定也为奇数。

@性质2@

但是,我们已知的数据结构大多是用来维护一棵树的,我们必须还要最优性的分析,删除那些不需要的边。题目要求的是边集的最大边权最小,于是我们可以猜测边集是最小生成树上的边。
事实上,我们又一次的猜对了(被打)

存在一个最优边集,使边集中的边都在最小生成树上。

【以下是作者的自我理解与简要证明】
首先我们需要证明这样一个引理:

将一条路径上的选中的边变成为未选中的边,未选中的边变成为选中的边,起点与终点的奇偶性反转,其他节点的奇偶性不变。

一条路径上除了起点与终点外的点,入边与出边都是成对存在,将入边状态反转,点的奇偶性反转;将出边状态反转,点的奇偶性再次反转,变回原样。而起点有一条出边、终点有一条入边,所以起点与终点的奇偶性将会改变。
特别地,假如路径的起点终点首尾相接,形成一个环的话,会得到这样一个定理:

将一个环上的选中的边变成为未选中的边,未选中的边变成为选中的边,则不会影响方案的合法性。

根据上面这个定理,我们就可以执行这样一个操作:将一个环反转,使环上的最大边变为未选中的边,再删除这条最大边。这个操作是正确的,贪心地想:我们既然可以一直让这条最大边对答案不再有贡献,我们为什么不删掉它呢?

这个操作是不是很像最小生成树的破圈操作呢?
动态维护MST,是不是一道LCT的模板题呢?

@作者最初的做法(可忽略)@

作者最初的做法是这样的:
首先是将边权信息改为点权信息的正常操作。
给每条边一个flag变量表示这条边是否被选中。加入一条边,如果该边连接的两个顶点已经连通,则进行破圈操作,并用链反转操作维护方案的合法性;否则,将边的初始flag置为false,加入该边。
用并查集维护每个连通块的大小,一开始大小为奇数的连通块数量为n,当两个大小为奇数的连通块合并时,奇数连通块的个数减2,当奇数连通块的数量为0时,输出答案。
……
然而并没有办法高效实现链反转操作……该方法作废……

@正确的算法@

上面的方法到底哪里出了问题呢?我们并没有要把方案构造出来,只是问在合法方案下的最大边权。而上面的方法维护出了方案,多走了一步,自然不高效。
考虑这样一个方法:一样的加边破圈维护MST,只是在输出答案前,对最大权值那条边进行检查,如果将这条边cut掉依然存在合法方案,就将它cut掉并继续检测边权第二大的边。这个过程可以用set来进行。怎么判断cut掉是否合法呢?这个时候我们就需要用到性质1。即如果一条边连接的两个连通块的大小都为偶数,这条边就可以被cut掉。运用贪心的思想就不难证明这个方法的正确性。

总结一下算法流程:
1)加入一条边,在set里相应的加入这条边,判断这条边连接的两个节点是否已经连通,连通进行 2),否则进行 3)
2)执行破圈操作,将最大边权的边相应从set里面删除。进行 4)
3)如果加入这条边连接的两个连通块大小为奇数,奇数连通块数量 - 2。进行4)
4)从大到小检测每一条在LCT里的边,如果该边连接的两个连通块大小为偶数,在set与LCT里将这条边删除。进行5)
5)如果奇数连通块的数量为0,输出set里的最大边权,否则输出无解。

@一个实现细节@

怎么用LCT维护一颗子树的大小呢???LCT不是只能维护路径信息吗???

大家可以先看看下面这篇博客的讲解,我觉得很详细了。
dalao的博客——LCT维护子树的方法

我在这里大概讲讲是怎么一回事吧:对于每个节点维护两个信息,即所有虚儿子的信息与总信息。PushUp时更新x的总信息为:x实儿子的总信息+x虚儿子的信息+x本身的信息。

由于Link-Cut-Tree的基本操作中,只有Access和Link会对虚儿子的信息进行修改。所以我们就对这两个操作进行些改变。

Access操作中割断了某条实边,该边变为了虚边,所以应该加到x的虚儿子信息中,加入了某条实边,该边不再是虚边,所以应从x的虚儿子信息中减去。

Link操作中为了在加入x时同时更新y的信息,需要Makeroot(x),Makeroot(y),然后连x->y的虚边,将x的信息统计入y的虚儿子信息中。
【基本全靠复制233因为我觉得这篇博客讲的真的很好】

@代码实现@

如果还有什么不懂的就自行阅读代码吧!代码会告诉你一切的!

#include<set>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 100000;
const int MAXM = 300000;
struct node{int key, id, rev;int siz1, siz2, s;node *fa, *ch[2], *mx;
}tree[MAXN+MAXM+5], *ad[MAXN+MAXM+5];
struct edge{int u, v, w, id;edge(int _u=0, int _v=0, int _w=0, int _i=0):u(_u), v(_v), w(_w), id(_i){}
}e[MAXM + 5];
bool operator < (edge a, edge b) {if( a.w == b.w ) return a.id < b.id;return a.w < b.w;
}
set<edge>Set;
set<edge>::iterator it;
node *NIL, *ncnt;
void Init() {NIL = ncnt = &tree[0];NIL->fa = NIL->ch[0] = NIL->ch[1] = NIL->mx = NIL;NIL->key = -1;
}
bool IsRoot(node *x) {return (x->fa == NIL) || (x->fa->ch[0] != x && x->fa->ch[1] != x);
}
void SetChild(node *x, node *y, int d) {x->ch[d] = y;if( y != NIL ) y->fa = x;
}
node *NewNode(int k, int id, int s) {ncnt++;ncnt->key = k, ncnt->id = id, ncnt->s = ncnt->siz1 = s;ncnt->fa = ncnt->ch[0] = ncnt->ch[1] = NIL;ncnt->mx = ncnt;return ncnt;
}
void PushDown(node *x) {if( x->rev ) {swap(x->ch[0], x->ch[1]);if( x->ch[0] != NIL ) x->ch[0]->rev ^= 1;if( x->ch[1] != NIL ) x->ch[1]->rev ^= 1;x->rev = 0;}
}
void PushUp(node *x) {if( x->key > x->ch[0]->mx->key && x->key > x->ch[1]->mx->key )x->mx = x;else if( x->ch[0]->mx->key > x->ch[1]->mx->key )x->mx = x->ch[0]->mx;else x->mx = x->ch[1]->mx;x->siz1 = x->s + x->ch[0]->siz1 + x->ch[1]->siz1 + x->siz2;
}
void Rotate(node *x) {node *y = x->fa;PushDown(y), PushDown(x);int d = (y->ch[1] == x);if( IsRoot(y) ) x->fa = y->fa;else SetChild(y->fa, x, y->fa->ch[1] == y);SetChild(y, x->ch[!d], d);SetChild(x, y, !d);PushUp(y);
}
void Splay(node *x) {PushDown(x);while( !IsRoot(x) ) {node *y = x->fa;if( IsRoot(y) )Rotate(x);else {if( (y->fa->ch[1] == y) == (y->ch[1] == x) )Rotate(y);else Rotate(x);Rotate(x);}}PushUp(x);
}
void Access(node *x) {node *y = NIL;while( x != NIL ) {Splay(x);x->siz2 += x->ch[1]->siz1;x->ch[1] = y;x->siz2 -= x->ch[1]->siz1;PushUp(x);y = x, x = x->fa;}
}
void MakeRoot(node *x) {Access(x), Splay(x);x->rev ^= 1;
}
void Link(node *x, node *y) {MakeRoot(x); MakeRoot(y);x->fa = y; y->siz2 += x->siz1;
}
void Cut(node *x, node *y) {MakeRoot(x);Access(y), Splay(y);y->ch[0] = x->fa = NIL;PushUp(y);
}
node *FindRoot(node *x) {Access(x), Splay(x);node *ret = x;while( ret->ch[0] != NIL )ret = ret->ch[0];PushUp(ret);return ret;
}
node *LCA(node *x, node *y) {Access(x), Splay(x);Splay(y);while( y->fa != NIL ) {y = y->fa;Splay(y);}return y;
}
node *QueryMAX(node *x, node *y) {node *z = LCA(x, y);node *ret = z;Access(x), Splay(z);if( z->ch[1]->mx->key > ret->key )ret = z->ch[1]->mx;Access(y), Splay(z);if( z->ch[1]->mx->key > ret->key )ret = z->ch[1]->mx;return ret;
}
int n, m, stot;
int main() {Init();scanf("%d%d", &n, &m);for(int i=1;i<=n;i++)ad[i] = NewNode(-1, -1, 1);stot = n;for(int i=1;i<=m;i++) {e[i].id = i;scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);if( e[i].u == e[i].v ) continue;ad[n+i] = NewNode(e[i].w, i, 0);Set.insert(e[i]);if( FindRoot(ad[e[i].u]) == FindRoot(ad[e[i].v]) ) {node *p = QueryMAX(ad[e[i].u], ad[e[i].v]); Splay(p);if( p->key > e[i].w ) {Set.erase(e[p->id]);Cut(p, ad[e[p->id].u]), Cut(p, ad[e[p->id].v]);Link(ad[e[i].u], ad[n+i]), Link(ad[e[i].v], ad[n+i]);}else Set.erase(e[i]);}else {MakeRoot(ad[e[i].u]), MakeRoot(ad[e[i].v]);if( ad[e[i].u]->siz1 % 2 == 1 && ad[e[i].v]->siz1 % 2 == 1 ) stot -= 2;Link(ad[e[i].u], ad[n+i]), Link(ad[e[i].v], ad[n+i]);}if( stot == 0 ) {it = Set.end(); it--;while( true ) {node *p = ad[n+it->id], *q = ad[e[it->id].u], *r = ad[e[it->id].v];MakeRoot(p); Access(q); Access(r);if( q->siz1 & 1 ) break;Access(q);if( r->siz1 & 1 ) break;Set.erase(it);it = Set.end(); it--;}it = Set.end(); it--;printf("%d\n", it->w);}else printf("-1\n");}
}

@END@

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~

【CodeForces - 603E】Pastoral Oddities相关推荐

  1. 【CF603E】Pastoral Oddities cdq分治+并查集

    [CF603E]Pastoral Oddities 题意:有n个点,依次加入m条边权为$l_i$的无向边,每次加入后询问:当前图是否存在一个生成子图,满足所有点的度数都是奇数.如果有,输出这个生成子图 ...

  2. 【CodeForces - 144C】Anagram Search(尺取,滑窗问题,处理字符串计数)

    题干: A string t is called an anagram of the string s, if it is possible to rearrange letters in t so ...

  3. 【CodeForces - 574B】Bear and Three Musketeers (枚举边,思维,优秀暴力)

    题干: Do you know a story about the three musketeers? Anyway, you will learn about its origins now. Ri ...

  4. 【CodeForces - 608C】Chain Reaction (二分 或 dp ,思维)

    题干: 题目大意: 题意是在一条直线上坐落着不同位置的灯塔,每一个灯塔有自己的power level,当作是射程范围.现在从最右边的灯塔开始激发,如果左边的灯塔在这个灯塔的范围之内,那么将会被毁灭.否 ...

  5. 「一题多解」【CodeForces 85D】Sum of Medians(线段树 / 分块)

    题目链接 [CodeForces 85D]Sum of Medians 题目大意 实现一个setsetset,支持插入,删除,求∑a5k+3∑a5k+3\sum a_{5k+3}.注意,setsets ...

  6. 【CodeForces 997C】Sky Full of Stars(组合计数)

    题目链接:[CodeForces 997C]Sky Full of Stars 官方题解:Codeforces Round #493 - Editorial 题目大意:有一个n×nn×nn\times ...

  7. 【codeforces 812C】Sagheer and Nubian Market

    [题目链接]:http://codeforces.com/contest/812/problem/C [题意] 给你n个物品; 你可以选购k个物品;则 每个物品有一个基础价值; 然后还有一个附加价值; ...

  8. 【codeforces 508B】Anton and currency you all know

    [题目链接]:http://codeforces.com/contest/508/problem/B [题意] 给你一个奇数; 让你交换一次数字; 使得这个数字变成偶数; 要求偶数要最大; [题解] ...

  9. 【codeforces 711B】Chris and Magic Square

    [题目链接]:http://codeforces.com/contest/711/problem/B [题意] 让你在矩阵中一个空白的地方填上一个正数; 使得这个矩阵两个对角线上的和; 每一行的和,每 ...

最新文章

  1. 私有网盘nextcloud 12的问题处理及优化
  2. CodeForces 392C Yet Another Number Sequence 矩阵快速幂
  3. 对象androidandroid 开发中 如何取得ListView 的 每条Item 的对象
  4. Tomcat运行三种模式:http-bio|http-nio|http-apr介绍
  5. 三星oneui主屏幕费电_这或许是单手握持手感最佳的手机 三星Galaxy S20上手体验...
  6. homebrew下安装mysql_Mac下homebrew安装Mysql以及配置问题
  7. 计算机写程序的步骤,电脑怎么编程 电脑编程的方法
  8. 为戴尔服务器下载ESXi
  9. 用心理书籍来帮助自身成长
  10. nginx服务器添加微信小程序校验文件
  11. 阿里巴巴十周年有感----宗教的盛宴
  12. 目标检测里,视频与图像有何区别?
  13. 夜间环境人脸识别_基于人脸识别的夜间疲劳驾驶判断方法与流程
  14. Chinese Segmentation Introduction
  15. lepus mysql 复制监控_lepus 天兔监控Mysql部署
  16. 图片对象(lv_img_t)的应用
  17. 20201215记一次502错误
  18. 2021-BUGKU-做题记录-WEB
  19. 计算机病毒及其防治 Computer Virus Analysis and Antivirus
  20. stc12c5a32s2c语言程序,为什么STC12C5A32S2程序不能烧录进去啊

热门文章

  1. 为什么很多企业都会选择微博营销呢?
  2. WTL 自绘控件库 (CQsTabCtrl)
  3. MySQL高可用和读写分离
  4. Ai智能自动写原创文章的软件是真是假?
  5. 记[HCTF 2018]Hideandseek
  6. 计算机网络 网络层 默认路由
  7. 程序员的自我修养之数学基础07:正交矩阵(正交向量、标准正交基、正交矩阵)
  8. 一阶广义差分模型_向后差分公式法、广义 α 法和龙格-库塔法
  9. 为什么在fonts文件夹菜单中没有“安装新字体”选项
  10. 解决Linux镜像使用软碟通制作系统安装盘无法启动的问题