ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

洛谷 P2835 刻录光盘

2019-03-14 13:48:33  阅读:323  来源: 互联网

标签:光盘 查集 205 int P2835 刻录光盘 营员 洛谷 low


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

题目描述

在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;
}

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

 

至此完结,谢谢!

标签:光盘,查集,205,int,P2835,刻录光盘,营员,洛谷,low
来源: https://www.cnblogs.com/qxyzili--24/p/10529783.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有