F. Phoenix and Earthquake(结论+模拟+启发式合并)

博客探讨了在处理图论问题时,使用优先队列+并查集算法可能遇到的问题。作者首先描述了原始思路,即选择最大值节点进行合并,但由于未正确处理连通块大小导致WA(错误答案)。接着,作者分析了错误原因,并提出了优化方案,即每次找到能代表连通块大小的正确节点进行合并,并处理好新连通块的边。最终,实现了O(nlognlogn)复杂度的解决方案。
摘要由CSDN通过智能技术生成

https://codeforces.com/contest/1515/problem/F


思路:

证明参考:https://www.luogu.com.cn/blog/_post/331276

 

我开始想的是,每次都选最大的ai 之后将他与跟他相连的且不在他集合中的点合并,尝试用优先队列 + 并查集可以轻松实现。

然后wa了。

发现有个很重要的问题没有处理。如果按照下面这样去处理。每次当前连通块合并了新的,但是原来之前合并到这个连通块里的,其siz并没有更新,显然一个连通块里的每个点的siz不同了。肯定有问题

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=3e5+1000;
typedef long long LL;
typedef pair<LL,LL>P;
inline LL read(){LL x=0,f=1;char ch=getchar();	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
struct Edge{
   LL v,id;
};
vector<Edge>g[maxn];
LL fa[maxn],siz[maxn],val[maxn];
LL find(LL x){
   if(x!=fa[x]) return fa[x]=find(fa[x]);
   return fa[x];
}
int main(void){
   cin.tie(0);std::ios::sync_with_stdio(false);
   LL n,m,x;cin>>n>>m>>x;
   LL sum=0;
   for(LL i=1;i<=n;i++) cin>>val[i],fa[i]=i,siz[i]=val[i],sum+=val[i];
   for(LL i=1;i<=m;i++){
       LL u,v;cin>>u>>v;
       g[u].push_back({v,i});
       g[v].push_back({u,i});
   }
   if(sum<(n-1)*x){cout<<"NO"<<"\n";return 0;}
   priority_queue<P>que;
   for(LL i=1;i<=n;i++){
       que.push({val[i],i});
   }
   cout<<"YES"<<"\n";
   vector<LL>ans;
   while(que.size()>=1){
       P now=que.top();que.pop();
       LL va=now.first;LL u=now.second;
       va=siz[find(u)];
      /// cout<<"va="<<va<<" "<<"u="<<u<<"\n";
       if(!g[u].empty()){
          LL to=g[u].back().v;LL edgeid=g[u].back().id;
        ///  debug(to);
          if(find(to)==find(u)){
             g[u].pop_back();
          }
          else{
             ans.push_back(edgeid);
             siz[find(u)]+=siz[find(to)];
             siz[find(to)]=0;
             fa[find(to)]=find(u);
             siz[find(u)]-=x;///本次合并花费
          ///   debug(siz[find(u)]);
          ///   debug(u);debug(to);
             que.push({siz[find(u)],u});
             que.push({siz[find(u)],to});
          }
       }
   }
   for(auto i:ans){
       cout<<i<<"\n";
   }
   return 0;
}

 

然后我尝试将这样的点扔了,重新扔进去正确的点。那毫无疑问tle了。

LL va=now.first;LL u=now.second;
       LL fac=siz[find(u)];
       if(fac!=va){
          que.push({fac,u});
          continue;
       }


 

正确的思路是每次找到当前正确能代表连通块siz的点,然后以它去合并,同时启发式合并处理好新连通块能直连的边。不正确的就continue掉

这样的复杂度就是O(nlognlogn)的

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=3e5+1000;
typedef long long LL;
typedef pair<LL,LL>P;
inline LL read(){LL x=0,f=1;char ch=getchar();	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
struct Edge{
   LL v,id;
};
vector<Edge>g[maxn];
LL fa[maxn],val[maxn];
LL a[maxn];
LL find(LL x){
   if(x!=fa[x]) return fa[x]=find(fa[x]);
   return fa[x];
}
int main(void){

   LL n,m,x;n=read();m=read();x=read();
   LL sum=0;
   for(LL i=1;i<=n;i++) val[i]=read(),fa[i]=i,sum+=val[i],a[i]=val[i];
   for(LL i=1;i<=m;i++){
       LL u,v;u=read();v=read();
       g[u].push_back({v,i});
       g[v].push_back({u,i});
   }
   if(sum<(n-1)*x){printf("NO\n");return 0;}
   priority_queue<P>que;
   for(LL i=1;i<=n;i++){
       que.push({val[i],i});
   }
   printf("YES\n");
   vector<LL>ans;
   for(LL i=1;i<=n-1;i++){

       P now=que.top();que.pop();
       LL u=now.second;
       while(u!=find(u)){///跳过队列中的无效点
         now=que.top();que.pop();
         u=now.second;
         continue;
       }

       while(u==find(g[u].back().v)&&!g[u].empty()) g[u].pop_back();

       ans.push_back(g[u].back().id);

       LL newanc=find(g[u].back().v);///合并成新的连通块

       a[u]+=a[newanc]-x;
       que.push({a[u],u});
       fa[newanc]=find(u);
       if(g[u].size()<g[newanc].size()) swap(g[u],g[newanc]);

       g[u].insert(g[u].end(),g[newanc].begin(),g[newanc].end());
       g[newanc].clear();

   }
   for(auto i:ans){
       printf("%lld\n",i);
   }
   return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值