tree(Kruskal + 二分)

题干:

  给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权恰好有need条白色边的生成树。题目保证有解。

  第一行V,E,need分别表示点数,边数和需要的白色边数。

  接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。

  V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。

题解:

  先看下题干,无向带权图,注意建双向边。

  针对 " 最小权的生成树 " :

  首先,它是一棵最小生成树,最好用Kruskal 算法(先将边排序成小根堆,不断用最小权值的边连接,直到边数为 节点数-1 )。

  其次,又因为加上了黑白边,我们比较容易地可以想到,将白边特殊处理一下,让它恰好被选择 need 条。又因为我们凭空无法确定选择哪几条白边(不满足贪心性质),所以我们可以让它有一定顺序地夹在黑边之间以供构建最小生成树。我们再看一下,在 Kruskal 算法的快排中,我们是以权值作为排序的主要依据(次要依据为颜色),我们就可以尝试特殊地改变白边的权值大小,来改变白边的相应位置。可是怎么改呀?我们再思考一下,白边与黑边其实是相对独立的,但相应地白边与白边,黑边与黑边之间却是有大小限制关系的。我们可以用这个性质,将白边的权值整体加上或减去一个数,就可达到我们的目标。加多少?二分呗!(注意二分范围应是 -100~100)。

  再次,Kruskal 算法中需注意:

    1、在 Kruskal 算法的快排中,排列条件需有所变化(白边优先)。

      为什么白边优先呢?因为在某一黑边与白边权值相同时,选择白边更有可能达到need条白边数量,而且就算选了这条白边后仍选了相同权值的黑边,这也只能说明白边数量不够,要么还需找白边,要么无解;如果后来选了其他权值更高的白边,那也是先选白边更好——没人知道是否就是因为最后一条边选择了黑边而未选相同权值的白边而二分错误(OJ上证实存在这样的点)。越早达到目标,和权值就越小。

    2、在 Kruskal 算法的并查集中,注意实现方式。

    3、统计答案时注意 只要白边数量>=need 就算二分成功,输出的答案注意不要取最小值,我们求出的答案不一定合法(恰好有need条白色边),取最小值只会Wrong Answer。。。

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define $ 100010
 5 using namespace std;
 6 const int L=1<<20|1;
 7 char buffer[L],*S,*T;
 8 inline int read(){
 9     char a=getchar();
10     int sum=0;
11     while(a<'0'||a>'9')   a=getchar();
12     while(a>='0'&&a<='9') sum=(sum<<1)+(sum<<3)+a-'0',a=getchar();
13     return sum;
14 }
15 int v,e,need,m,n,mid,minn,maxx,dad[$],ans;
16 struct tree{
17     int s,t,w,col;
18     friend bool operator < (const tree &a,const tree &b){
19         if(a.w==b.w) return a.col<b.col;
20         return a.w<b.w;
21     }
22 }a[$];
23 inline int find(int x){    return dad[x]==x?x:dad[x]=find(dad[x]);     }
24 inline bool Kruskal(int x){
25     int col=0,w=0,sum=0;
26     for(register int i=1;i<=v;++i) dad[i]=i;
27     for(register int i=1;i<=e;++i) if(a[i].col==0) a[i].w+=x;
28     std::sort(a+1,a+e+1);
29     for(register int i=1,xx,yy,dadx,dady;i<=e;++i){
30         xx=a[i].s, yy=a[i].t;
31         dadx=find(xx), dady=find(yy);
32         if(dadx==dady) continue;
33         dad[dadx]=dady;
34         if(a[i].col==0) col++;
35         w+=a[i].w;      sum++;
36         if(sum==v-1) break;
37     }
38     for(register int i=1;i<=e;++i) if(a[i].col==0) a[i].w-=x;
39     if(col>=need) ans=w-x*need;
40     return col<need;
41 }
42 signed main(){
43     v=read(), e=read(), need=read();
44     for(register int i=1,s,t,c,col;i<=e;++i){
45         s=read(),t=read(),c=read(),col=read();
46         a[i]=(tree){    s+1,t+1,c,col    };
47     }
48     minn=-222, maxx=222;
49     while(minn<maxx){
50         mid=(minn+maxx)>>1;
51         if(Kruskal(mid)) maxx=mid;
52         else             minn=mid+1;
53     }
54     printf("%d",ans);
55 }
View Code

 

posted @ 2019-07-12 17:45  OI_zzyy  阅读(184)  评论(0编辑  收藏  举报