[POI2005] SKA-Piggy Banks

Posted 南條キミツ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[POI2005] SKA-Piggy Banks相关的知识,希望对你有一定的参考价值。

ps.有关Tarjan算法缩点的简要回顾。

今天上午在Luogu随机到了这道题 Luogu P3420,题目大概是这样:

 

题目描述

Byteazar the Dragon拥有N个小猪存钱罐。每一个存钱罐能够用相应的钥匙打开或者被砸开。Byteazar已经将钥匙放入到一些存钱罐中。现在已知每个钥匙所在的存钱罐,Byteazar想要买一辆小汽车,而且需要打开所有的存钱罐。然而,他想要破坏尽量少的存钱罐,帮助Byteazar去决策最少要破坏多少存钱罐。

你需要写一段程序包括:读入存钱罐的数量以及相应的钥匙的位置,求出能打开所有存钱罐的情况下,需要破坏的存钱罐的最少数量并将其输出。

输入输出格式

输入格式:

第一行:包括一个整数N(1<=N<=1000000),这是Byteazar the Dragon拥有的存钱罐的数量。

存钱罐(包括它们对应的钥匙)从1到N编号。

接下来有N行:第i+1行包括一个整数x,表示第i个存钱罐对应的钥匙放置在了第x个存钱罐中。

输出格式:

仅一行:包括一个整数,表示能打开所有存钱罐的情况下,需要破坏的存钱罐的最少数量。

输入输出样例

输入样例#1:
4
2
1
2
4
输出样例#1:
2

看到这道题的时候我是很高兴的:这不就是记录一个入度的问题嘛!图都不需要建就做好了。于是写了一个前后不超20行的代码就交了,果断拿了20分。

然后我开始重新审视这道题目:我发现题目是有环的,而且还可能有好多个,所以入度为零的点可能一个都没有,而且还有各种问题……

糟了,难度一下就上来了。不过好在我会复制敲Tarjan的板子!可以发现,只要砸开(打开)一个环里的任一个存钱罐,那么整个环就都会被打开。

缩点!

Tarjan的本质其实就是Dfs的过程,而缩点的思想是,我们将未搜过的点称为白点,已退栈(已经Dfs过)的点称为黑点,而正在Dfs栈中的点定义为灰点。

然后我们可以愉快地Dfs,遇到白点就走,黑点就返回(因为已经搜完了)。

但是如果搜着搜着搜到了灰点,也就是vis[x] = 1,dfn[x] < dfn[now]的点,那么说明这玩意儿转回去了……那这就是一个环。

这时我们对每个节点维护的2个变量——dfn(被dfs到的次序,也叫时间戳),low(标记了节点i能够回溯到的最早位于栈中的节点,也就是你走回去到达的节点啦)其中的low进行操作取min,这里具体的原理和作用我并说不很清楚,背过理解就好。

之后就弹栈,将这一段环染成同一种颜色,也就是缩点。

技术分享图片
 1 int stack[maxn], top;
 2 
 3 void Tarjan(int s) {
 4     dfn[s] = low[s] = ++dfn_num;
 5     vis[s] = 1, stack[++top] = s;
 6     for(int i=head[s]; i; i=edge[i].nxt) {
 7         int v = edge[i].v;
 8         if( !dfn[v] ) {
 9             Tarjan(v), low[s] = min(low[s], low[v]);
10         } else if( vis[v] ) low[s] = min(low[s], dfn[v]);
11     }
12     if( dfn[s]==low[s] ) {
13         vis[s] = 0, col[s] = ++col_num;
14         while( stack[top]!=s ) {
15             col[stack[top]] = col_num;
16             vis[stack[top]] = 0, --top;
17         }
18         --top;
19     }
20 }
Tarjan缩点

 

不一会我就复制来了敲完了板子,高高兴兴的提交了上去——40分。

???

其实这篇博客的重点已经写完了,不过我还要继续写。

我只好仔细对着题解检查然后调试,发现我边建反了……

而且Tarjan判断一个点已经被搜过的根据是dfn[] != 0,不是vis[],vis[]只是一个是否在栈中的标记,也就是给灰点的标记……而我在main()里却拿vis[]来判断是否搜过这个点了。

 以及在对新图做什么的时候,注意此时的点已经不是一开始的点了,而是一个颜色代表一个点。所以操作的时候大多是应该是对col[n]操作,而不是n……

 然后再统计一下入度为0的点就好啦。

最后帖一波代码。

技术分享图片
 1 #include <cstdio>
 2 #include <cctype>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 const int maxn = 1000000 + 10;
 8 int n, t, head[maxn], vis[maxn], degree[maxn], ans;
 9 int dfn[maxn], low[maxn], col[maxn], dfn_num, col_num;
10 
11 struct Edge { int v, nxt; }edge[maxn];
12 
13 inline void read(int &x) {
14     register char ch = 0;  x = 0;
15     while( !isdigit(ch) ) ch = getchar();
16     while( isdigit(ch) ) x = (x*10) + (ch^48), ch = getchar();
17 }
18 
19 int stack[maxn], top;
20 
21 void Tarjan(int s) {
22     dfn[s] = low[s] = ++dfn_num;
23     vis[s] = 1, stack[++top] = s;
24     for(int i=head[s]; i; i=edge[i].nxt) {
25         int v = edge[i].v;
26         if( !dfn[v] ) {
27             Tarjan(v), low[s] = min(low[s], low[v]);
28         } else if( vis[v] ) low[s] = min(low[s], dfn[v]);
29     }
30     if( dfn[s]==low[s] ) {
31         vis[s] = 0, col[s] = ++col_num;
32         while( stack[top]!=s ) {
33             col[stack[top]] = col_num;
34             vis[stack[top]] = 0, --top;
35         }
36         --top;
37     }
38 }
39 
40 int main(int argc, char const *argv[])
41 {
42     scanf("%d", &n);
43     for(int i=1; i<=n; ++i) {
44         read(t), edge[i].v = i;
45         edge[i].nxt = head[t], head[t] = i;
46     }
47     for(int i=1; i<=n; ++i)  if( !col[i] ) Tarjan(i);
48     for(int i=1; i<=n; ++i) {
49         for(int j=head[i]; j; j=edge[j].nxt)
50             if( col[edge[j].v]!=col[i] )  ++degree[col[edge[j].v]];
51     }
52     for(int i=1; i<=col_num; ++i)  if( !degree[i] ) ++ans;
53     printf("%d\n", ans);
54     return 0;
55 }
View Code

 




以上是关于[POI2005] SKA-Piggy Banks的主要内容,如果未能解决你的问题,请参考以下文章

[POI2005]SKA-Piggy Banks

[POI2005] SKA-Piggy Banks

bzoj1529: [POI2005]ska Piggy banks

BZOJ1529 [POI2005]ska Piggy banks

bzoj1529 [POI2005]ska Piggy banks

Bzoj1529/POI2005 ska Piggy banks