【转载】NOIP2015 TG D1T2 message

来源:互联网 发布:中国网络教育大学 编辑:程序博客网 时间:2024/04/29 05:15
又是一道简单题。这道题目只不过是江老师讲了一下,我才写解题报告……还有一个原因是介绍了时间戳。
【题意分析】
给出一个n个点,n条边的有向图,求最小环的长度。
【算法分析】
有三种方法:删点删边、并查集、时间戳。
【1:删点删边】
对于对于这样的一个有向图,我们可以发现必然有一个环。当然,也有可能是有若干个环,但是无疑,我们实际上只需要这些环。那其他的多余的、不构成环的点和边怎么办呢?删掉!以样例数据为例,我们可以知道,如果有多余的点,我们必然能够找到入度为0的点(也可以由拓扑排序知道),接着将这些点以及其相连的边删去,不断重复这个过程,剩下来没被删掉的点和边,我们就可以通过while循环来遍历。最后即可求出环的长度。
这个过程和拓扑排序有关,所以我们也同时可以知道拓扑排序的另外一个用途:将整个有向图删得只剩环。如果没有环,那就什么都没有剩下了,反之,我们就可以直接对有向图中环进行相应的操作。
【2:并查集】
并查集的实现更加简单。我们只需不断加入边,当我们发现某条边所相连的两个点在同一个集合中,则继续用while循环遍历一遍。以此类推,直至求得最小环。
并查集的实现还要注意一点:不能用普通的递归写法,应该用迭代(或循环),要么就用启发式合并。
【3:时间戳】
时间戳的实现是最为巧妙的。什么是时间戳?在这里就是被DFS到的次序。比如说,我们从某一个点出发,开始DFS,或者说因为每个点的出度都为1,那么就可以直接while。如果在某个节点u上出发,发现next[u]是节点v,而且节点v已经被访问过,那么这个联通块中的环的长度就是u和v的时间戳的差减一。以此类推,可以扩展到有多个联通块的情况。但是要注意多个联通快的情况,需要再加一个所谓“轮数”的标记,比如说如果有一些入度为0的点,同时连到一个环中,那么还需要比较一下最后的轮数,不然的话这个最后的节点及其下一个节点就是被以不同的节点出发的,也就是说,同一个环,如果不加轮数这样的标记的话,就会有不同的长度。

【代码】
【删点删边】

/*
Program : NOIP2015 TG D1T2 message
Way : 1,Delete Points & Lines
Input : Files,"message.in"
Output : Files,"message.out"
*/
# include <queue>
# include <cstdio>
using namespace std;
const int MAXN = 200010;
const int INF = 10000000;
int next[MAXN];
int in_n[MAXN];
bool visited[MAXN];
int n;
int MinLen = INF;
queue<int> Q;
void Delete_in_0(){
for (int i=0;i<n;++i) if (in_n[i] == 0) Q.push(i);
while (!Q.empty()){
visited[Q.front()] = true;
--in_n[next[Q.front()]];
if (in_n[next[Q.front()]] == 0) Q.push(next[Q.front()]);
Q.pop();
}
}

int Round_Len(int pos){
int len = 0;
while (!visited[pos]){
visited[pos] = true;
pos = next[pos];
++len;
}
return len;
}

inline int min(int a,int b){ return a < b ? a : b; }

int main(){
freopen("message.in","r",stdin);
freopen("message.out","w",stdout);
scanf("%d",&n);
for (int i=0;i<n;++i){
scanf("%d",&next[i]);
--next[i];
++in_n[next[i]];
}

Delete_in_0();

for (int i=0;i<n;++i){
if (!visited[i]){
MinLen = min(MinLen,Round_Len(i));
}
}

printf("%d\n",MinLen);
return 0;
}

【并查集】

/*
Program : NOIP2015 TG D1T2 message
Way : 2,Union-Find Set
Input : Files,"message.in"
Output : Files,"message.out"
*/
# include <cstdio>
using namespace std;
const int MAXN = 200010;
const int INF = 10000000;
int father[MAXN];
int size[MAXN];
int findroot(int x){
//printf("%d\n",x);
return father[x] = (father[x] == x ? x : findroot(father[x]));
}

void merge(int a,int b){
if (findroot(a) == findroot(b)) return;
int sizea = size[findroot(a)];
int sizeb = size[findroot(b)];
if (sizea < sizeb){
father[findroot(a)] = findroot(b);
size[findroot(b)] += sizea;
}
else{
father[findroot(b)] = findroot(a);
size[findroot(a)] += sizeb;
}
}

int n;
int next[MAXN];
bool visited[MAXN];

int RoundLen(int pos){
int len = 0;
while (!visited[pos]){
visited[pos] = true;
pos = next[pos];
++len;
}
return len;
}

int MinLen = INF;

inline int min(int a,int b){ return a < b ? a : b; }

int main(){
freopen("message.in","r",stdin);
freopen("message.out","w",stdout);
scanf("%d",&n);
for (int i=0;i<n;++i) { size[i] = 1; father[i] = i; }
for (int i=0;i<n;++i){
scanf("%d",&next[i]); --next[i];
if (findroot(i) == findroot(next[i]) && !visited[i] && !visited[next[i]]){
MinLen = min(MinLen,RoundLen(i));
}
merge(i,next[i]);
}
printf("%d\n",MinLen);
}

【时间戳】

/*
Program : NOIP2015 TG D1T2 message
Way : 3,Time Signs
Input : Files,"message.in"
Output : Files,"message.out"
*/
# include <cstdio>
using namespace std;
const int MAXN = 200010;
const int INF = 10000000;
int t = 1;
int ttime = 0;
int next[MAXN];
int time[MAXN];
int turn[MAXN];

int RoundLen(int x){
++ttime;
time[x] = t;
turn[x] = ttime;
while (time[next[x]] == 0){
++t;
x = next[x];
time[x] = t;
turn[x] = ttime;
}
if (ttime == turn[next[x]])
return time[x] - time[next[x]] + 1;
else
return INF;
}

inline int min(int a,int b){ return a < b ? a : b; }

int n;
int MinLen = INF;

int main(){
freopen("message.in","r",stdin);
freopen("message.out","w",stdout);
scanf("%d",&n);
for (int i=0;i<n;++i){ scanf("%d",&next[i]); --next[i]; }
for (int i=0;i<n;++i){
if (time[i] == 0) MinLen = min(MinLen,RoundLen(i));
}
printf("%d\n",MinLen);
return 0;
}

【总结】
很明显,时间戳的代码要好很多,不仅好写,也好想。所以在算法竞赛中,我们应该追求更巧妙的算法!而这一次的时间戳,也是图论中一个非常重要的内容,应该要再学好。

0 0