1. 程式人生 > >Kruskal算法及其類似原理的應用——【BZOJ 3654】tree&&【BZOJ 3624】[Apio2008]免費道路

Kruskal算法及其類似原理的應用——【BZOJ 3654】tree&&【BZOJ 3624】[Apio2008]免費道路

ios ide line onclick 隨著 fine sort pri ostream

首先讓我們來介紹Krukal算法,他是一種用來求解最小生成樹問題的算法,首先把邊按邊權排序,然後貪心得從最小開始往大裏取,只要那個邊的兩端點暫時還沒有在一個聯通塊裏,我們就把他相連,只要這個圖裏存在最小生成樹我們就一定可以找到他。(證明:首先如果我們沒有選最小的邊,那麽他一定可以踢掉其他的邊來使生成樹更小,於是最小一定取,那麽接下來能取的邊同理,以此類推我們證畢。)

這個算法其實不要緊,但是他這種利用邊的置換的思想,與得到最小生成樹的定性,才是我們真正的收獲。

【BZOJ 3654】tree

這道題在思路上還是很清晰的,他保證存在了,那麽我們就是找最小的就可以。那麽我們先把邊排序,跑Kruskal,然後通過二分給白邊加權,然後再求最小生成樹,慢慢使我們的白邊樹逼近需要就是了,因為他說一定存在,所以你二分到小一點就多,大一點就少的情況就可以看你取邊順序直接取一個值就好了。

技術分享
#include <cstdio>
#include <algorithm>
inline void read(int &sum){
    register char ch=getchar();
    for(sum=0;ch<0||ch>9;ch=getchar());
    for(;ch>=0&&ch<=9;sum=(sum<<1)+(sum<<3)+ch-0,ch=getchar());
}
const int N=50010;
const int M=100010
; struct E{ int a,b,w,c; }e[M]; int f[N],h[2]; inline int find(int x){ return f[x]==x?x:(f[x]=find(f[x])); } int n,m,need; inline bool comp(E a,E b){ return a.w+h[a.c]<b.w+h[b.c]||(a.w+h[a.c]==b.w+h[b.c]&&a.c<b.c); } inline int get_ans(int &get){ for(int
i=1;i<=n;i++)f[i]=i; std::sort(e+1,e+m+1,comp); register int x,y,w,c,hav=0,ret=0,whi=0; for(int i=1;i<=m;i++){ x=e[i].a+1,y=e[i].b+1,w=e[i].w,c=e[i].c; if(find(x)==find(y))continue; f[find(x)]=find(y); ret+=w+h[c],whi+=c,hav++; if(hav==n-1)break; } get=hav-whi; return ret; } int main(){ read(n),read(m),read(need); for(int i=1;i<=m;i++) read(e[i].a),e[i].a++,read(e[i].b),e[i].b++,read(e[i].w),read(e[i].c); int mid,l=-100,r=100,ans,get; while(l<=r){ mid=(l+r)>>1,h[0]=mid; int ret=get_ans(get); if(get>=need) ans=ret-need*h[0],l=mid+1; else r=mid-1; } printf("%d",ans); return 0; }
【BZOJ 3654】tree

【BZOJ 3624】[Apio2008]免費道路

這道題的思維就要比上道題,大得多。首先鵝卵石邊數不夠 PASS!!!,然後不聯通 PASS!!!。現在我們就可以用一種特殊的方法來試圖找到我們想要的邊數,我們發現如果我們把水泥路分成兩半,一半放在鵝卵石前(鵝卵石連續),另一半放在鵝卵石後,然後跑類Krusal(其實一樣只是不是求最小),那麽隨著前一半長度減小,鵝卵石邊數單調不減,且最小變化幅度小於等於1,所以如果存在我們一定可以找到那種方案(為什麽呢,如果我們把水泥路鵝卵石路分別都視為一類,不管具體是什麽,那麽顯然成立,那麽如果我們關註他們具體是誰就要考慮到他們順序問題,然而實際上並沒有關系),如果不存在直接 PASS !!!

然而如果我們知道這個的話,就會發現,其實我們只要判斷一下題目中的k在不在先鵝卵石再水泥和先水泥再鵝卵石圍成的區間裏就好了。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ft first
#define sd second
#define abs(a) ((a)<0?-(a):(a))
#define mmp(a,b) std::make_pair((a),(b))
typedef std::pair<int,int> pii;
const int N=20010;
const int M=100010;
int f[N],n,m,m0,m1,k;
pii use[M],ger[M],cct[M];
int in[M];
inline int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
inline void Unit(int x,int y){f[find(x)]=find(y);}
inline bool judge(){
  if(k>m0)return true;int ret=0;
  for(int i=1;i<=n;++i)
    if(find(i)==i)++ret;
  return ret>1;
}
inline int check(int pos){
  for(int i=1;i<=n;++i)f[i]=i;
  for(int i=1;i<=pos;++i)use[i]=cct[i];
  for(int i=1;i<=m0;++i)use[i+pos]=ger[i];
  for(int i=pos+1;i<=m1;++i)use[m0+i]=cct[i];
  memset(in,-1,sizeof(in));int ret=0,have=1;
  for(int i=1;i<=m&&have<n;++i){
    if(find(abs(use[i].ft))==find(use[i].sd))continue;
    if(use[i].ft>0)in[i]=0,++ret;
    else in[i]=1;
    Unit(abs(use[i].ft),use[i].sd),++have;
  }//printf("<%d %d>\n",pos,ret);
  return ret;
}
int main(){
  scanf("%d%d%d",&n,&m,&k);
  for(int i=1;i<=n;++i)f[i]=i;
  for(int i=1,x,y,z;i<=m;++i){
    scanf("%d%d%d",&x,&y,&z);
    if(z)cct[++m1]=mmp(-x,y);
    else ger[++m0]=mmp(x,y);
    Unit(x,y);
  }if(judge()){puts("no solution");return 0;}
  int l=0,r=m1,mid;
  bool ans=false;
  while(l<=r){
    mid=(l+r)>>1;
    int ret=check(mid);
    if(ret==k){ans=true;break;}
    if(ret>k)l=mid+1;
    else r=mid-1;
  }if(!ans){puts("no solution");return 0;}
  for(int i=1;i<=m;++i)
    if(in[i]!=-1)
      printf("%d %d %d\n",abs(use[i].ft),use[i].sd,in[i]);
  return 0;
}

Kruskal算法及其類似原理的應用——【BZOJ 3654】tree&&【BZOJ 3624】[Apio2008]免費道路