题目简述
给定一张\(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'\))进行修改时,我们要修改两个部分。
考虑它和它父亲之间的影响。
我们先要把它从原来在的\(CLS_{fa[u],c}\)取出来,放进\(CLS_{fa[u],c'}\)中,这时\(min\{CLS_{fa[u],c}\}\)和
\(min\{CLS_{fa[u],c'}\}\)都会改变,我们还要修改\(best_u\)中的这两项。
- 考虑改过颜色后,它和它儿子的答案可能由
*(best[u]).begin()
变为*((best_u).begin()++)
或*((best_u).begin()++)
变为*(best[u]).begin()
,我们对其进行修改。
代码如下
#include
#define mp make_pair
#define pb push_back
#define fi first
#define se second
using namespace std;
typedef map >::iterator MSIT;
typedef set >::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>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 > best[maxn];
map > 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;
}