神仙题

题目大意:

有一张\(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. Linux 学习记录三(Vim 文书编辑器).

       所有的Unix Like系统都会内建vi文书编辑器,其他的文书编辑器不一定存在,vim是vi的升级版,具有程序编辑的能力,可以主动的以字体颜色辨别语法的正确性,方便程序设计.vim 里面加入了很 ...

  2. 用Python做个海量小姐姐素描图

    素描作为一种近乎完美的表现手法有其独特的魅力,随着数字技术的发展,素描早已不再是专业绘画师的专利,今天这篇文章就来讲一讲如何使用python批量获取小姐姐素描画像.文章共分两部分: 第一部分介绍两种使 ...

  3. DFS(四):剪枝策略

    顾名思义,剪枝就是通过一些判断,剪掉搜索树上不必要的子树.在采用DFS算法搜索时,有时候我们会发现某个结点对应的子树的状态都不是我们要的结果,这时候我们没必要对这个分支进行搜索,砍掉这个子树,就是剪枝 ...

  4. Redis缓存雪崩,缓存穿透,热点key解决方案和分析

    缓存穿透 缓存系统,按照KEY去查询VALUE,当KEY对应的VALUE一定不存在的时候并对KEY并发请求量很大的时候,就会对后端造成很大的压力. (查询一个必然不存在的数据.比如文章表,查询一个不存 ...

  5. 【shell脚本】nginx启动脚本

    [root@localhost init.d]# cat nginx #!/bin/bash #nx Startup script for the Nginx HTTP Server # it ver ...

  6. 愉快地使用Groovy Shell

    这是一篇有关Groovy Shell的帖子,以及它如何在日常工作中为您提供帮助(只要您是软件开发人员).无论您使用哪种编程语言或技术,都可以从Groovy Shell中受益.唯一真正的要求是您能够编写 ...

  7. [IDA] 将连续的单个变量值修改为结构体

    直接选中最上边的结构体,使用ALT+Q来进行修改.

  8. Java开发桌面程序学习(四)——常用应用布局模板和简单分析

    布局 前言 刚开始的时候,不知道使用什么布局,发现SceneBuilder其实有8.5版本的,里面就是有提供一个简单的桌面程序模板,8.5可以去官网下载,不过网速好像有点慢,慢慢等吧,官网下载地址 布 ...

  9. C#简单的枚举及结构

    using System; namespace program { enum WeekDays { a, b, c = ,//11 赋值以后就变成11,不赋值就是2 d, e, f, g }//不能输 ...

  10. 解决真机编译出现System.DllNotFoundException: 'libmono-native.so'错误都方法

    1.去掉勾选:使用共享运行时 2 检查android SDK是否安装了NDK 3.使用真机运行编译APK