1、P1177 【模板】快速排序
题目描述
利用快速排序算法将读入的 N 个数从小到大排序后输出。
快速排序是信息学竞赛的必备算法之一。对于快速排序不是很了解的同学可以自行上网查询相关资料,掌握后独立完成。(C++ 选手请不要试图使用
STL
,虽然你可以使用sort
一遍过,但是你并没有掌握快速排序算法的精髓。)输入格式
第 1 行为一个正整数 N,第 2 行包含 N 个空格隔开的正整数 ai,为你需要进行排序的数,数据保证了 Ai 不超过 10^9。
输出格式
将给定的 N 个数从小到大输出,数之间空格隔开,行末换行且无空格。
输入输出样例
输入 #1复制
5 4 2 4 5 1输出 #1复制
1 2 4 4 5说明/提示
对于 20% 的数据,有 N≤10^3;
对于 100% 的数据,有 N≤10^5。
题解:因为本题的测试数据有很大的数据量且有已排好序的测试数据,对于已排好序的数据传统的快速排序的时间复杂度会退化为O(n^2),所以用传统快速排序来解决该问题会T掉三个点。
所以我们可以用二分来优化,我们每次排序都需先找到中间点,然后用左右两边的数据来跟中间值作比较,将左边比中间值大的与右边中间值小的进行交换,直到left>right这样左边就都是比中间值小的,右边就都是比中间值大的。这样一轮排序就做好了,接着按照传统快排分治思想按同样的方法处理两边,直到整个数列有序。
#include <stdio.h>
int n,a[1000010];
void swap(int *p,int *q)
{ int t=*p;
*p=*q;
*q=t;
return;
}
void quicksort(int left,int right)
{ int mid=a[(left+right)/2],l=left,r=right;
if(left>=right)
return;
do
{while(a[l]<mid)
l++;
while(a[r]>mid)
r--;
if(l<=r)
{swap(&a[l],&a[r]);
l++;
r--;
}
}while(l<=r);
quicksort(left,r);
quicksort(l,right);
}
main()
{scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
quicksort(0,n-1);
for(int i=0;i<n-1;i++)
printf("%d ",a[i]);
printf("%d\n",a[n-1]);
}
2、P1305 新二叉树
题目描述
输入一串二叉树,输出其前序遍历。
输入格式
第一行为二叉树的节点数 n。(1≤n≤26)
后面 n 行,每一个字母为节点,后两个字母分别为其左右儿子。
空节点用
*
表示输出格式
二叉树的前序遍历。
输入输出样例
输入 #1复制
6 abc bdi cj* d** i** j**输出 #1复制
abdicj
题解:首先先记录下根节点的字符,一个节点即可以是父节点的子节点,也可以是子节点的父节点,对于每一个节点我们需要先建立它与父节点以及子节点的关系,将它本身字符的ASCII码值作为存左右儿子节点的数组下标,同样子节点也应将它的父节点的ASCII值的下标记录起来,建立双向互通的关系。在输出树前序时,可以通过父节点的ASCII值找到他的左右儿子节点并输出。(不过应注意的是应为直接用字符的ASCII作为数组下标所以数组应该稍微开大点,要开小点也可以将下标改为字符-‘a’对应的数字)
#include <stdio.h>
struct node
{char l;
char r;
char fa;
}tree[1000];//开大点因为下标存的是字符的ASCII码值
void preorder(char t)
{ printf("%c",t);
if(tree[t].l!='*')preorder(tree[t].l);//通过父节点的ASCII码值找他对应的左儿子
if(tree[t].r!='*')preorder(tree[t].r);//同理
}
main()
{int n;
char a[4],root;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{scanf("%s",&a);
if(i==1) root=a[0]; //记录根
//建立父节点与左右儿子的相互连接关系
tree[a[0]].l=a[1]; //父节点的左儿子
tree[a[1]].fa=a[0];//左节点的父节点
tree[a[0]].r=a[2];//父节点的右儿子
tree[a[2]].fa=a[0];//右儿子的父节点
}
preorder(root);//从根开始找
}
3、P3367 【模板】并查集
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 N,M ,表示共有 N 个元素和 M 个操作。
接下来 M 行,每行包含三个整数 Zi,Xi,Yi 。
当 Zi=1 时,将 Xi 与 Yi 所在的集合合并。
当 Zi=2 时,输出 Xi 与 Yi 是否在同一集合内,是的输出
Y
;否则输出N
。输出格式
对于每一个 Zi=2 的操作,都有一行输出,每行包含一个大写字母,为
Y
或者N
。输入输出样例
输入 #1复制
4 7 2 1 2 1 1 2 2 1 2 1 3 4 2 1 4 1 2 3 2 1 4输出 #1复制
N Y N Y说明/提示
对于 30% 的数据,N≤10,M≤20。
对于 70% 的数据,N≤100,M≤10^3。
对于 100% 的数据,1≤N≤10^4,1≤M≤2×10^5, 1≤Xi,Yi≤N,Zi∈{1,2}。
题解:用一个数组下标表示自己,里面的值存它的根,对于每个元素都能通过递归它的父节点找到他们的根。首先将每个元素的根都赋为自己,以便之后寻找根,当Z为1时将两个值统一往一边将对方作为它的父节点联系起来,Z为2时则判断两个的根是否一样,一样则在同一个集合。
#include <stdio.h>
int n,m,a[10010],z,x,y;
int find_root(int t)
{ if(a[t]==t) //根是自己则停止
return t;
return a[t]=find_root(a[t]); //压缩路径
}
main()
{scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)//先将根都初始化为自己
a[i]=i;
while(m--)
{scanf("%d%d%d",&z,&x,&y);
if(z==2)
{if(find_root(x)==find_root(y))
printf("Y\n");
else
printf("N\n");
}
else
a[find_root(x)]=find_root(y);
}
}
4、 P1551亲戚
题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x 和 y 是亲戚,y 和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲戚都是 y 的亲戚,y 的亲戚也都是 x 的亲戚。
输入格式
第一行:三个整数 n,m,p,(n,m,p≤5000),分别表示有 n 个人,m 个亲戚关系,询问 p 对亲戚关系。
以下 m 行:每行两个数 Mi,Mj,1≤Mi, Mj≤N,表示 Mi 和 Mj 具有亲戚关系。
接下来 p 行:每行两个数 Pi,Pj,询问 Pi 和 Pj 是否具有亲戚关系。
输出格式
p 行,每行一个
Yes
或No
。表示第 i 个询问的答案为“具有”或“不具有”亲戚关系。输入输出样例
输入 #1复制
6 5 3 1 2 1 5 3 4 5 2 1 3 1 4 2 3 5 6输出 #1复制
Yes Yes No
题解:本题跟上一题“P3367 【模板】并查集”几乎一样,也是将他们两两通过函数联系起来,即以一边为根,最后判断时两个的根一样则就是亲戚。
#include <stdio.h>
int a[5010];
int find(int t)
{ if(a[t]==t)
return t;
return a[t]=find(a[t]);
}
main()
{int n,m,p,x,y;
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n;i++)//初始化根
a[i]=i;
while(m--)
{scanf("%d%d",&x,&y);
a[find(x)]=find(y);
}
while(p--)
{scanf("%d%d",&x,&y);
if(find(x)==find(y))
printf("Yes\n");
else
printf("No\n");
}
}
5、P2078 朋友
题目背景
小明在 A 公司工作,小红在 B 公司工作。
题目描述
这两个公司的员工有一个特点:一个公司的员工都是同性。
A 公司有 N 名员工,其中有 P 对朋友关系。B 公司有 M 名员工,其中有 Q 对朋友关系。朋友的朋友一定还是朋友。
每对朋友关系用两个整数 (Xi,Yi) 组成,表示朋友的编号分别为 Xi,Yi。男人的编号是正数,女人的编号是负数。小明的编号是 1,小红的编号是 -1。
大家都知道,小明和小红是朋友,那么,请你写一个程序求出两公司之间,通过小明和小红认识的人最多一共能配成多少对情侣(包括他们自己)。
输入格式
输入的第一行,包含 4 个空格隔开的正整数 N,M,P,Q。
之后 P 行,每行两个正整数 Xi,Yi。
之后 Q 行,每行两个负整数 Xi,Yi。
输出格式
输出一行一个正整数,表示通过小明和小红认识的人最多一共能配成多少对情侣(包括他们自己)。
输入输出样例
输入 #1复制
4 3 4 2 1 1 1 2 2 3 1 3 -1 -2 -3 -3输出 #1复制
2说明/提示
对于 30% 的数据,N,M≤100,P,Q≤200;
对于 80% 的数据,N,M≤4×10^3,P,Q≤10^4;
对于 100% 的数据,N,M≤10^4,P,Q≤2×10^4。
题解:用两个数组分别存A、B两公司的关系情况,即每个数组的元素都存着它们的根,需注意的是B公司输入的X,Y都是负数需要给他们添个负号变成正的。如果根相同那么就意味着他们都是朋友,最后只需判断A公司和小明根相同的人数还有B公司与小红根相同的人数,最后输出两个队人数中较少的即可(因为男女一对一,最多只有少的人数那么多对情侣)。
#include <stdio.h>
int find(int a[10010],int t)
{if(a[t]==t)
return t;
return a[t]=find(a,a[t]);
}
main()
{int n,m,p,q,x,y,a[10010],b[10010],roota,rootb,cnt1=0,cnt2=0;
scanf("%d%d%d%d",&n,&m,&p,&q);
for(int i=1;i<=n;i++) //先将A公司每个人根赋为自己
a[i]=i;
for(int i=1;i<=m;i++)//将B公司每个人根赋为自己
b[i]=i;
while(p--)
{scanf("%d%d",&x,&y);
a[find(a,y)]=find(a,x);//将y的根赋为a的根
}
while(q--)
{scanf("%d%d",&x,&y);
b[find(b,-y)]=find(b,-x);//数组下标不能为负数,所以在x,y前面添个负号
}
roota=find(a,1);//A公司小明的根
rootb=find(b,1);//B公司小红的根
for(int i=1;i<=n;i++)
if(find(a,a[i])==roota) cnt1++; //和小明同根的人数
for(int i=1;i<=m;i++)
if(find(b,b[i])==rootb) cnt2++; //和小红同根的人数
printf("%d",cnt1<cnt2?cnt1:cnt2);//男女配对只能取较少一方的人数
}
6、P1455 搭配购买
题目描述
明天就是母亲节了,电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢?听说在某个网站上有卖云朵的,小朋友们决定一同前往去看看这种神奇的商品,这个店里有 n 朵云,云朵已经被老板编号为 1,2,3,...,n,并且每朵云都有一个价值,但是商店的老板是个很奇怪的人,他会告诉你一些云朵要搭配起来买才卖,也就是说买一朵云则与这朵云有搭配的云都要买,电脑组的你觉得这礼物实在是太新奇了,但是你的钱是有限的,所以你肯定是想用现有的钱买到尽量多价值的云。
输入格式
第一行输入三个整数,n,m,w,表示有 n 朵云,m 个搭配和你现有的钱的数目。
第二行至 n+1 行,每行有两个整数,ci,di,表示第 i 朵云的价钱和价值。
第 n+2 至 n+1+m 行 ,每行有两个整数ui,vi。表示买第ui 朵云就必须买第 vi 朵云,同理,如果买第 vi 朵就必须买第 ui 朵。
输出格式
一行,表示可以获得的最大价值。
输入输出样例
输入 #1复制
5 3 10 3 10 3 10 3 10 5 100 10 1 1 3 3 2 4 2输出 #1复制
1说明/提示
- 对于 30% 的数据,满足 1≤n≤100;
- 对于 50% 的数据,满足 1≤n,w≤10^3,1≤m≤100;
- 对于 100% 的数据,满足 1≤n≤10^4,0≤m≤5×10^3。
题解:首先将搭配购买的云朵合并起来,即先将每朵云朵的根赋为自己,然后每输入两个需要搭配购买的就将前者的根存为后者的根,最后根相同的云朵就是必须要一起搭配购买的(跟之前的并查集实现操作都一样)。把需要搭配购买的价钱和价值都加起来看做一个整体,并计算合并有多少个集合,最后集合的个数就是物品的个数,自己有多少钱就相当于背包容量,转换为经典的01背包解决即可。
#include <bits/stdc++.h>
using namespace std;
int cost[10010],value[10010],a[10010];//分别是云朵价钱,价值 ,并和集数组
int tcost[10010],tvalue[10010];//搭配购买的总价钱和总价值
int dp[10010];
int find(int t) //经典找根
{ if(a[t]==t)
return t;
return a[t]=find(a[t]);
}
main()
{int n,m,w,k=1;
scanf("%d%d%d",&n,&m,&w);
for(int i=1;i<=n;i++) //先将每个云朵的根赋为自己
a[i]=i;
for(int i=1;i<=n;i++)
scanf("%d%d",&cost[i],&value[i]);
while(m--)
{int x,y;
scanf("%d%d",&x,&y);
a[find(x)]=find(y); //将x的根存为y的根
}
for(int i=1;i<=n;i++)
{tcost[find(a[i])]+=cost[i]; //用桶排将同一个根的价钱加起来(即搭配在一起的云朵)
tvalue[find(a[i])]+=value[i];//价值相加
}
for(int i=1;i<=n;i++) //搭配在一起后总个数肯定会不多于原个数,将合并在一起之后多余的0清空
{if(tcost[i]!=0)
{tcost[k]=tcost[i];
tvalue[k++]=tvalue[i];
}
}
k--;//统计搭配在一起后的数量 (多少个不同的根)
for(int i=1;i<=k;i++)
for(int j=w;j>=tcost[i];j--)
dp[j]=max(dp[j],dp[j-tcost[i]]+tvalue[i]);
printf("%d",dp[w]);
}
7、P1111 修复公路
题目背景
A地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。
题目描述
给出A地区的村庄数N,和公路数M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)
输入格式
第1行两个正整数N,M
下面M行,每行3个正整数x, y, t,告诉你这条公路连着x,y两个村庄,在时间t时能修复完成这条公路。
输出格式
如果全部公路修复完毕仍然存在两个村庄无法通车,则输出-1,否则输出最早什么时候任意两个村庄能够通车。
输入输出样例
输入 #1复制
4 4 1 2 6 1 3 4 1 4 5 4 2 3输出 #1复制
5说明/提示
N≤1000,M≤100000
x≤N,y≤N,t≤100000
题解:我们要求任意村庄通车的最短时间,我们就可以转换思路,按每条路通车的时间从小到大开始就两个村庄连同,并且每连同两个村庄就判断所有的村庄是否为同一个根(即在同一个并查集内),如果都为同一个根就代表所有的村庄在这个时间点已经全部连接,那么自然现在时刻所有村庄都能通车了,因为是按时间从小到大开始的,所有这个时刻就是最短时间,输出该时间并且结束循环即可。如果所有路的修完,所有村庄的根还不是同一个就证明这些不同根的村庄就永远无法通车,输出-1即可。
#include <bits/stdc++.h>
using namespace std;
int a[1010];
struct road
{int x;
int y;
int t;
}r[100010];
bool com(struct road a,struct road b)
{ return a.t<b.t;
}
int find(int t)
{ if(a[t]==t)
return t;
return a[t]=find(a[t]);//压缩路径
}
main()
{int n,m,cnt;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) //初始化根为自己
a[i]=i;
for(int i=1;i<=m;i++)
scanf("%d%d%d",&r[i].x,&r[i].y,&r[i].t);
sort(r+1,r+m+1,com); //按时间排序
for(int i=1;i<=m;i++) //按修路时间从小到大开始合并,当集合只有一个就证明所有村庄都已连同即可输出并结束
{cnt=0;
a[find(r[i].x)]=find(r[i].y);
for(int i=1;i<=n;i++)
if(a[i]==i) cnt++;
if(cnt==1) //集合只有一个
{printf("%d",r[i].t);
return 0;
}
}
printf("-1");//所有路都修好了还是不止一个集合,就证明有村庄无法到达
}
8、P4913 【深基16.例3】二叉树深度
题目描述
给出每个节点的两个儿子节点,建立一棵二叉树(根节点为 1),如果是叶子节点,则输入
0
。建好树后希望知道这棵二叉树的深度。二叉树的深度是指从根节点到叶子结点时,最多经过了几层。最多有 10^6 个结点。
输入格式
无
输出格式
无
输入输出样例
输入 #1复制
7 2 7 3 6 4 5 0 0 0 0 0 0 0 0输出 #1复制
4
题解:用一个包含左节点和又节点的结构体数组存每个节点的左右节点值,数组的下标即是他的父节点,这样就能建立父节点与左右子节点的关系。建立完树后从根节点开始向左右子树开始深搜,当搜索到了叶节点的位置就更新当前深度,最终便可求出根到叶节点最多经过几层。
#include <stdio.h>
#define N 1000010
int dep;
struct node
{int l,r;
}tree[N];
void dfs(int t,int step)
{if(t==0)//到叶节点
{if(step>dep) dep=step-1;
return;
}
dfs(tree[t].l,step+1);//左子树
dfs(tree[t].r,step+1);//右子树
}
main()
{int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) //下标即是它们的父节点
scanf("%d%d",&tree[i].l,&tree[i].r);
dfs(1,1);//从根开始遍历,当前深度为1
printf("%d",dep);
}
9、P1827 [USACO3.4]美国血统 American Heritage
题目描述
农夫约翰非常认真地对待他的奶牛们的血统。然而他不是一个真正优秀的记帐员。他把他的奶牛 们的家谱作成二叉树,并且把二叉树以更线性的“树的中序遍历”和“树的前序遍历”的符号加以记录而 不是用图形的方法。
你的任务是在被给予奶牛家谱的“树中序遍历”和“树前序遍历”的符号后,创建奶牛家谱的“树的 后序遍历”的符号。每一头奶牛的姓名被译为一个唯一的字母。(你可能已经知道你可以在知道树的两 种遍历以后可以经常地重建这棵树。)显然,这里的树不会有多于 26 个的顶点。 这是在样例输入和 样例输出中的树的图形表达方式:
C / \ / \ B G / \ / A D H / \ E F
树的中序遍历是按照左子树,根,右子树的顺序访问节点。
树的前序遍历是按照根,左子树,右子树的顺序访问节点。
树的后序遍历是按照左子树,右子树,根的顺序访问节点。
输入格式
第一行: 树的中序遍历
第二行: 同样的树的前序遍历
输出格式
单独的一行表示该树的后序遍历。
输入输出样例
输入 #1复制
ABEDFCHG CBADEFGH输出 #1复制
AEFDBHGC说明/提示
题目翻译来自NOCOW。
USACO Training Section 3.4
题解:先序遍历的顺序为根、左、右,中序遍历的顺序为左、根、右,所以先序遍历的第一个既是树的根,然后到中序遍历中找到与根相等的元素,那么它的左边的元素即为树的左根,右边的元素即是右根,接着通过递归函数一直细分左右根(传先序和中序的左根下标范围和右根范围)。因为后序遍历的顺序为左、右、根,所以每次细分都是先左根再右根最后再输出根元素,即可通过递归完成后序遍历的输出。
#include <stdio.h>
#include <string.h>
char in[30],pre[30];
void cut(int infir,int inlas,int pfir,int plas)
{if(infir>inlas||pfir>plas) //无法再划分左右子树
return;
for(int i=infir;i<=inlas;i++)
if(in[i]==pre[pfir])//在中序遍历中找到根的位置
{cut(infir,i-1,pfir+1,pfir+i-infir); //中序和先序遍历的左子树范围
cut(i+1,inlas,pfir+i-infir+1,plas-infir+inlas);//中序和先序遍历的又子树范围
printf("%c",pre[pfir]);//输出根
return;
}
}
main()
{scanf("%s",in);
scanf("%s",pre);
cut(0,strlen(in)-1,0,strlen(pre)-1);
return 0;
}
10、P1030 [NOIP2001 普及组] 求先序排列
题目描述
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,长度≤8)。
输入格式
2行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。
输出格式
1行,表示一棵二叉树的先序。
输入输出样例
输入 #1复制
BADC BDCA输出 #1复制
ABCD说明/提示
【题目来源】
NOIP 2001 普及组第三题
题解:后序遍历的顺序为左、右、根,中序遍历的顺序为左、根、右,所以后序遍历的最后一个既是树的根,然后到中序遍历中找到与根相等的元素,那么它的左边的元素即为树的左根,右边的元素即是右根,接着通过递归函数一直细分左右根(传后序和中序的左根下标范围和右根范围)。因为先序遍历的顺序为根、左、右,所以每次细分都是先输出根元素再左根最后再右根,即可通过递归完成先序遍历的输出。
#include <stdio.h>
#include <string.h>
char in[30],post[30];
void cut(int infir,int inlas,int pfir,int plas)
{if(infir>inlas||pfir>plas) //无法再划分左右子树
return;
for(int i=infir;i<=inlas;i++)
if(in[i]==post[plas])//在中序遍历中找到根的位置
{printf("%c",post[plas]);//输出根
cut(infir,i-1,pfir,pfir+i-infir-1); //中序和先序遍历的左子树范围
cut(i+1,inlas,pfir+i-infir,plas-1);//中序和先序遍历的又子树范围
return;
}
}
main()
{scanf("%s",in);
scanf("%s",post);
cut(0,strlen(in)-1,0,strlen(post)-1);
return 0;
}
11、P1229 遍历问题
题目描述
我们都很熟悉二叉树的前序、中序、后序遍历,在数据结构中常提出这样的问题:已知一棵二叉树的前序和中序遍历,求它的后序遍历,相应的,已知一棵二叉树的后序遍历和中序遍历序列你也能求出它的前序遍历。然而给定一棵二叉树的前序和后序遍历,你却不能确定其中序遍历序列,考虑如下图中的几棵二叉树:
所有这些二叉树都有着相同的前序遍历和后序遍历,但中序遍历却不相同。
输入格式
输A数据共两行,第一行表示该二叉树的前序遍历结果s1,第二行表示该二叉树的后序遍历结果s2。
输出格式
输出可能的中序遍历序列的总数,结果不超过长整型数。
输入输出样例
输入 #1复制
abc cba输出 #1复制
4说明/提示
无提示
题解:先序遍历为根、左、右,后序遍历为左、右、根,对于有两个子儿子的节点显然是不能组成多个中序遍历的,而如果一个节点只有一个儿子,那么这个儿子不管是左儿子还是右儿子,都不影响先序和后序的顺序,只会影响中序遍历的顺序,那样总的中序遍历结果的个数就需要×2。所以我们本题只需要找有多少个节点是只有一个儿子的,而根据先序遍历和后序遍历的顺序,我们可以在先序遍历和后序遍历中找到相同的元素位置,如果先序遍历的后一个元素等于后序遍历的前一个元素,那么该节点就是只有一个儿子的节点。
#include <stdio.h>
main()
{char pre[30],post[30];
int cnt=1;
scanf("%s",pre);
scanf("%s",post);
for(int i=0;pre[i];i++)
for(int j=0;post[j];j++)
if(pre[i]==post[j]&&pre[i+1]==post[j-1]&&pre[i+1]&&post[j-1])
cnt*=2;
printf("%d",cnt);
}
12、P2835 刻录光盘
题目描述
在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
题解:本题用的Floyd,如果i能把磁盘传给j,那么i也能把磁盘传给j要传给的人。所以本题我们需要找有几个i能直接或间接传光盘给j的团体,每个团体需要1个光盘。我们需要用到一个一维数组存他们的根(即谁最终能传给另一个人),和一个二维数组存他们愿给谁传光盘的关系。先初始化n个人都愿意传给谁,然后用一个三层循环建立i通过直接或间接能传给j的关系,最后再把j的父亲赋为i。最后计算有多少个父亲是自己的即可。
#include <stdio.h>
int fa[210],map[210][210];//fa存父亲,map[i][j] 表示i愿意借给j
main()
{int n,cnt=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)//初始化父亲为自己
fa[i]=i;
for(int i=1;i<=n;i++)
{int m;
while(scanf("%d",&m)&&m)
map[i][m]=1;//i愿意借给m(单指向)
}
for(int k=1;k<=n;k++) //经典Floyd
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(map[i][k]&&map[k][j]) //判断i是否能通过其他人(k)传递光盘给j
map[i][j]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(map[i][j])//如果i能传光盘给j(直接传或间接传)
fa[j]=fa[i];//j的父亲赋为i
for(int i=1;i<=n;i++)
if(fa[i]==i)//父亲为自己
cnt++;
printf("%d",cnt);
}