Codeforces 603E Pastoral Oddities
传送门:http://codeforces.com/problemset/problem/603/E
【题目大意】
给出$n$个点,$m$个操作,每个操作加入一条$(u, v)$长度为$l$的边。
对于每次操作后,求出一个边集,使得每个点度数均为奇数,且边集的最大边最小。
$n \leq 10^5, m \leq 3 * 10^5$
【题解】
有结论:满足条件(每个点度数均为奇数),当且仅当每个连通块大小都是偶数(容易证明,从下往上,调整法)。
那么显然可以LCT维护连通性,连通块大小以及最大边位置,每次拉出最大边,加入即可。做法有点像水管局长那题。
复杂度$O((n+m)log(n+m))$,常数大……LCT太不优美了!!!
考虑一种优美的做法:
这个类似于动态维护生成树(生成森林),那么是否可以分治?
答案是可以的。一开始我们往整体二分的方向想,发现没有办法支持并查集的撤销,然后就gg了。实际上这道题有一个非常优美的分治做法!
【手动分割】
容易发现把-1看成inf,那么答案是不增的。
定义$solve(l, r, lo, hi)$表示目前处理的操作区间为$[l, r]$,答案区间为$[lo, hi]$。
那么考虑求出$mid = (l+r)/2$的时候的答案$ans[mid]$。
那么我们是不是可以根据$mid$和$ans[mid]$,划分成$solve(l, mid-1, ans[mid], hi)$和$solve(mid+1, r, lo, ans[mid])$来分治!!!
想到这里了,问题在于怎样求出$ans[mid]$以及划分区间需要进行的并查集操作。
这里的并查集容易发现,要使用按秩合并,不能路径压缩,因为要支持撤销。(支持撤销的并查集套路有很多,比如bzoj连通图、二分图那几题)
考虑求$ans[mid]$,我们画一张图,横坐标代表询问id,纵坐标代表length。
(字母要用光了QAQ)
我们目标是求出ans[mid]的这条线从而划分成BFQG和DEQH来分治,目前的区域为ABCD。
因为我们要求mid的答案,相当于求Q点的坐标。所以$[l,mid-1]$的边相当于已经加了。
考虑矩形DHYX,表示$[l, mid-1]$的边,权值范围小于$lo$,容易发现这个对于我们统计$mid$的答案是必须加入的,加入即可。
接下来按从小到大的顺序依次加入每一条权值在$[lo, hi]$的边,然后实时记录是否满足条件了,如果满足,那么我们就能求出$ans[mid]$。
求完$ans[mid]$要撤销并查集操作哦~
那么这步就做完了,下面就有两个问题了。
1. 找不到$ans[mid]$,那么说明$[l, mid]$都没有解,就能把DHYX的所有边加入并查集中了,然后递归寻找$solve(mid+1, r, lo, hi)$即可,记得撤销操作。
2. 找到了$ans[mid]$:
这个就非常兹磁了对吧,我们就可以分成两块做了。
考虑如果递归到$solve(mid+1, r, lo, ans[mid])$,那么同样的,DHYX的所有边也可以被加入并查集中了。
考虑如果递归到$solve(l, mid-1, ans[mid], hi)$,那么,STDE中的所有边也可以被加入并查集中了。
那么维护这个东西就行了。
容易发现,每条边最多被加入$O(logm)$次,每次复杂度为$O(logn)$,所以复杂度为$O(mlogmlogn)$,由于常数小,实测跑的飞快。
【手动分割-2】
真的跑的飞快吗?
第一次测:TLE on test 83(代码见下)
- # include <vector>
- # include <stdio.h>
- # include <string.h>
- # include <iostream>
- # include <algorithm>
- // # include <bits/stdc++.h>
- using namespace std;
- typedef long long ll;
- typedef long double ld;
- typedef unsigned long long ull;
- const int M = 3e5 + , N = 1e5 + ;
- const int mod = 1e9+;
- int n, m, mx;
- struct op {
- int a, b, l, sl;
- }q[M];
- vector<int> v[M], ps;
- struct us {
- struct backup {
- int x, y, del;
- } st[N]; int stn;
- int fa[N], sz[N], cnt_odd;
- inline void set(int n) {
- cnt_odd = n; stn = ;
- for (int i=; i<=n; ++i) fa[i] = i, sz[i] = ;
- }
- inline int getf(int x) {
- return fa[x] == x ? x : getf(fa[x]);
- }
- inline void un(int x, int y) {
- x = getf(x), y = getf(y);
- if(x == y) return ;
- if(sz[x] < sz[y]) swap(x, y);
- ++stn;
- if((sz[x] & ) && (sz[y] & )) cnt_odd -= , st[stn].del = ;
- else st[stn].del = ;
- st[stn].x = x, st[stn].y = y;
- sz[x] += sz[y]; fa[y] = x;
- }
- inline void re() {
- backup s = st[stn--];
- cnt_odd += s.del;
- sz[s.x] -= sz[s.y]; fa[s.y] = s.y;
- }
- inline bool check() {
- return cnt_odd == ;
- }
- }S;
- int ans[M];
- inline void solve(int l, int r, int lo, int hi) {
- if(l > r) return ;
- // now doing intervals [l, r], the answer is in [lo, hi]
- // find the answer of mid = (l+r)/2
- int mid = l+r>>, ans_mid = -, lst = S.stn;
- // add in edges that in interval [l, mid], and satisfy length < lo
- for (int i=l; i<=mid; ++i)
- if(q[i].sl < lo) S.un(q[i].a, q[i].b);
- // add in edges from [lo] to [hi], and find the answer of mid
- for (int i=lo; i<=hi; ++i) {
- for (int j=; j<v[i].size(); ++j)
- if(v[i][j] <= mid) S.un(q[v[i][j]].a, q[v[i][j]].b);
- // if satisfy the condition
- if(S.check()) { ans_mid = i; break; }
- }
- while(S.stn > lst) S.re();
- if(ans_mid == -) {
- // cannot find the answer of mid
- for (int i=l; i<=mid; ++i) ans[i] = -;
- // add in edges that in interval [l, mid], and satisfy length < lo
- for (int i=l; i<=mid; ++i)
- if(q[i].sl < lo) S.un(q[i].a, q[i].b);
- solve(mid+, r, lo, hi);
- while(S.stn > lst) S.re();
- return ;
- }
- // set the answer of mid to ans_mid
- // so to interval [l, mid], the answer is [ans_mid, hi];
- // to interval [mid+1, r], the answer is [lo, ans_mid].
- ans[mid] = ans_mid;
- // for the right side [mid+1, r], we can add in edges that in [l, mid], and satisfy length < lo.
- for (int i=l; i<=mid; ++i)
- if(q[i].sl < lo) S.un(q[i].a, q[i].b);
- solve(mid+, r, lo, ans_mid);
- while(S.stn > lst) S.re();
- // for the left side [l, mid], we can add in edges that satisfy length < ans_mid, and in [1, l)
- for (int i=lo; i<ans_mid; ++i)
- for (int j=; j<v[i].size(); ++j)
- if(v[i][j] < l) S.un(q[v[i][j]].a, q[v[i][j]].b);
- solve(l, mid-, ans_mid, hi);
- while(S.stn > lst) S.re();
- }
- int main() {
- cin >> n >> m; S.set(n);
- if(n & ) {
- while(m --) puts("-1");
- return ;
- }
- for (int i=; i<=m; ++i) {
- scanf("%d%d%d", &q[i].a, &q[i].b, &q[i].l);
- ps.push_back(q[i].l);
- }
- sort(ps.begin(), ps.end());
- ps.erase(unique(ps.begin(), ps.end()), ps.end());
- for (int i=; i<=m; ++i) {
- q[i].sl = lower_bound(ps.begin(), ps.end(), q[i].l) - ps.begin() + ;
- v[q[i].sl].push_back(i);
- }
- solve(, m, , ps.size());
- for (int i=; i<=m; ++i) {
- if(ans[i] == -) puts("-1");
- else printf("%d\n", ps[ans[i]-]);
- }
- return ;
- }
感受了下原因,这是一个$n = 8, m = 2.6 * 10^5$的非常稠密图。我之前的写法是离散,用vector维护每种值有多少,然后会出现的问题是$ans[mid]$可能很长时间不动,那么访问$ans[mid]$里所有的值是近似$O(m)$复杂度,所以肯定爆炸了啊!
有两种解决方法,一种加入id,表示是$ans[mid]$中的第$id$个节点达到答案;一种是用排序后的边的下标来替代值,那么$lo$和$hi$就变成了边的下标。
我设排序前数组为q,排序后为p。
然后我还傻逼WA了一次,因为我直接把q[i].l和p[lo].l比大小,小于就加入,如果有q[i].l=p[lo].l,就很难分清楚要不要加入了,这个的解决办法是对于q加入一个id表示在p中的位置,那么就兹磁比大小了。
现在跑的飞快了!
- # include <stdio.h>
- # include <string.h>
- # include <iostream>
- # include <algorithm>
- // # include <bits/stdc++.h>
- using namespace std;
- typedef long long ll;
- typedef long double ld;
- typedef unsigned long long ull;
- const int M = 3e5 + , N = 1e5 + ;
- const int mod = 1e9+;
- int n, m;
- struct op {
- int a, b, l, id;
- friend bool operator < (op a, op b) {
- return a.l < b.l;
- }
- }q[M], p[M];
- struct us {
- struct backup {
- int x, y, del;
- } st[N]; int stn;
- int fa[N], sz[N], cnt_odd;
- inline void set(int n) {
- cnt_odd = n; stn = ;
- for (int i=; i<=n; ++i) fa[i] = i, sz[i] = ;
- }
- inline int getf(int x) {
- return fa[x] == x ? x : getf(fa[x]);
- }
- inline void un(int x, int y) {
- x = getf(x), y = getf(y);
- if(x == y) return ;
- if(sz[x] < sz[y]) swap(x, y);
- ++stn;
- if((sz[x] & ) && (sz[y] & )) cnt_odd -= , st[stn].del = ;
- else st[stn].del = ;
- st[stn].x = x, st[stn].y = y;
- sz[x] += sz[y]; fa[y] = x;
- }
- inline void re() {
- backup s = st[stn--];
- cnt_odd += s.del;
- sz[s.x] -= sz[s.y]; fa[s.y] = s.y;
- }
- inline bool check() {
- return cnt_odd == ;
- }
- }S;
- int ans[M];
- inline void solve(int l, int r, int lo, int hi) {
- if(l > r) return ;
- // CAUTION!!! [lo, hi] cannot be real number, it must be the index of the array!!!
- // now doing intervals [l, r], the answer is in [lo, hi]
- // find the answer of mid = (l+r)/2
- int mid = l+r>>, ans_mid = -, lst = S.stn;
- // add in edges that in interval [l, mid], and satisfy length < lo (that is, number < lo
- for (int i=l; i<=mid; ++i)
- if(q[i].id < lo) S.un(q[i].a, q[i].b);
- // add in edges from [lo] to [hi], and find the answer of mid
- for (int i=lo; i<=hi; ++i) {
- if(p[i].id <= mid) S.un(p[i].a, p[i].b);
- // if satisfy the condition
- if(S.check()) { ans_mid = i; break; }
- }
- while(S.stn > lst) S.re();
- if(ans_mid == -) {
- // cannot find the answer of mid
- for (int i=l; i<=mid; ++i) ans[i] = -;
- // add in edges that in interval [l, mid], and satisfy length < lo
- for (int i=l; i<=mid; ++i)
- if(q[i].id < lo) S.un(q[i].a, q[i].b);
- solve(mid+, r, lo, hi);
- while(S.stn > lst) S.re();
- return ;
- }
- // set the answer of mid to ans_mid
- // so to interval [l, mid], the answer is [ans_mid, hi];
- // to interval [mid+1, r], the answer is [lo, ans_mid].
- ans[mid] = p[ans_mid].l;
- // for the right side [mid+1, r], we can add in edges that in [l, mid], and satisfy length < lo.
- for (int i=l; i<=mid; ++i)
- if(q[i].id < lo) S.un(q[i].a, q[i].b);
- solve(mid+, r, lo, ans_mid);
- while(S.stn > lst) S.re();
- // for the left side [l, mid], we can add in edges that satisfy length < ans_mid, and in [1, l)
- for (int i=lo; i<ans_mid; ++i)
- if(p[i].id < l) S.un(p[i].a, p[i].b);
- solve(l, mid-, ans_mid, hi);
- while(S.stn > lst) S.re();
- }
- int main() {
- cin >> n >> m; S.set(n);
- if(n & ) {
- while(m --) puts("-1");
- return ;
- }
- for (int i=; i<=m; ++i) {
- scanf("%d%d%d", &q[i].a, &q[i].b, &q[i].l);
- p[i] = q[i]; p[i].id = i;
- }
- sort(p+, p+m+);
- for (int i=; i<=m; ++i) q[p[i].id].id = i;
- solve(, m, , m);
- for (int i=; i<=m; ++i) printf("%d\n", ans[i]);
- return ;
- }
Codeforces 603E Pastoral Oddities的更多相关文章
- 【CF603E】Pastoral Oddities cdq分治+并查集
[CF603E]Pastoral Oddities 题意:有n个点,依次加入m条边权为$l_i$的无向边,每次加入后询问:当前图是否存在一个生成子图,满足所有点的度数都是奇数.如果有,输出这个生成子图 ...
- CF603E Pastoral Oddities
CF603E Pastoral Oddities 度数不好处理.转化题意:不存在连通块为奇数时候就成功了(自底向上调整法证明) 暴力:从小到大排序加入.并查集维护.全局变量记录奇数连通块的个数 答案单 ...
- Codeforces603E - Pastoral Oddities
Portal Description 初始时有\(n(n\leq10^5)\)个孤立的点,依次向图中加入\(m(m\leq3\times10^5)\)条带权无向边.使得图中每个点的度数均为奇数的边集是 ...
- CF603E Pastoral Oddities 优先队列+结论+LCT维护生成树
首先,一个神奇的结论:一个合法的方案存在的条件是每一个联通块的节点数都是偶数个的. 这个可以用数学归纳法简单证一证. 证出这个后,我们只需动态加入每一个边,并查看一下有哪些边能够被删除(删掉后联通块依 ...
- cf Round 603
A.Alternative Thinking(思维) 给出一个01串,你可以取反其中一个连续子串,问取反后的01子串的最长非连续010101串的长度是多少. 我们随便翻一个连续子串,显然翻完之后,对于 ...
- NOIP2017提高组 模拟赛13(总结)
NOIP2017提高组 模拟赛13(总结) 第一题 函数 [题目描述] [输入格式] 三个整数. 1≤t<10^9+7,2≤l≤r≤5*10^6 [输出格式] 一个整数. [输出样例] 2 2 ...
- Codeforces Round #439 (Div. 2) Problem C (Codeforces 869C) - 组合数学
— This is not playing but duty as allies of justice, Nii-chan! — Not allies but justice itself, Onii ...
- Codeforces Round #439 (Div. 2) Problem B (Codeforces 869B)
Even if the world is full of counterfeits, I still regard it as wonderful. Pile up herbs and incense ...
- python爬虫学习(5) —— 扒一下codeforces题面
上一次我们拿学校的URP做了个小小的demo.... 其实我们还可以把每个学生的证件照爬下来做成一个证件照校花校草评比 另外也可以写一个物理实验自动选课... 但是出于多种原因,,还是绕开这些敏感话题 ...
随机推荐
- JavaScript(一):JavaScript简介
一.什么是JavaScript JavaScript是一种具有面向对象能力的.解释性的程序设计语言.更具体一点,它是基于对象和事件驱动并具有相对安全性的客户端脚本语言.因为他不需要在一个语言环境下运行 ...
- CSRF简单介绍及利用方法
x00 简要介绍 CSRF(Cross-site request forgery)跨站请求伪造,由于目标站无token/referer限制,导致攻击者可以用户的身份完成操作达到各种目的.根据HTTP请 ...
- 自然语言交流系统 phxnet团队 创新实训 项目博客 (二)
基本要求 打开软件,即可进入2D文本交流界面, 软件此时已经连接到服务器,点击文本输入框输入你想说的话,点击发送按钮即可进行交流,点击CHAT和STUDY分别切换到聊天模式或是学习模式,聊天模式是机器 ...
- Python之批量改变图片大小
image_pylib模块:https://github.com/huangshiyu13/image_pylib data_engine模块:https://github.com/huangshiy ...
- imx6 电容屏参数更改
imx6使用电容屏时需要获取对应的usb的event.其中用到了shell的一些命令.分析如下. # inputCheckLine=`cat /proc/bus/input/devices | gre ...
- Java JNI初探
---说明,之前直接百度出来的例子,照猫画虎.没想到的是这例子居然直接来自百度百科,写着写着就囧了.. ---anyway,写完了就当是给自己看吧. 同事求助,就看了一下,照猫画虎一番,略有所得. J ...
- 第三百一十一节,Django框架,Form表单验证
第三百一十一节,Django框架,Form表单验证 表单提交 html <!DOCTYPE html> <html lang="en"> <head& ...
- Floyd算法实例
~ 当k=0时,我们关注的是邻接矩阵的第0行和第0列,即顶点0的入边和出边: 考察矩阵中其他元素,如果元素D[i][j]向第0行和第0列的投影D[0][j]和D[i][0]都有值,就说明原图中从 i ...
- 转载:15个最受欢迎的Python开源框架
出自:http://python.jobbole.com/72306/?replytocom=57112 15个最受欢迎的Python开源框架 Django: Python Web应用开发框架 Dja ...
- EF修改对象里面的值。。。(对象字段多的时候)
后台代码 public ActionResult Edit(my m)//my实体类 { testEntities t = new testEntities();//数据库上下文 t.my.Attac ...