神仙题
题目大意:
有一张(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
",q[i].ret);
}
}
signed main()
{
red::main();
return 0;
}