[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 }

原文地址:https://www.cnblogs.com/nanjoqin/p/9062990.html

时间: 2024-10-07 04:59:43

[POI2005] SKA-Piggy Banks的相关文章

[POI2005]ska Piggy banks|并查集|tarjan

1529: [POI2005]ska Piggy banks Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 973  Solved: 445[Submit][Status][Discuss] Description Byteazar 有 N 个小猪存钱罐. 每个存钱罐只能用钥匙打开或者砸开. Byteazar 已经把每个存钱罐的钥匙放到了某些存钱罐里. Byteazar 现在想买一台汽车于是要把所有的钱都取出来. 他想尽量少的打破存钱罐取出所有的钱,

Bzoj1529/POI2005 ska Piggy banks

题目:ska Piggy banks 传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1529 题目描述:Byteazar 有 N 个小猪存钱罐. 每个存钱罐只能用钥匙打开或者砸开. Byteazar 已经把每个存钱罐的钥匙放到了某些存钱罐里. Byteazar 现在想买一台汽车于是要把所有的钱都取出来. 他想尽量少的打破存钱罐取出所有的钱,问最少要打破多少个存钱罐. 分析: (1)算法一:如果存钱罐i有存钱罐j的钥匙,就建一条(i->j)

【BZOJ】1529 [POI2005]ska Piggy banks

[算法](强连通分量)并查集 [题解] 1.用tarjan计算强连通分量并缩点,在新图中找入度为0的点的个数就是答案. 但是,会爆内存(题目内存限制64MB). 2.用并查集,最后从1到n统计fa[i]==i的数量即是答案. (tarjan) #include<cstdio> #include<algorithm> using namespace std; const int maxn=1000010; struct edge{int u,v,from;}e[maxn]; int

BZOJ 1529: [POI2005]ska Piggy banks [并查集]

题意: Byteazar 有 N 个小猪存钱罐. 每个存钱罐只能用钥匙打开或者砸开. Byteazar 已经把每个存钱罐的钥匙放到了某些存钱罐里. Byteazar 现在想买一台汽车于是要把所有的钱都取出来. 他想尽量少的打破存钱罐取出所有的钱,问最少要打破多少个存钱罐. 每个存钱罐只有一把钥匙... 和那道洛谷3月月赛R1T3长的一样的有向连通环套树... 任意连通分量环上一个点都可以到达全部点 #include <iostream> #include <cstdio> #inc

BZOJ 1529 [POI2005]ska Piggy banks(并查集)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1529 [题目大意] 给出一张n个点n条边的有向图,问选取几个点为起点可以遍历全图 [题解] 由于是n条边,因此图为基环森林,选取环上点一定可以到达连通块内所有点, 因此只要统计连通块个数即可. [代码] #include <cstdio> #include <algorithm> using namespace std; const int N=1000100; int

【BZOJ】【1529】 【POI2005】ska Piggy banks

本来以为是tarjan缩点……但是64MB的空间根本不足以存下原图和缩点后的新图.所以呢……并查集= = orz hzwer MLE的tarjan: 1 /************************************************************** 2 Problem: 1529 3 User: Tunix 4 Language: C++ 5 Result: Memory_Limit_Exceed 6 *******************************

BZOJ 1529 ska piggy banks

1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4   5 #define up(a,b,c) for(register int c=a;c<=b;c++) 6   7 int n,fa[1000005],x,tot; 8   9 inline int Read(){ 10     int x = 0,f = 1; 11     char ch = getchar(); 12     

Sicily 1350. Piggy banks 解题报告

题目:1350. Piggy banks 思路: 首先把每个钥匙的位置存进key_positions[]中,然后从第一个bank开始,用不同的color给它们分组.比如第一个bank的钥匙在第二个bank中,那么可以直接先开第二个,第二个钥匙在第四个bank中,同样可以先开第四个,以此类推,直到某个钥匙出现在前面的同一组的某个bank中则需要打破一个.visited数组初始化为0,然后访问过后标记为颜色的编号.第21行visited[cur] == color只有找到颜色相同即在同一组的才需要打

[Poi2005]Piggy Banks小猪存钱罐

题目描述 Byteazar有 N 个小猪存钱罐. 每个存钱罐只能用钥匙打开或者砸开. Byteazar已经把每个存钱罐的钥匙放到了某些存钱罐里. Byteazar 现在想买一台汽车于是要把所有的钱都取出来. 他想尽量少的打破存钱罐取出所有的钱,问最少要打破多少个存钱罐. 输入格式 第一行一个整数 N (1 <= N <= 1.000.000)表示存钱罐的总数. 接下来每行一个整数,第 i+1行的整数代表第i个存钱罐的钥匙放置的存钱罐编号. 输出格式 一个整数表示最少打破多少个存钱罐. 分析题目

【并查集&amp;&amp;带权并查集】BZOJ3296&amp;&amp;POJ1182

bzoj1529[POI2005]ska Piggy banks [题目大意] n头奶牛m种语言,每种奶牛分别掌握一些语言.问至少再让奶牛多学多少种语言,才能使得它们能够直接或间接交流? [思路] (n+m)个点,奶牛学会某种语言就合并它和语言的节点.并查集维护联通块,答案为联通块个数-1.水,可是我跳坑了. 我一开始做法是设总的联通块有(n+m)个,然后没合并一次减去1.其实这样是不可行的,因为我们只需要考虑奶牛(即节点1..n)有几个联通块.有可能一些语言根本没有任何奶牛掌握-- 1 #in