神仙题

题目大意:

有一张\(n\)个点\(m\)条边的无向联通图,每次修改一条边的边权,问每次修改之后这张图的最小生成树权值和

话说是不是\(cdq\)题目都可以用什么数据结构莽过去啊……

这道题目确实是\(cdq\)好题

\(cdq\)分治一定要处理多维偏序问题吗?并不是,它的核心思想是一个用子问题计算对另一个子问题的贡献

我们将这道题按照时间轴来分治,那么显然一个子问题对另一个子问题是存在贡献的

我们将整张图上的边进行分类:

在当前分治区间内涉及到修改的边称为动态边

其他边称为静态边

我们在分治过程中,动态边显然会随着区间的缩小而减少,那么我们怎么来处理静态边呢?

仔细考虑,我们根本不可以发现,静态边可以分为三类:

\(1.\)无用边:肯定不会作为最小生成树中的边

对于这类边,我们可以直接扔掉

\(2.\)必要边:必作为生成树中的边

对于这种边,我们可以直接把边权计入答案,然后把边连接的两个点用并查集合并到一起

\(3.\)其他边

没什么办法了,直接传给子区间吧

按照这种方法,每分治到一个更小的区间,其他区间的动态边就会变成静态边,当我们分治到\(l==r\)时,所有的边都会变为静态边,那么其他边将不存在,必要边贡献的答案就是答案了

现在我们有两个问题:

1:如何求出前两类边?

求无用边,我们可以将所有动态边的边权设置为\(inf\),然后跑\(Kruskal\),最后没有被用到的静态边都是无用边

求必要边,我们可以将所有动态边的边权设置为\(-inf\),还跑\(Kruskal\),最后被用到的静态边都是必要边

正确性好像挺显然的(雾

2:时间复杂度?

假设当前区间内有\(k\)条动态边,那么除去必要边和无用边之后,最多留下\(k+1\)个点和\(2k\)条边

由于\(k\)每次降低一倍,所以边数和点数都是都每次减少接近一倍,复杂度约为\(O(nlog^2n)\)

注意我们每次操作要先求必要边再求无用边复杂度才对!!!

我们注意这道题,它的其他子问题对当前子问题的贡献并不是按照一般\(cdq\)的方法,而是在进入当前区间之前已经将贡献计算好了

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define mid ((l+r)>>1)
#define lowbit(x) ((x)&(-x))
inline int read()
{
int x=0;char ch,f=1;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') f=0,ch=getchar();
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return f?x:-x;
}
const int N=1e5+10;
int n,m,ask;
struct edge
{
int x,y,val,opt;
inline bool operator < (const edge &t) const
{
return val<t.val;
}
}a[N];
struct moved
{
int x,y;
};
struct node
{
int num,val,ret;
}q[N];
struct dsu
{
int f[N],str[N];
struct blue {int x,y;};
stack<blue> st;
inline void init()
{
for(int i=1;i<=n;++i) f[i]=i,str[i]=1;
}
inline int find(int k)
{
return f[k]==k?k:find(f[k]);
}
inline void merge(int x,int y)
{
int tx=find(x),ty=find(y);
if(tx==ty) return;
if(str[tx]<str[ty]) swap(tx,ty);
str[tx]+=str[ty];
f[ty]=tx;
blue tmp;
tmp.x=tx,tmp.y=ty;
st.push(tmp);
}
inline void del()
{
blue now=st.top();
st.pop();
f[now.y]=now.y;
str[now.x]-=str[now.y];
}
inline void clear(int bot)//可撤销并查集
{
while(st.size()>bot) del();
}
}s1,s;//s全局
int ret[30];
//reduction无用边
//contraction必须边
vector<edge> eg[30],tr;//存每一层静态边和临时静态边
vector<moved> rose;//存当前动态边
int bot[30];
bool book[N];
inline void push_down(int dep)
{
tr.clear();
for(int i=0;i<eg[dep].size();++i) tr.push_back(eg[dep][i]);
sort(tr.begin(),tr.end());
s1.clear(0);
ret[dep+1]=ret[dep];
for(int i=0;i<rose.size();++i) s1.merge(rose[i].x,rose[i].y);
rose.clear();
for(int tx,ty,i=0;i<tr.size();++i)//求必要边
{
tx=s1.find(tr[i].x),ty=s1.find(tr[i].y);
if(tx==ty) continue;
tr[i].opt=1;
s1.merge(tr[i].x,tr[i].y);
s.merge(tr[i].x,tr[i].y);
ret[dep+1]+=tr[i].val;
}
s1.clear(0);//撤销
for(int i=0;i<tr.size();++i)//求无用边
{
if(tr[i].opt) continue;
if(s.find(tr[i].x)==s.find(tr[i].y)||s1.find(tr[i].x)==s1.find(tr[i].y))
{
tr[i].opt=-1;
continue;
}
s1.merge(tr[i].x,tr[i].y);
}
s1.clear(0);//撤销
eg[dep+1].clear();
for(int tx,ty,i=0;i<tr.size();++i)
{
if(tr[i].opt) continue;
tx=s.find(tr[i].x),ty=s.find(tr[i].y);
if(tx==ty) continue;
edge tmp;
tmp.x=tx,tmp.y=ty,tmp.val=tr[i].val,tmp.opt=0;
eg[dep+1].push_back(tmp);
}
return;
}
inline void cdq(int l,int r,int dep)
{
bot[dep]=s.st.size();
if(l==r)
{
edge tmp;
tmp.x=s.find(a[q[r].num].x),tmp.y=s.find(a[q[r].num].y);//底层
tmp.val=q[r].val,tmp.opt=0;
a[q[r].num].val=q[r].val;//注意这里,直接更改
eg[dep].push_back(tmp);
push_down(dep);
q[r].ret=ret[dep+1];
s.clear(bot[dep-1]);
return;
}
for(int i=l;i<=mid;++i) book[q[i].num]=1;//递归左边时,右边的动态变静态
for(int i=mid+1;i<=r;++i)
{
if(book[q[i].num]) continue;
edge tmp;
tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y);
tmp.val=a[q[i].num].val,tmp.opt=0;
eg[dep].push_back(tmp);
}
for(int i=l;i<=mid;++i)//左边的动态存进去
{
moved tmp;
tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y);
rose.push_back(tmp);
} push_down(dep);//下放 for(int i=mid+1;i<=r;++i)
{
if(book[q[i].num]) continue;
eg[dep].pop_back();
}
for(int i=l;i<=mid;++i) book[q[i].num]=0;//回溯 cdq(l,mid,dep+1); for(int i=0;i<eg[dep].size();++i) eg[dep][i].opt=0;//去掉标记 for(int i=mid+1;i<=r;++i) book[q[i].num]=1; for(int i=l;i<=mid;++i)//左边的动态变静态
{
if(book[q[i].num]) continue;
edge tmp;
tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y);
tmp.val=a[q[i].num].val,tmp.opt=0;
eg[dep].push_back(tmp);
}
for(int i=mid+1;i<=r;++i)//右边的动态放进去
{
book[q[i].num]=0;
moved tmp;
tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y);
rose.push_back(tmp);
}
push_down(dep);
cdq(mid+1,r,dep+1);
s.clear(bot[dep-1]);//撤销并查集
}
inline void main()
{
n=read(),m=read(),ask=read();
s1.init(),s.init();
for(int i=1;i<=m;++i) a[i].x=read(),a[i].y=read(),a[i].val=read();
for(int i=1;i<=ask;++i)
{
q[i].num=read(),q[i].val=read();
book[q[i].num]=1;
}
for(int i=1;i<=m;++i)
{
if(book[i]) continue;
eg[1].push_back(a[i]);
}
for(int i=1;i<=ask;++i) book[q[i].num]=0;
cdq(1,ask,1);
for(int i=1;i<=ask;++i) printf("%lld\n",q[i].ret);
}
}
signed main()
{
red::main();
return 0;
}

洛谷P3206 [HNOI2010]城市建设的更多相关文章

  1. P3206 [HNOI2010]城市建设 [线段树分治+LCT维护动态MST]

    Problem 这题呢 就边权会在某一时刻变掉-众所周知LCT不支持删边的qwq- 所以考虑线段树分治- 直接码一发 如果 R+1 这个时间修改 那就当做 [L,R] 插入了一条边- 然后删的边和加的 ...

  2. 【BZOJ2001】[HNOI2010]城市建设(CDQ分治,线段树分治)

    [BZOJ2001][HNOI2010]城市建设(CDQ分治,线段树分治) 题面 BZOJ 洛谷 题解 好神仙啊这题.原来想做一直不会做(然而YCB神仙早就切了),今天来怒写一发. 很明显这个玩意换种 ...

  3. 【LG3206】[HNOI2010]城市建设

    [LG3206][HNOI2010]城市建设 题面 洛谷 题解 有一种又好想.码得又舒服的做法叫线段树分治+\(LCT\) 但是因为常数过大,无法跑过此题. 所以这里主要介绍另外一种玄学\(cdq\) ...

  4. [HNOI2010]城市建设

    [HNOI2010]城市建设 玄学cdq O(nlog^2n)的动态最小生成树 其实就是按照时间cdq分治+剪枝(剪掉一定出现和不可能出现的边) 处理[l,r]之间的修改以及修改之后的询问,不能确定是 ...

  5. [洛谷P3761] [TJOI2017]城市

    洛谷题目链接:[TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速 ...

  6. 【经典DP】洛谷 P2782 友好城市

    嘤嘤嘤,昨天两个文化课老师在上奥赛时招呼我(亲切交流),今天又要写工作报告,没时间写题解,希望今天能补上 友好城市 题目://洛谷那粘来的题面竟然能把格式粘过来 题目描述 有一条横贯东西的大河,河有笔 ...

  7. 洛谷 P2120 [ZJOI2007] 仓库建设

    链接: P2120 题意: 有 \(n\) 个点依次编号为 \(1\sim n\).给出这 \(n\) 个点的信息,包括位置 \(x_i\),所拥有的的物品数量 \(p_i\),在此建设一个仓库的费用 ...

  8. 洛谷P3203 [HNOI2010]弹飞绵羊(LCT,Splay)

    洛谷题目传送门 关于LCT的问题详见我的LCT总结 思路分析 首先分析一下题意.对于每个弹力装置,有且仅有一个位置可以弹到.把这样的一种关系可以视作边. 然后,每个装置一定会往后弹,这不就代表不存在环 ...

  9. Bzoj2002/洛谷P3203 [HNOI2010]弹飞绵羊(分块)

    题面 Bzoj 洛谷 题解 大力分块,分块大小\(\sqrt n\),对于每一个元素记一下跳多少次能跳到下一个块,以及跳到下一个块的哪个位置,修改的时候时候只需要更新元素所在的那一块即可,然后询问也是 ...

随机推荐

  1. 用OC基于数组实现循环队列

    一.简言 使用数组实现循环队列时,始终会有一个空余的位置预留着,作为一个判决队列已满的条件(当然也可以采用其他方式).在前面已经用C++介绍了基本的算法,可以去回顾一下https://www.cnbl ...

  2. 一起学react (1) 10分钟 让你dva从入门到精通

    前言 如果文章中有错误的地方的话 可以直接加我QQ:469373256 自己针对一些问题做的优化版本 目前刚启动 还不是很成熟 https://github.com/fangkyi03/fastkit ...

  3. dotnetcore实现Aop

    dotnetcore实现Aop Aop大家都不陌生,然而今天给大家不将讲官方的filter,今天给大家分享一个轻量级的Aop解决方案(AspectCore) 什么是AspectCore AspectC ...

  4. Springboot整合Thmeleaf

    1.概述 Thymeleaf类似JSP.Velocity.Freemarker都是模板引擎,主要用来展示数据,原理如下 springboot官网还是推荐使用Thymeleaf而不是jsp, 不使用js ...

  5. vue发送ajx请求 axios

    一. 简介 1.vue本身不支持发送AJAX请求,需要使用vue-resource(vue1.0版本).axios(vue2.0版本)等插件实现 2.axios是一个基于Promise的HTTP请求客 ...

  6. ASP.NET Core使用Nacos作为配置中心的多环境问题

    前言 双11那天离职后,这段时间都待在家里,看看书,写写代码,逛逛招聘网站 周一去Gworld面试的时候,有听到面试官说他们用到了配置中心Apollo,聊下来,听他的意思,大概是处理了多环境这个比较方 ...

  7. hive creating temporary folder on: Error encountered near token 'TOK_TMP_FILE'

    执行create tmp.tablename  as select .....语句的时候报以下错误: SemanticException 0:0 creating temporary folder o ...

  8. centos 配置sentry+钉钉+邮件通知

    1.sentry官方推荐docker方式安装.使用docker-compose,最好是centos7 2.卸载旧版本 yum remove docker docker-common docker-se ...

  9. XHR 对象实例所有的配置、属性、方法、回调和不可变值

    当我们声明了一个XMLHttpRequest对象的实例的时候,使用for-in来循环遍历一下这个实例(本文使用的是chrome45版本浏览器),我们会发现在这个实例上绑定了一些内容,我把这些内容进行了 ...

  10. nRF24L01+如何检测信道被占用-RSSI寄存器实战分享

    检测信道占用的需求场景 在使用nRF24L01模块做一对多或多对一的组网通信中,大家都会担心一个问题就是在发送的时候,希望不要有其他的模块也进行发送,因为这样就会使无线信号发生碰撞,信道被堵塞,造成通 ...