您的位置:首页 > 编程语言

【HDU4313】Matrix 多校 解题报告+AC代码+思路+算法正确性证明,此为Kruskal贪心恶心版本,非自虐倾向慎入!建议想找解题报告的童鞋看简单版本的,这个我写给自己【目标达成 0.2%】

2012-08-02 03:40 801 查看
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>

using namespace std;
/**
【警告】这个恶心版本是我写给自己看的,关于解题报告,还是请移步简单版本吧,就在这篇文章的前一篇。

c0de4fun声明:本人未给出测试数据、未声明一次AC的题均为参考解题报告自己写的。
对那些无私贡献的大神真诚的致敬!
Problem: HDU4313 - Matrix
Reference:
I-   http://page.renren.com/601081183/note/862977450?ref=minifeed&sfet=2012&fin=5&ff_id=601081183&feed=page_blog&tagid=862977450&statID=&level= 好吧这个网址太尼玛蛋疼了!如果想看的话,人人网搜索HDOJ
找到2012 Multi-University Training Contest 2 Solution Report
1004-Matrix就是本题的思路。
II-  http://www.2cto.com/kf/201207/144028.html ↑参考代码
来源居然是红黑联盟……
很多年前了吧,现在也洗白了。
祝他们发展的越来越好
Knowledge Point: Kruskal+贪心算法  【或】 树形DP,本版本是Kruskal的,还会写树形dp
Version: Kruskal贪心简单版
Thought:

解题思路:
并查集+贪心
思路:
我们把有机器的节点颜色染成黑色(看到这句话别怕。。。其实跟什么红黑树一点关系都没有,就是为了方便)
那么,如果一个集合中有黑色节点的话,他就不能再跟有黑色节点的集合连接,也不能再连接黑色节点。
我们判断代表节点的颜色,然后按照Kruskal生成树,如果碰到集合代表是黑色节点,并且将与黑色节点连接或者
与另一个代表节点为黑色的集合连接,我们就干掉这条边,并且费用增加对应的权值。

一、关于采用Kruskal贪心算法正确性的证明。
【注意,我们这里关于有些术语的定义跟《算法导论》中的不一样!请仔细阅读!】
【注意,我们在使用该贪心算法之前已经将边按照《权值由高到低》排序了,阅读时请务必记住这一点】
前提:
给定的树是一颗。。。那叫什么树啊,也不叫最小生成树,反正就是能让全图连起来的树咯。
定义:
【安全边】:加入该边之后不会使两台机器连起来的边。
1.初始化:
对于第一条加入树的边(i,j),有三种情况:
I.color[i]与color[j]都是白色——(i,j)安全边,加入树
II.color[i]与color[j]一黑一白——(i,j)安全边,加入树
III.color[i]与color[j]都是黑色——危险边,删除并计算费用!( sum = sum + weight(i,j) )
由于我们将边按照权值由高到低排列了,那么无论是以上三种情况的哪一种,sum都是最优的。
2.保持
对于一个安全边集合(就是已经确定了一棵树,但是该树未遍历完所有的边,亦即还有一些边没有加入到树中)
,新加入该集合的边(i,j)与I其实一样的,分1.中的三种情况。
那么,【由于边按照权值从高到低排序】,已经加入这棵树中的所有边都是安全边,我们遇到的要加入这棵树的第一条危险边就是费用最小的、让机器联通起来的边了。
因为权值大的安全边都被我们尽可能的塞进了这棵树。
那么此时费用sum也是最优的。
如果这么说想不明白的话,那么就把这个安全边的集合想象成一个新的节点i,j是即将跟它连起来的节点!
3.终止
这还终止个毛啊,根据1和2遍历完给定图中所有的边之后,我们得到的必然是最小费用,直接输出就好。

二、并查集代表节点颜色问题(这部分在这个代码中没有体现,不给出证明了,在恶心代码中会给出证明)
在简单版本中,我们给出的代表节点颜色问题的解决方法是直接让黑色节点作为代表。
为什么要单独说并查集代表的颜色问题呢?因为如果你一个根节点(就是代表节点,下同)是白色,但是其中却含有黑色的节点,那么每次
还要搜索根节点的儿子以确定该集合内是否含有黑色的节点,这样时间复杂度太恶心了。
而在这个AC版本的代码中,我们是直接调换代表节点颜色的。
也就是说,如果 1 -> 3,3是黑色,1是根是白色,那么我们会直接让3变成白色,而1变成黑色。也就是说,如果一个全是白色的并查集中有了第一个黑色节点,那么这个
黑色节点就会被我们修改成白色,而根节点被我们修改成黑色,仔细一想不难发现这其实是与直接让黑色节点作根是等价的。
等价原因就是我们将边的权值从高到低排序了,因此两边的节点颜色并不影响其加入生成树的顺序。节点颜色只是用来判断是否会构成危险边而已。

那么,link起来的两个节点有三种情况:
1、i白色j白色——这时候寻找i、j的父亲(不用判断是否是同一个哦!因为j是新加入的嘛!~)如果i和j的父亲有一个是黑色,那么根据link的原则,谁是新父亲谁就要变成黑色,原父亲要变成白色
(妈的少考虑了这一点让我WA了7次啊!卧槽)
2、i黑色j黑色——直接拒绝这条边
3、i白色j黑色——找i的父亲,如果也是黑色,拒绝,如果是白色的话,放行,然后根据rank调整i的父亲与j的父亲之间的关系,并且根据i、j的颜色调整新父亲之间颜色的关系
4、j是白色i是黑色——同理

娘希匹的,那个1可恨死我了。。。。

*/
const int WHITE = 0;
const int BLACK = 1;
const int MAX_SIZE = 100010;

struct node
{
int id;
int x;
int y;
int val;
};
node nodes[MAX_SIZE];
int fa[MAX_SIZE]; //娘希匹的,以后并查集强迫自己用int数组写,否则写到struct里太非主流
int color[MAX_SIZE]; //节点颜色
int rank[MAX_SIZE];

int comp (const void *a,const void *b)
{
node *_a = (node*)a;
node *_b = (node*)b;
return _b->val - _a->val; //sort by val,order by decrease.
}
int findFather(int id)
{
int x = id;
while ( fa[x] != x )
{
fa[x] = fa[ fa[x] ];
x = fa[x];
} //这么写并查集不会超过三层。
return x;
}
bool join(int x,int y)
{
bool xBlack = false;
bool yBlack = false;
if( color[x] == BLACK )
xBlack = true;
if( color[y] == BLACK )
yBlack = true;

int fax = findFather(x);
int fay = findFather(y);

if( xBlack && yBlack )
return false;
if( color[fax] == BLACK && color[y] == BLACK )
return false;
if( color[fay] == BLACK && color[x] == BLACK )
return false;
if( color[fax] == BLACK && color[fay] == BLACK )
{
if( fax != fay )
return false;
}

if( rank[fax] > rank[fay] )
{
fa[fay] = fax;
rank[fax] = rank[fay] + rank[fax];
// if( xBlack == BLACK && yBlack == WHITE )
if( color[x] == WHITE && color[y] == WHITE )
{
if( color[fay] == BLACK )
{
color[fay] = WHITE;
color[fax] = BLACK;
}
}
if( color[x] == BLACK && color[y] == WHITE )
{
color[x] = WHITE;
color[fay] = WHITE;
color[fax] = BLACK;
}
// if( xBlack == WHITE && yBlack == BLACK )
if( color[x] == WHITE  && color[y] == BLACK )
{
color[y] = WHITE;
color[fay] = WHITE;
color[fax] = BLACK;
}
}
else
{
fa[fax] = fay;
rank[fay] = rank[fay] + rank[fax];
if( color[x] == WHITE && color[y] == WHITE )
{
if( color[fax] == BLACK )
{
color[fay] = BLACK;
color[fax] = WHITE;
}
}
if( color[x] == BLACK && color[y] == WHITE )
{
color[x] = WHITE;
color[fax] = WHITE;
color[fay] = BLACK;
}
if( color[x] == WHITE  && color[y] == BLACK )
{
color[y] = WHITE;
color[fax] = WHITE;
color[fay] = BLACK;
}
}
return true;
}
long long solve(int N)
{
long long res = 0;
for(int i = 0 ; i < N ;i++)
{
if ( join(nodes[i].x,nodes[i].y) == 0 )
{
//#define DBG
#ifdef DBG
printf("double black cuted : %d %d val is %d\n",nodes[i].x,nodes[i].y,nodes[i].val);
#endif
//join faild. Two black node has been linked! deleted.
res = res + nodes[i].val;
}
}
return res;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("B:\\acm\\SummerVacation\\DP-II\\C.in","r",stdin);
freopen("B:\\acm\\SummerVacation\\DP-II\\C.out","w",stdout);
#endif
int N,K,T;
while(scanf("%d",&T) != EOF)
{
for(int t = 1 ; t <= T; t++)
{
memset(fa,0,sizeof(fa));
memset(color,0,sizeof(color));
memset(nodes,0,sizeof(nodes));//memset是TLE和MLE杀手,不过据说是初始化大数组最快的,╮(╯_╰)╭谁知道呢
scanf("%d%d",&N,&K); //initallization over.
for(int i = 0 ; i < N-1 ; i++)
{
scanf("%d%d%d",&nodes[i].x,&nodes[i].y,&nodes[i].val);
nodes[i].id = i;

/*   fa[i] = i;  //Its father is itself.
rank[i] = 1;
color[i] = WHITE; // No machine.*/
fa[nodes[i].x] = nodes[i].x;
fa[nodes[i].y] = nodes[i].y;
rank[nodes[i].x] = 1;
rank[nodes[i].y] = 1;
color[nodes[i].x] = WHITE;
color[nodes[i].y] = WHITE;
}
for(int i = 0 ; i < K ; i++)
{
int nid;
scanf("%d",&nid);
color[nid] = BLACK; //set it have a machine.
}
qsort(nodes,N-1,sizeof(nodes[0]),comp);
long long  ans = solve(N);
printf("%I64d\n",ans);
}

}
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐