给定一张 n n n 和节点, m m m 条边的无向图,每个点有一个初始颜色,接下来有 Q Q Q 个操作,每次操作会更改一个点的颜色,并询问距离最近的不同色点对的最小距离,颜色上限不超过 K K K 且图中至少有两种颜色。
1 ≤ n , Q ≤ 2 × 1 0 5 1 \leq n,Q \leq 2 \times 10^5 1≤n,Q≤2×105
1 ≤ m ≤ 4 × 1 0 5 1 \leq m \leq 4 \times 10^5 1≤m≤4×105
1 ≤ K ≤ 1 0 6 1 \leq K \leq 10^6 1≤K≤106
首先答案肯定是连接两个不同色点对的最短边,而这个最短边肯定在最小生成树上。前者显然成立,后者可以在 Kruskal \text{Kruskal} Kruskal 的过程中看出来。
那么首先把图缩成一棵最小生成树并把它拎成一棵有根树。对于每一个节点,我们维护它不同颜色子节点的信息,由此达到不重复。
每次更新,对于一个节点 u u u,它的颜色可能随时会变,设之为 c u c_u cu,每次的查询就是连接颜色在区间 [ 1 , c u − 1 ] ⋃ [ c u + 1 , K ] [1,c_u-1]\bigcup[c_u+1,K] [1,cu−1]⋃[cu+1,K] 中儿子的边的最小权值,我们不妨把边权拉到子节点上方便处理。而同种颜色的子节点权值不一定相同。颜色权值两维,不妨两个数据结构套用:颜色要查询区间,用线段树维护,直接维护又会炸内存,改用动态开点;而权值要维护住很多值,还要支持删除、插入、查询最小值, multiset \text{multiset} multiset 可以实现,但是可删堆常数更小。
为了快速回答询问,我们实时维护每个节点和它子节点之间的答案,用 multiset/ \text{multiset/} multiset/可删堆 维护这个数组每次查询最小值即可。
用数据结构解题首先要形成清晰的算法流程,再套用合适的数据结构,当然模板要打熟,功底要深厚。
#include
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
typedef long long LL;
using namespace std;
const int N=2e5+5;
const int M=4e5+5;
template<const int maxn,const int maxm>struct Linked_list
{
int head[maxn],to[maxm],nxt[maxm],cost[maxm],tot;
Linked_list(){clear();}
void clear(){memset(head,-1,sizeof(head));tot=0;}
void add(int u,int v,int w){to[++tot]=v,cost[tot]=w,nxt[tot]=head[u],head[u]=tot;}
#define EOR(i,G,u) for(int i=G.head[u];~i;i=G.nxt[i])
};
struct DisjointSet
{
int fa[N],n;
void init(int _n){n=_n;FOR(i,1,n)fa[i]=i;}
int getfa(int k){return k==fa[k]?k:fa[k]=getfa(fa[k]);}
bool merge(int x,int y)
{
x=getfa(x),y=getfa(y);
if(x==y)return false;
fa[x]=y;
return true;
}
}DSU;
struct ErasableHeap
{
priority_queue<int,vector<int>,greater<int> >Q,E;
void push(int x){Q.push(x);}
void erase(int x){E.push(x);}
void modify(){while(!Q.empty()&&!E.empty()&&Q.top()==E.top())Q.pop(),E.pop();}
void pop(){modify();if(!Q.empty())Q.pop();}
int top(){modify();if(Q.empty())return 2e9;return Q.top();}
};
struct edge
{
int u,v,w;
bool operator <(const edge &_)const{return w<_.w;}
}E[M];
Linked_list<N,N<<1>G;
ErasableHeap ans;
struct node{int lson,rson,miner,id;};
struct SegmentTree
{
ErasableHeap st[N<<2];
node nd[N*40];
int rt[N<<1],tot,ID;
void build(){memset(rt,0,sizeof(rt));tot=ID=0;nd[0].miner=2e9;}
void create(int &k){if(k==0)nd[k=++tot]=(node){0,0,2e9,0};}
void add_up(int k){nd[k].miner=min(nd[nd[k].lson].miner,nd[nd[k].rson].miner);}
void update(int &k,int x,int A,bool B,int l,int r)
{
create(k);
if(l==r)
{
if(nd[k].id==0)nd[k].id=++ID;
if(B)st[nd[k].id].push(A);
else st[nd[k].id].erase(A);
nd[k].miner=st[nd[k].id].top();
return;
}
int mid=l+r>>1;
if(x<=mid)update(nd[k].lson,x,A,B,l,mid);
else update(nd[k].rson,x,A,B,mid+1,r);
add_up(k);
}
int query(int &k,int L,int R,int l,int r)
{
if(!k)return 2e9;
if(L<=l&&r<=R)return nd[k].miner;
int mid=l+r>>1;
if(R<=mid)return query(nd[k].lson,L,R,l,mid);
else if(L>mid)return query(nd[k].rson,L,R,mid+1,r);
else return min(query(nd[k].lson,L,R,l,mid),query(nd[k].rson,L,R,mid+1,r));
}
}ST;
int fa[N],p[N],minson[N],c[N],n,m,K,Q;
void Kruskal()
{
DSU.init(n);
sort(E+1,E+1+m);
FOR(i,1,m)if(DSU.merge(E[i].u,E[i].v))
{
G.add(E[i].u,E[i].v,E[i].w);
G.add(E[i].v,E[i].u,E[i].w);
}
}
int getMin(int u)
{
if(c[u]==1)return ST.query(ST.rt[u],c[u]+1,K,1,K);
else if(c[u]==K)return ST.query(ST.rt[u],1,c[u]-1,1,K);
else return min(ST.query(ST.rt[u],1,c[u]-1,1,K),ST.query(ST.rt[u],c[u]+1,K,1,K));
}
void dfs(int u,int f)
{
fa[u]=f;
EOR(i,G,u)
{
int v=G.to[i],w=G.cost[i];
if(v==f)continue;
p[v]=w;
ST.update(ST.rt[u],c[v],p[v],1,1,K);
dfs(v,u);
}
minson[u]=getMin(u);
ans.push(minson[u]);
}
int main()
{
ST.build();
scanf("%d%d%d%d",&n,&m,&K,&Q);
FOR(i,1,m)scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
FOR(i,1,n)scanf("%d",&c[i]);
Kruskal();
dfs(1,0);
while(Q--)
{
int x,y;
scanf("%d%d",&x,&y);
if(fa[x])
{
ST.update(ST.rt[fa[x]],c[x],p[x],0,1,K);
c[x]=y;
ST.update(ST.rt[fa[x]],c[x],p[x],1,1,K);
ans.erase(minson[fa[x]]);
minson[fa[x]]=getMin(fa[x]);
ans.push(minson[fa[x]]);
}
else c[x]=y;
ans.erase(minson[x]);
minson[x]=getMin(x);
ans.push(minson[x]);
printf("%d\n",ans.top());
}
return 0;
}