[USACO17OPEN Pt T2]Switch Grass 切换牧草

题目简述

给定一张\(n,m(n,m\le200,000)\)带权(权为正)无向图,每个点有一个颜色\(k(k\le n)\),每次改变一个点的颜色,要求你在操作后输出这个图中最近异色点对之间的距离。最近异色点对定义为:一对点颜色不同,且距离最小.

传送门

解题报告

一道非常清新脱俗的US open题。

Part1

首先因为边权为正,显然可以想到答案一定是某一条边的权值。

进一步可以猜想答案一定是在图的最小生成树上。

给出一个证明:

考虑Kruskal的过程,我们先把边按边权排序,我们不断把边加入最小生成树的集合。

若某条连接\(u,v\)的边不能被加入集合(称为我们放弃的边),则这条边一定和某一条连接\(u,v\)的路径形成环。且环上所有边权都比这一条边小。

\(u,v\)两端颜色相同,则这一条边一定没有贡献

否则\(u,v\)颜色不同,在原有的\(u,v\)路径中,至少有一条边连接的两端颜色也不同,且那条边边权比我们放弃的边边权更小,显然更优。

Part 2

考虑维护这颗最小生成树。

我们先把它变成一颗有根树,然后考虑每个点 它的所有儿子 与他的连边的贡献

USACO官方题解给出了一种非常优美的解法(虽然写起来又臭又长)。

我们对于每一个点(\(u​\))的所有儿子的颜色都开一个multiset,

我们称其为\(CLS_{u,c}(c为颜色)​\),存储它和所有颜色为\(c​\)的儿子的边长

显然每一条边只有可能在一个multiset里,总的节点数只有\(n-1​\),不会炸空间。(下面有具体实现)

再为每一个节点开一个multiset,称其为\(best_u​\),储存所有的\((min\{CLS_{u,c} \})_{C \in all colors }​\)

我们再建一颗线段树,称其为\(SGT​\),储存每个节点和他儿子的最近异色点对长度。

所以对于每一个节点\(u​\),他的答案不是*(best[u]).begin()​就是*((best_u).begin()++)​

因为\(best_u\)中每种颜色显然只有一个,若第一个是同色,那第二个一定是异色(或者没有第二个)。

那我们如何维护呢?

首先,当对于每一个点的颜色(从\(c​\)改为\(c'​\))进行修改时,我们要修改两个部分。

  1. 考虑它和它父亲之间的影响。

    我们先要把它从原来在的\(CLS_{fa[u],c}​\)取出来,放进\(CLS_{fa[u],c'}​\)中,这时\(min\{CLS_{fa[u],c}\}​\)

\(min\{CLS_{fa[u],c'}\}​\)都会改变,我们还要修改\(best_u​\)中的这两项。

  1. 考虑改过颜色后,它和它儿子的答案可能由*(best[u]).begin()变为*((best_u).begin()++)

*((best_u).begin()++)变为*(best[u]).begin(),我们对其进行修改。

代码如下

#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define fi first
#define se second

using namespace std;

typedef map<int,multiset<int> >::iterator MSIT;
typedef set<pair<int,int > >::iterator SPIT;


const int INF=0x3f3f3f3f, maxn=200007;
int n,m,q,k;
struct edge{
    int to,nxt,val;
}e[maxn<<1];
int head[maxn];
int tot;
void addedge(int u,int v,int val){
    e[++tot].to=v;
    e[tot].nxt=head[u];
    e[tot].val=val;
    head[u]=tot;
}//树边

namespace MST{
    struct edge1{
        int x,y,val;    
        friend bool operator <(const edge1 &a,const edge1 &b){
            return a.val<b.val; 
        }
    }G[maxn];
    int fa[maxn];
    int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
    void init(){for(int i=1;i<=n;i++)fa[i]=i;}
    void GMST(){
        init();
        sort(G+1,G+m+1);
        int cnt=0;
        for(int i=1;i<=m;i++){
            int u=G[i].x,v=G[i].y;
            int fu=find(u),fv=find(v);
            if(fu==fv)continue;
            cnt++,fa[fu]=fv;
            addedge(u,v,G[i].val);
            addedge(v,u,G[i].val);
        }
    }
}//求最小生成树
using namespace MST;

struct SGT{
    int t[maxn<<2];

    void pushup(int x){
        t[x]=min(t[x<<1],t[x<<1|1]);
    }
    void build(int x,int l,int r){
        if(l==r){
            t[x]=INF;
            return ;
        }
        int mid=(l+r)>>1;
        build(x<<1,l,mid);
        build(x<<1|1,mid+1,r);
        pushup(x);
    }
    void update(int id,int l,int r,int pos,int val ){
        if(l==r){
            t[id]=val;
            return ;
        }
        int mid=(l+r)>>1;
        if(pos<=mid)update(id<<1,l,mid,pos,val);
        else update(id<<1|1,mid+1,r,pos,val);
        pushup(id);
    }
}T;//线段树
multiset<pair<int,int > > best[maxn];
map<int,multiset<int > > cls[maxn];
int cl[maxn];
int f[maxn];
int w[maxn];

void dfs(int u,int ff){
    f[u]=ff;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==ff)continue;
        w[v]=e[i].val;
        cls[u][cl[v]].insert(w[v]);
        dfs(v,u);
    }
    for(MSIT it=cls[u].begin();it!=cls[u].end();it++){
        best[u].insert(mp(*(*it).se.begin(),(*it).fi));//初始化best数组
    }
    //初始化线段树
    if(!best[u].empty()){
        SPIT it=best[u].begin();
        if(cl[u]!=(*it).se)T.update(1,1,n,u,(*it).fi);
        else{
            it++;
            if(it==best[u].end())T.update(1,1,n,u,INF);
            else T.update(1,1,n,u,(*it).fi);
        }
    }
        
}

int main(){

    scanf("%d%d%d%d",&n,&m,&k,&q);
    for(int i=1;i<=m;i++){
        int u,v,val;
        scanf("%d%d%d",&u,&v,&val);
        G[i]=(edge1){u,v,val};
    }   
    GMST();
    for(int i=1;i<=n;i++)scanf("%d",&cl[i]);
    T.build(1,1,n);
    dfs(1,0);
    while(q--){
        int u,c2;
        scanf("%d%d",&u,&c2);//每一次更改
        if(f[u]){
            int c1=cl[u];
            best[f[u]].erase(best[f[u]].find(mp(*cls[f[u]][c1].begin(),c1)));
            cls[f[u]][c1].erase(cls[f[u]][c1].find(w[u]));
            if(cls[f[u]][c1].empty())cls[f[u]][c1].insert(INF);
            best[f[u]].insert(mp(*cls[f[u]][c1].begin(),c1));
            //我们先把u从cl[u]抹去
            if(!cls[f[u]][c2].empty())
                best[f[u]].erase(best[f[u]].find(mp(*cls[f[u]][c2].begin(),c2)));
            cls[f[u]][c2].insert(w[u]);
            best[f[u]].insert(mp(*cls[f[u]][c2].begin(),c2));
            //再加入新的集合中
            SPIT it=best[f[u]].begin();
            if(cl[f[u]]!=(*it).se)T.update(1,1,n,f[u],(*it).fi);
            else{
                it++;
                if(it==best[f[u]].end())T.update(1,1,n,f[u],INF);
                else T.update(1,1,n,f[u],(*it).fi);
            }
        }
        if(!best[u].empty()){
            SPIT it=best[u].begin();
            if(c2!=(*it).se)T.update(1,1,n,u,(*it).fi);
            else{
                it++;
                if(it==best[u].end())T.update(1,1,n,u,INF);
                else T.update(1,1,n,u,(*it).fi);
            }
        }
        cl[u]=c2;
        printf("%d\n",T.t[1]);
    }
    return 0;
}

转载于:https://www.cnblogs.com/pmt2018/p/11588465.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值