tree(陈立杰)[国家集训队2012]
时间限制:3.0s 内存限制:1.0GB
【大意】
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。题目保证有解。
【输入格式】
第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行
每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
接下来E行
每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
【输出格式】
一行表示所求生成树的边权和。
【数据规模和约定】
0:V<=10
1,2,3:V<=15
0,..,19:V<=50000,E<=100000
所有数据边权为[1,100]中的正整数。
1,2,3:V<=15
0,..,19:V<=50000,E<=100000
所有数据边权为[1,100]中的正整数。
【样例输入】
2 2 1
0 1 1 1
0 1 2 0
0 1 1 1
0 1 2 0
【样例输出】
2
【题解】
cogs上这题有个标签叫二分,刚开始完全不知道是为了什么……先搞了一遍愚蠢的贪心,后来发现好像不是那么回事。然后想着暴力枚举情况,感觉这是时间内存双爆炸的节奏……然后看了题解,被正解的做法惊艳到了,%%%WJMZBMR。本蒟蒻曾经盯着那个明显有所寓意的[1,100]看了半天,然而并没能参悟其中玄机。
事情是这样的……克鲁斯卡尔不是要按边权排序从小到大选边吗,然后每个边它边权越小越容易被选上啊。我们不是不知道怎么选刚好合适吗,就从多到少二分选呗。怎么能实现呢,只要从-101到+101二分,对于分到的每一个值,都把白边的权值全部加上它,去看现在选了多少白边,加的值越大选的白边越少;如果白边少了就把右界向下调,白边多了或正相等就把左界向上调,直到恰好调出那个need。原来二分都是二分答案云云,这种神奇的二分还是头一次做到。通过二分调整来确定一个刚好合适的值,题面上的“恰好”、“保证有解”这些字眼原来都有所指啊……不愧是国家集训队的题。
实现的时候WA得很厉害,因为我是直接统计的每一个被选边的原边权之和,而正确的做法应该是用加了二分值的边权减去need*二分值。刚开始百思不得其解,后来忽然明白二分可能并不能刚好分出那个值,但是已经找到了一个确定值。这时候选的白边不一定就是need个,毕竟相同权值的白边还可能有很多条呢。所以减去need*二分值,得到的答案才是准确的;如果在check的真实过程中最后选了更多的白边,直接把它当做选了同样权值的黑边看待。如果某人在考场上想到了正解却因为这种细节被卡到几十分甚至更低,失落之情可想而知。在所有细节处留心,才是OIer的风范啊。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int sj=50005; int f[sj],e1,n,m,nee,a1,a2,a3,a4,zj,yj,mid; struct B { int v,w,u,bh,cr; }b[sj<<1]; void add(int x,int y,int z,int e) { e1++; b[e1].v=y; b[e1].w=z; b[e1].cr=z; b[e1].u=x; b[e1].bh=e; } int comp(const B&x,const B&y) { return (x.cr==y.cr)?(x.bh<y.bh):(x.cr<y.cr); } int find(int x) { if(f[x]==-1) return x; f[x]=find(f[x]); return f[x]; } void hb(int x,int y) { x=find(x),y=find(y); f[x]=y; } void init() { scanf("%d%d%d",&n,&m,&nee); for(int i=1;i<=m;i++) { scanf("%d%d%d%d",&a1,&a2,&a3,&a4); add(a1,a2,a3,a4); } } bool kl() { a1=a2=a4=0; sort(b+1,b+m+1,comp); for(int i=1;i<=m;i++) if(find(b[i].u)!=find(b[i].v)) { if(b[i].bh==0) a1++; a4++; a2+=b[i].cr; hb(b[i].u,b[i].v); if(a4==n-1) break; } return a1>=nee; } int main() { init(); zj=-105,yj=105; while(zj<=yj) { mid=(zj+yj)>>1; memset(f,-1,sizeof(f)); for(int i=1;i<=m;i++) if(b[i].bh==0) b[i].cr=b[i].w+mid; if(kl()) zj=mid+1,a3=a2-nee*mid; else yj=mid-1; } printf("%d",a3); return 0; }
南风知我意,吹梦到西洲。