在我 AC 这道题后,我对我的做法存疑。于是在机房小伙伴和我的讨论下,成功 Hack 了我的做法。故发帖,一是提醒谷友们写这道题时不要写这种错误做法,二是询问 CF 如何 Hack。
我的做法:
有解的充要条件:点权之和>=(点的个数-1)*x。
所以只要能合并,怎么合并都是对的。
暴力枚举每个点,只要这个点之前没被合并,就遍历它的链表去合并其他点,合并过程中还要把两个链表合并,直到无法继续合并,之后一定会有其他点合并这个无法继续合并的点。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=3e5+10,M=N*2;
int n,m;
LL x,a[N];
int h[N],t[N],id[M],e[M],ne[M],idx;//t[u]:链表u的末尾;id[idx]:边idx的编号
int p[N],cnt[N]; //p:并查集;cnt[p]:并查集p的总点数
LL tot[N]; //tot[p]:并查集p的点权之和
void add(int u,int v,int i)
{
e[++idx]=v;
if(!h[u]) t[u]=idx;
id[idx]=i;
ne[idx]=h[u];
h[u]=idx;
return ;
}
int find(int x)
{
return x==p[x] ? p[x] : p[x]=find(p[x]);
}
void merge(int u)
{
while(find(e[h[u]])==u) h[u]=ne[h[u]]; //把并查集自己到自己的边删除
for(int i=h[u];i!=0;i=ne[i])
{
int v=find(e[i]);
if(tot[u]+tot[v]>=(cnt[u]+cnt[v]-1)*x) //满足充要条件,可以合并
{
printf("%d\n",id[i]);
p[v]=u,tot[u]+=tot[v],cnt[u]+=cnt[v];
//把整个链表v插到i的后面
ne[t[v]]=ne[i];
ne[i]=h[v];
}
while(find(e[ne[i]])==u) ne[i]=ne[ne[i]]; //把并查集自己到自己的边删除
}
return ;
}
int main()
{
scanf("%d%d%lld",&n,&m,&x);
LL suma=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
suma+=a[i];
p[i]=i,tot[i]=a[i],cnt[i]=1;
}
if(suma<(n-1)*x)
{
puts("NO");
return 0;
}
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v,i),add(v,u,i);
}
puts("YES");
for(int i=1;i<=n;i++) if(p[i]==i/*之前没被合并*/) merge(i);
return 0;
}
Hack 数据:
input:
3 2 10
1 1 100
2 3
1 2
answer:
YES
1
2
wrong output:
YES
1
原因:遍历点1的链表什么都合并不了。遍历点2的链表先遍历点1,无法合并;再遍历点3,可以合并,把点3的链表合并,但是之后依然无法遍历到点1。点3被合并过了,不会遍历点3的链表。