其实这题水的一批...............
题目描述
在JSOI2005夏令营快要结束的时候,很多营员提出来要把整个夏令营期间的资料刻录成一张光盘给大家,以便大家回去后继续学习。组委会觉得这个主意不错!可是组委会一时没有足够的空光盘,没法保证每个人都能拿到刻录上资料的光盘,又来不及去买了,怎么办呢?!
组委会把这个难题交给了LHC,LHC分析了一下所有营员的地域关系,发现有些营员是一个城市的,其实他们只需要一张就可以了,因为一个人拿到光盘后,其他人可以带着U盘之类的东西去拷贝啊!
可是,LHC调查后发现,由于种种原因,有些营员并不是那么的合作,他们愿意某一些人到他那儿拷贝资料,当然也可能不愿意让另外一些人到他那儿拷贝资料,这与我们JSOI宣扬的团队合作精神格格不入!!!
现在假设总共有N个营员(2<=N<=200),每个营员的编号为1~N。LHC给每个人发了一张调查表,让每个营员填上自己愿意让哪些人到他那儿拷贝资料。当然,如果A愿意把资料拷贝给B,而B又愿意把资料拷贝给C,则一旦A获得了资料,则B,C都会获得资料。
现在,请你编写一个程序,根据回收上来的调查表,帮助LHC计算出组委会至少要刻录多少张光盘,才能保证所有营员回去后都能得到夏令营资料?
输入输出格式
输入格式:
先是一个数N,接下来的N行,分别表示各个营员愿意把自己获得的资料拷贝给其他哪些营员。即输入数据的第i+1行表示第i个营员愿意把资料拷贝给那些营员的编号,以一个0结束。如果一个营员不愿意拷贝资料给任何人,则相应的行只有1个0,一行中的若干数之间用一个空格隔开。
输出格式:
一个正整数,表示最少要刻录的光盘数。
输入输出样例
5 2 3 4 0 4 5 0 0 0 1 0
1
首先,这题有两种做法:并查集可以水过,然而太监就正解完事了啊。
所以首先说一下Tarjan的做法:
我们先建个图,然后就可以很显然的发现:
- 一个环只需要一张光盘QAQ。
- 只有每个入度为0的点需要光盘!
那么我们的思路就很好发现了丫,我们先缩个点,然后就可以很容易的统计一下入度为零的个数啊耶!
至此本题完结。
放上代码。
#include<bits/stdc++.h> using namespace std; int n; int dfn[205],low[205]; int cnt; bool ins[205]; int head[205],tot,s[205],tp,k; int inn[205],chu[205],hao[205]; bool vis[205],viss[205]; struct edge{ int to; int nxt; }a[100000],b[100000]; void add(int u,int v){ a[++tot].to=v; a[tot].nxt=head[u]; head[u]=tot; } void tarjan(int x){ dfn[x]=low[x]=++cnt; s[++tp]=x;ins[x]=true; for(int i=head[x];i;i=a[i].nxt) { if(!dfn[a[i].to]){ tarjan(a[i].to); low[x]=min(low[x],low[a[i].to]); } else if(ins[a[i].to]==true) low[x]=min(low[x],dfn[a[i].to]); } if(dfn[x]==low[x]) { k++; int y; do{ y=s[tp];
tp--;
ins[y]=false; hao[y]=k; }while(x!=y); } } int main() { cin>>n; for(int i=1;i<=n;i++) { int x; cin>>x; while(x!=0){ add(i,x); cin>>x; } } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(int x=1;x<=n;x++) for(int i=head[x];i;i=a[i].nxt) { int y=a[i].to; if(hao[x]==hao[y]) continue; inn[hao[y]]=1; chu[hao[x]]=1; } int ans=0,ans2=0; for(int i=1;i<=n;i++) if(inn[hao[i]]==0&&!vis[hao[i]]) {
ans++;
vis[hao[i]]=true;
}if(k==1) cout<<1<<endl; else cout<<ans<<endl; return 0; }
二:找爸爸 并查集
很多人会发现并查集时会将两个点结成双向的边。即本来只能A是B的爸爸,但是并查集之后B也是A的爸爸了显然哪里有点问题
所以这样就会导致最终统计的光盘数减少
其实我们只需要把并查集改一改,合并操作略有变化.改成“单向并查集”.
如果x愿意把光盘分享给y,就合并x的根和y本身,可以这么理解:x的根分享给x,x再分享给y
原版的并查集是合并x、y的根,这里不能这么做,因为这么做即表示:x的根可以分享给y的根(然而并不是)
此处还用了floyd转移关系。
特别感谢代码提供者!!
#include <iostream> using namespace std; int G[210][210]; int N, ans; int f[210]; //父亲 int ufs[210]; //结点是否为并查集的根 int Find(int x) { while(x != f[x]) x = f[x]; return x; } void Unite(int x, int y) { while(x != f[x]) x = f[x]; //找到x的根 f[y] = x; //x是y的父亲 } int main() { int tmp; cin >> N; for(int i=1; i<=N; i++) f[i] = i; //并查集初始化 for(int i=1; i<=N; i++) { while(true) { cin >> tmp; if(!tmp) break; G[i][tmp] = true; } } for(int k=1; k<=N; k++) //Floyd for(int i=1; i<=N; i++) for(int j=1; j<=N; j++) G[i][j] = G[i][j] || (G[i][k] && G[k][j]); for(int i=1; i<=N; i++) for(int j=1; j<=N; j++) if(G[i][j]) Unite(i, j); for(int i=1; i<=N; i++) ufs[Find(i)] = 1; for(int i=1; i<=N; i++) if(ufs[i]) ans ++; cout << ans << endl; return 0; }
最后,此处链接一种很神奇的方法!!-->点我点我
至此完结,谢谢!