神仙题

题目大意:

有一张\(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. 【洛谷5335】[THUSC2016] 补退选(指针实现Trie)

    点此看题面 大致题意: 三种操作:加入一个字符串,删除一个字符串,求最早什么时候以某个字符串为前缀的字符串个数超过给定值. \(Trie\) 这道题显然是\(Trie\)的暴力裸题. 考虑我们对于\( ...

  2. 前端笔记之Vue(六)分页排序|酷表单实战&Vue-cli

    一.分页排序案例 后端负责提供接口(3000) 前端负责业务逻辑(8080) 接口地址:从8080跨域到3000拿数据 http://127.0.0.1:3000/shouji http://127. ...

  3. Linux网络基础协议和ip管理

    1.简述osi七层模型和TCP/IP五层模型 osi七层模型分别是:物理层.数据链路层.网络层.传输层.会话层.表示层.应用层. 1)物理层:这一层的主要功能是二进制传输数据,界定连接器和网线的规格: ...

  4. python-3.8.0 新特性之赋值表达式

    [python-3.8.0 新特性之赋值表达式] 赋值表达式的语法是这样的“ name := expression ”,形式上看和赋值语句 “ = ” 差不多,就作用上来看也雷同.也就是说 “:=” ...

  5. swoole中使用task进程异步的处理耗时任务

    我们知道,swoole中有两大进程,分别是 master 主进程和 manager 管理进程. 其中 master 主进程中会有一个主 reactor 线程和多个 reactor 线程,主要的作用就是 ...

  6. C#函数的参数传递方式1(值传递与地址传递)

    using System; namespace class1 { class program { static void Main(string[] args) { //值传递引用,实际参数不会变化 ...

  7. PlayJava Day010

    今日所学: /* 2019.08.19开始学习,此为补档. */ 1.继承补充: ①不要仅为了获取其他类中某个功能而去继承,而是要有所属关系. ②Super关键字: a.代表父类对象的引用,且main ...

  8. java核心技术第六篇之断言、日志、包装类型和工具类

    JDK1.5新特性: 1.自动拆装箱. 2.泛型 3.可变参数 4.静态导入 5.增强for循环 6.互斥锁 7.枚举 8.注解 JDK1.6新特性: 1.Desktop类和SystemTray类 2 ...

  9. CSS语法规范一

    CSS语法规范 CSS规则由两个主要的部分构成:选择器以及一条或多条声明. p{ color: red; font-size: 12px; } CSS代码风格 样式格式书写 紧凑格式 h3 {colo ...

  10. Element-ui中ElScrollBar组件滚动条的使用

    在翻看 element-ui官网的文档时,发现其左侧导航和右边的内容超出屏幕时,滚动条的样式比较小巧,通过浏览器审查工具查看,发现它是使用了el-scrollbar的样式,跟element-ui的组件 ...