https://atcoder.jp/contests/nikkei2019-qual/tasks/nikkei2019_qual_e
题目给出n个点m条边,问你至少删掉多少条边,能够使得最后的所有连通块中,一个连通块中的点权之和要大于这个连通块中每一条边的值。
考场上已经接近正解了,按边权排序,从小到大考虑是否加进答案里去,并查集维护一下点权和,求出最多能加进答案的边,输出m-ans,但是不知道这个怎么判断。
题解的思路对这个判断很巧妙,对于一个合法连通块中的边,因为从小到大枚举的边,并查集中维护一个当前连通块中还有多少边没有加进答案,如果此时点权和已经比当前边大了,那么说明该连通块剩下的边全部合法,于是把当前连通块中没加入答案的边全部加入答案。
在submissions中还发现另外一种写法,用非路径压缩的最短路写,每次把小的连通块的根连上大的连通块的根,这样分叉就很多,也许能保证找根的深度不超过logn?(没想的很清楚)
从头开始跑一遍最小生成树,记录并查集上连接的顺序情况,而且不清空之前的值,再从后往前删掉那些非法的边,按照记录还原点权和,连通块大小等信息,最后剩下的边就是最后连通性地状况,再扫一遍所有边,在不改变连通性地情况下,看哪些边在答案中。
前一种正解52ms,后一种56ms,非压缩路径的并查集也这么快的吗?
#include<bits/stdc++.h>
#define maxl 100010
using namespace std;
int n,m,ans;
int a[maxl];
struct ed
{
int u,v,val;
}e[maxl];
int f[maxl],cnt[maxl];
long long sum[maxl];
inline bool cmp(const ed &x,const ed &y)
{
return x.val<y.val;
}
inline void prework()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
f[i]=i;cnt[i]=0;sum[i]=a[i];
}
for(int i=1;i<=m;i++)
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].val);
sort(e+1,e+1+m,cmp);
}
inline int find(int x)
{
if(f[x]!=x)
{
f[x]=find(f[x]);
cnt[f[x]]+=cnt[x];
sum[f[x]]+=cnt[x];
sum[x]=cnt[x]=0;
}
return f[x];
}
inline void mainwork()
{
int x,y,xx,yy;
for(int i=1;i<=m;i++)
{
x=e[i].u;y=e[i].v;
xx=find(x);yy=find(y);
if(xx!=yy)
{
cnt[xx]+=cnt[yy]+1;
sum[xx]+=sum[yy];
f[yy]=xx;
sum[yy]=0;cnt[yy]=0;
if(sum[xx]>=e[i].val)
{
ans+=cnt[xx];
cnt[xx]=0;
}
}
else
{
cnt[xx]++;
if(sum[xx]>=e[i].val)
{
ans+=cnt[xx];
cnt[xx]=0;
}
}
}
}
inline void print()
{
printf("%d",m-ans);
}
int main()
{
prework();
mainwork();
print();
return 0;
}
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e5;
struct edge{int fr,to,w;}a[N],sta[N];
int n,m,val[N],fa[N];
int siz[N],cnt,top,ans;
ll sum[N];
int cmp(const edge&A,const edge&B) {return A.w<B.w;}
int find(int x) {return fa[x]==x?x:find(fa[x]);}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d",&val[i]);
fa[i]=i,siz[i]=1,sum[i]=val[i];
}
for(int i=1,x,y,w;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&w);
a[++cnt]=(edge){x,y,w};
}
sort(a+1,a+cnt+1,cmp);
for(int i=1;i<=cnt;i++)
{
int x=a[i].fr,y=a[i].to;
x=find(x);y=find(y);
if(x==y) continue;
if(siz[x]>siz[y]) swap(x,y);
sta[++top]=(edge){x,y,a[i].w};
fa[x]=y;siz[y]+=siz[x];sum[y]+=sum[x];
}
for(int i=top;i>=1;i--)
{
int x=sta[i].fr,y=sta[i].to;
ll v=sum[find(y)],val=sta[i].w;
if(v<val) fa[x]=x,siz[y]-=siz[x],sum[y]-=sum[x];
}
for(int i=1;i<=m;i++)
{
int x=a[i].fr,y=a[i].to;
if(find(x)!=find(y)) ans++;
else if(a[i].w>sum[find(x)]) ans++;
}
cout<<ans<<endl;
}

本文深入探讨AtCoder平台上nikkei2019-qual竞赛的一道难题,讲解如何通过并查集和贪心策略来优化算法,实现高效求解最小生成树问题,确保连通块的点权和大于边权。
102

被折叠的 条评论
为什么被折叠?



