DescriptionDescription
给定一个 nn 个点,mm 条边的连通无向图,图有边权,每个点有一个颜色。有 qq 次操作,每次操作可改变某一点颜色。
每次操作后求图中不同颜色点的最短距离。
保证图中点颜色至少存在两种,图颜色的种数为 cc.
c,q,n≤2⋅105,m≤3⋅105c,q,n≤2⋅105,m≤3⋅105.
SolutionSolution
谁能知道我连根号分治都妹想到呢,还以为不能做……而且这题的数据竟然还能跑过,就离谱。
首先这题有两个结论:
- 最短距离一定是一条边;
- 这条边一定在原图的最小生成树上。考虑证明,一个感性的猜测是会不会发生连通块内的树边都不可用,而非树边可以使用的情况。事实上,"相等" 是有传递性的,也就是说我们模拟 kruskalkruskal 求最小生成树的过程,如果树边都不可用相当于这个块的颜色全部相同,此时非树边显然也不可以使用。
后来想了想,当时根号分治没有想出来的原因是对于可修改的取 minmin 查询的套路没有摸清:为啥不用 multisetmultiset 呢?才一个 loglog 呢!由于上文已经将边数缩减到 nn 级别,所以根号分治变成了根号下 nn. 对于度数 <√n<√n 的点,就直接枚举,更新答案 multisetmultiset(全局)即可;对于度数 ≥√n≥√n 的点,开一棵线段树(值域为颜色)维护区间最小边权(叶子节点还是一个 multisetmultiset),其实就是无法枚举邻接点,所以用 <√n<√n 的点来更新。所以总共是 O(n√nlogn)O(n√nlogn) 的。
接下来可能是一个 tricktrick 吧:考虑我们已经将可选边的集合缩成了树的形式,就可以利用这个结构来思考。对于点 uu 开 cc 个 multisetmultiset(这里只是为了叙述方便,实际上只用开至多 ∑v∈son(u)1∑v∈son(u)1 个), 将每个 multisetmultiset 中的最小边权扔进一个答案 multisetmultiset(全局),然后瞎 jbjb 判断一下就行了。所以总共是 O(nlogn)O(nlogn) 的。
CodeCode
另外还从别人那里抄来一个优先队列延迟模拟 multisetmultiset 的,具体就看代码辣~
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' || s<'0')
f |= (s=='-');
while(s>='0' && s<='9')
x = (x<<1)+(x<<3)+(s^48),
s = getchar();
return f?-x:x;
}
template <class T>
inline void write(T x) {
static int writ[50],tp=0;
if(x<0) putchar('-'),x=-x;
do writ[++tp] = x-x/10*10, x /= 10; while(x);
while(tp) putchar(writ[tp--]^48);
}
#include <queue>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int maxn = 2e5+5;
const int maxm = 3e5+5;
int n,m,q,cnt,W[maxn],col[maxn];
int head[maxn],f[maxn],fa[maxn];
struct Edges {
int u,v,w;
bool operator < (const Edges& t) const {
return w<t.w;
}
} E[maxm];
struct edge {
int nxt,to,w;
} e[maxn<<1];
struct heap {
priority_queue < int, vector <int>, greater <int> > q,del;
inline void upd() {
while(!del.empty() && q.top()==del.top())
q.pop(), del.pop();
}
inline int top() { return upd(),q.top(); }
inline int size() { return upd(),q.size()-del.size(); }
inline void push(int x) { q.push(x); }
inline void pop(int x) { del.push(x); }
} ans;
unordered_map <int,heap> t[maxn];
void addEdge(int u,int v,int w) {
e[++ cnt].to=v, e[cnt].nxt=head[u];
e[cnt].w=w, head[u]=cnt;
e[++ cnt].to=u, e[cnt].nxt=head[v];
e[cnt].w=w, head[v]=cnt;
}
int Find(int x) {
return x==f[x]?x:f[x]=Find(f[x]);
}
void Kruskal() {
for(int i=1;i<=n;++i) f[i]=i;
sort(E+1,E+m+1);
for(int i=1;i<=m;++i) {
int u=E[i].u,v=E[i].v;
if(Find(u)^Find(v)) {
f[Find(u)] = Find(v);
addEdge(u,v,E[i].w);
}
}
}
void dfs(int u,int die) {
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].to;
if(v==die) continue;
t[u][col[v]].push(e[i].w);
fa[v]=u; W[v]=e[i].w;
dfs(v,u);
}
for(auto& [c,Q]:t[u])
if(c^col[u]) ans.push(Q.top());
}
void change(int x,int y) {
if(col[x]==y) return;
if(fa[x]) {
heap &h = t[fa[x]][col[x]];
if(col[x]^col[fa[x]]) ans.pop(h.top());
h.pop(W[x]);
if(!h.size()) t[fa[x]].erase(col[x]);
else if(col[x]^col[fa[x]]) ans.push(h.top());
heap &H = t[fa[x]][y];
if(H.size() && y!=col[fa[x]]) ans.pop(H.top());
H.push(W[x]);
if(y^col[fa[x]]) ans.push(H.top());
}
if(t[x].count(col[x])) ans.push(t[x][col[x]].top());
if(t[x].count(y)) ans.pop(t[x][y].top());
col[x]=y;
}
int main() {
freopen("color.in","r",stdin);
freopen("color.out","w",stdout);
n=read(9), m=read(9);
read(9), q=read(9);
for(int i=1;i<=m;++i)
E[i].u=read(9), E[i].v=read(9),
E[i].w=read(9);
for(int i=1;i<=n;++i) col[i]=read(9);
Kruskal(); dfs(1,0);
while(q --) {
int x=read(9),y=read(9);
change(x,y);
print(ans.top(),'\n');
}
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· PPT革命!DeepSeek+Kimi=N小时工作5分钟完成?
· What?废柴, 还在本地部署DeepSeek吗?Are you kidding?
· DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
· 程序员转型AI:行业分析