洛谷P3206 [HNOI2010]城市建设

跟風遠走 提交于 2019-12-16 11:39:28

神仙题

题目大意:

有一张\(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;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!