洛谷 P2835 刻录光盘

时间:2022-02-26 00:19:40

  其实这题水的一批...............

题目描述

在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,一行中的若干数之间用一个空格隔开。

 

输出格式:

一个正整数,表示最少要刻录的光盘数。

 

输入输出样例

输入样例#1:
5
2 3 4 0
4 5 0
0
0
1 0
输出样例#1:
1

 
 

  首先,这题有两种做法:并查集可以水过,然而太监就正解完事了啊。

所以首先说一下Tarjan的做法:

  我们先建个图,然后就可以很显然的发现:

  1. 一个环只需要一张光盘QAQ。
  2. 只有每个入度为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;
}

最后,此处链接一种很神奇的方法!!-->点我点我

 

至此完结,谢谢!