NHOI 2023 Feb T6 拯救地球
题意
地球上有 \(n\) 个国家,有 \(m\) 条无向道路,每条道路上只有一个外星生物(地球上的外星生物全部都在道路上,国家里面没有外星生物),每个外星生物都有一个防御值,接着你有 \(q\) 次任务,每次任务你拥有 \(w\) 的攻击力(每次任务的攻击力是一直不变的),然后用传送门将你送去国家 \(x\)(国家 \(x\) 已经被你拯救),你的任务就是拯救尽可能多的国家。(如果国家 \(a\) 与国家 \(b\) 之间有一条道路,国家 \(a\) 已经被你拯救,并且你的攻击值大于等于这条道路上外星生物的防御值,那么国家 \(b\) 能被你拯救,否则你无法通过这条道路拯救国家 \(b\))
对于每次任务,输出你最多能拯救的国家数目,每次任务独立,互不影响。
Step1:
直接暴力建图,暴力计算答案。
于是时间复杂度就为 \(O(qm)\) 。
因为很显然并不是正解,所以就没打了。
Step2:
不难发现,如果从 \(a\) 开始可以拯救到 \(b\),那么再相同的情况下从 \(b\) 也能到 \(a\)。既然如此,所有点都能到达在同一连通块的任一点了,而答案正是这个连通块的点的数量。两个点连一条边,就是将两点所对应的这两个连通块合并。
既然如此,我们可以用并查集来维护这个合并了。
显然如果一条边的边权 \(ew\),给定的战斗力为 \(qw\),那么必须要满足 \(ew\le qw\) 才能连这条边。注意到询问中的 \(w\) 并不是升序的,可以使用离线来让他变成升序。并且所有边都是按照 \(w\) 来排序的,这样就可以不需要有删边的操作了。
于是,这个代码很轻松的就出来了。
注意一点,这里可以直接存 \(u,v,w\) ,而不是真正的建图。因为在这里,建一条边实质只是把两个连通块合并而已。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,M=4e5+5,Q=2e5+5;
int n,m,q,p;
struct edge{int u,v,w;}e[M];
bool cmp(edge x,edge y){return x.w<y.w;}
struct data{int b,id,w,ans;}que[Q];
bool cmpw(data x,data y){return x.w<y.w;}
bool cmpid(data x,data y){return x.id<y.id;}
int fa[N],num[N];
int find(int x){return (x==fa[x]?x:fa[x]=find(fa[x]));}
void merge(int u,int v)
{
u=find(u);v=find(v);
if(u==v)return;
fa[v]=u;
num[u]+=num[v];
}
void solve(int lim)
{
while(1)
{
p++;
if(p>m)break;
if(e[p].w<=lim)merge(e[p].u,e[p].v);
else break;
}
p--;
}
int main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)fa[i]=i,num[i]=1;
for(int i=1;i<=m;i++)scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
sort(e+1,e+1+m,cmp);
for(int i=1;i<=q;i++)scanf("%d%d",&que[i].b,&que[i].w),que[i].id=i;
sort(que+1,que+1+q,cmpw);
for(int i=1;i<=q;i++)
{
solve(que[i].w);
que[i].ans=num[find(que[i].b)];
}
sort(que+1,que+1+q,cmpid);
for(int i=1;i<=q;i++)printf("%d\n",que[i].ans);
return 0;
}

浙公网安备 33010602011771号