题解报告:contest 1094

Contest 1094

第一题:1543 -- 分组

看不到没关系:

Description

信息学竞赛班的班主任 \text{Smart}Smart 是一位心思很缜密的老师,他在接手信息学竞赛班一个学期以后,想调查一下班上同学之间相互交流的情况,以便及时了解班级动态。
信息学竞赛班一共有 nn 个同学。这 nn 位同学每一个人都有一个小花名册,名册里面写着他所愿意交流的人的名字。比如说在 AA 的人名单里写了 BB,那么表示 AA 愿意与 BB 交流;但是 BB 的名单里不见得有 AA,也就是说 BB 不见得想与 AA 交流。但是如果 AA 愿意与 BB 交流,BB 愿意与 CC 交流,那么 AA 一定愿意与 CC 交流。也就是说交流有传递性。
班主任 \text{Smart}Smart 觉得需要将这 nn 个人分为 mm 组,要求每一组的任何一人都愿意与组内其他人交流。并求出一种方案以确定 mm 的最小值是多少。

Input

第一行一个整数 nn(1≤n≤2001≤n≤200)。
接下来 nn 行,第 (i+1)(i+1) 行表示编号为 ii 的人的小花名册名单,名单以 00 结束。
注意:自己的名单里面不会有自己的名字。

Output

一行,一个整数 mm 。

Sample Input

5
2 0
1 0
4 5 0
3 0
4 0

Sample Output

2

Sample Explanation

样例中 11 和 22 分在一组,33、44 和 55 分在一组。

Hint

100\%100%的数据:1≤n≤2001≤n≤200。

Source

编程提高班

一.题目大意

今天的比赛是FLOYD专题,所以先用两句话概括一下FLOYd

好那么首先floyd是o(n^3)的算法,也就是说今天三道题(后两道没搞懂。。。)的数据范围都在300以内

然后FLOYD算法本身就是三层循环:k阶段,i,j状态,所以k放在第一层循环,而i,j的顺序不重要

方程会分成两种不同的用途

        1.求最短路径(第三题):f[i][j]=max(f[i][k]+f[k][j],f[i][j])

        2.求闭包(好家伙,闭包是个啥),额么么,闭包就是说a->b,b->c 那么一定有a->c也可以先粗略的认为就是强联通分量之后再变化一下

那么第一道题,其实就是强联通分量

二.算法思路

就是让你把尽量多的同学分成一组,然后求有多少组,求强联通分量少不了FLOYD算法(当然塔杨算法更好)

现在,我们使用FLOYD求出了任意两点是否连通,然后呢

我们就按照算出来的数组进行模拟,把那些可以互达的点合并到一组,统计最终的组数就行了

三.代码实现

#include<bits/stdc++.h>
using namespace std;

const int N=205;

int f[N][N];
int a[N][N],cnt[N],n;

int read(){
	int a=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))a=a*10+c-'0',c=getchar();
	return a; 
}

int ans;

int main(){
	n=read();
	for(int i=1;i<=n;i++){
		int tmp=read();
		while(tmp){
			f[i][tmp]=1;
			tmp=read();
		}
		f[i][i]=1;
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				f[i][j]=f[i][j] || (f[i][k] && f[k][j]);
			}
		}
	} 
	int vis[N]={0};
	for(int i=1;i<=n;i++){
		if(vis[i])continue;
		ans++;
		for(int j=1;j<=n;j++){
			if(f[i][j])vis[j]=1;
		}
	}
	printf("%d\n",ans); 
	return 0;
}

 第二题:1562 -- 舞会邀请

看不到没关系:

1562 -- 舞会邀请

Description

\text{Smart}Smart 是一位颇有成就的艺术家,他因油画作品《我爱北京天安门》闻名于世界。现在,他为了报答帮助他的同行们,准备开一个舞会。
\text{Smart}Smart 准备邀请 nn 个已经确定的人,可是问题来了:
这 nn 个人每一个人都有一个小花名册,名册里面写着他能够通知到的人的名字。比如说在 AA 的人名单里写了 BB,那么表示 AA 能够通知到 BB;但是 BB 的名单里不见得有 AA,也就是说 BB 不见得能够通知到AA。
\text{Smart}Smart 觉得需要确定自己需要通知到多少个人(人数mm),能够实际将所有 nn 个人都通知到。并求出一种方案以确定 mm 的最小值是多少。
注意:自己的名单里面不会有自己的名字。

Input

第一行一个数 nn。
接下来 nn 行,第 i+1i+1 行表示编号为 ii 的人的小花名册名单,名单以 00 结束。

Output

一个整数,即 mm 的值。

Sample Input

5
5 2 0
5 3 0
5 4 0
5 1 0
2 0

Sample Output

1

Hint

100\%100%的数据:1≤n≤2001≤n≤200。

Source

编程提高班

一.题目大意

这道题比上道题就少一个约束(如果代码原封不动提交能够那50PTS):只需要单向告知,不需要互达

二.算法思路

如果还是用FLOYD算法计算强联通分量,也可以,然后我们可以认为,一个强连通分量在图中的地位就是一个宏观意义上的点

那么就可以缩点(亲测可以过,但是要76行代码)

然后就想一想可不不可以偷个懒(主要是缩点我不会2333)

那实际上我们可以使用一种巧妙地算法:就是设f[i]表示通知f[i]的人,那么如果i、j连通,我就可以让i通知J

那么就是f[j]=f[i],而初始化就是f[i]=i,也就表示是自己通知自己,也就是凭空想出来(必须),那么所有必须瞎猜的人就是我必须要通知的人

三.代码实现

#include<bits/stdc++.h>
using namespace std;

const int N=205;

int f[N],a[N][N],n,x,y;

inline int read(){
	int a=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))a=a*10+c-'0',c=getchar();
	return a;
}

int main(){
	n=read();
	for(int i=1;i<=n;i++){
		int tmp=read();
		while(tmp!=0){
			a[i][tmp]=1;
			tmp=read();
		}
		f[i]=i;
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(i==j || j==k || i==k)continue;
				a[i][j]|=a[i][k] && a[k][j];
			}
		}
	} 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i][j]==1){
				//可以互达
				f[j]=f[i];//就让 i 告诉 j  
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)ans+=(f[i]==i);
	printf("%d\n",ans);
	return 0;
}

四.细节聚焦

就是这种思路特别想并查集

那么我们可不可以往并查集上凑一下,把他改成并查集的思路

实际上就是并查集!

我们所有能够收到一个人通知(我们称消息大发源地为通知者)的人,都是一个集合,而队长就是那个无法得到通知而编造“谣言”的人

然后我去告诉这些造谣的人,说你们是对的,然后就是最少的通知人数

所以第一题是求有向图连通+强连通分量模拟

       第二题是求有向图连通+并查集求集合数

第三题:1546 -- 最短路上的统计

看不见没关系:

1546 -- 最短路上的统计

Description

一个无向图上,没有自环,所有边的权值均为 11,对于一个点对 (a,ba,b),我们要把所有 aa 与 bb 之间所有最短路上的点的总个数输出。

Input

第一行两个整数 n,mn,m,表示 nn 个点,mm 条边;
接下来 mm 行,每行两个数 a,ba,b,表示 a,ba,b 之间有条边;
接下来一个整数 pp,表示问题的个数;
接下来 pp 行,每行两个数 a,ba,b,表示询问 a,ba,b。

Output

对于每个询问,输出一行包含一个数cc,表示 a,ba,b 之间最短路上点的总个数。

Sample Input

5 6
1 2
1 3
2 3
2 4
3 5
4 5
3
2 5
5 1
2 4

Sample Output

4
3
2

Hint

100\%100% 的数据:N≤100,p≤5000N≤100,p≤5000。

Source

编程提高班

一.题目大意

这个题就是让你求任意两点之间最短路径的所有情况画出来之后会覆盖多少个点

二.算法思路

那么首先求最短路径,而且是多元的,那就是FLOYD一定的

求完之后,我们思考某一个点为什么有资格被统计?

就是他一定是最短路径中某一种的某一个转折点!

转换成计算机语言就是f[i][k]+f[k][j]==f[i][j]的所有k点

然后就是把FLOYD算一遍然后读入a,b并且遍历1~n并且同时寻找所有的k统计下来

三.代码实现

#include<bits/stdc++.h>
using namespace std;

const int N=105;

int n,m,a,b,d[N][N],p,ans;

inline int read(){
	int a=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))a=a*10+c-'0',c=getchar();
	return a;
}

int main(){
	n=read(),m=read();
	memset(d,0x3f,sizeof d);
	for(int i=1;i<=m;i++){
		a=read(),b=read();
		d[a][b]=1;
		d[b][a]=1;
	} 
	for(int i=1;i<=n;i++)d[i][i]=0;
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
			}
		}
	}
	p=read();
	for(int i=1;i<=p;i++){
		a=read(),b=read(); 
		for(int k=1;k<=n;k++){
			if(d[a][k]+d[k][b]==d[a][b])ans++;
		}
		printf("%d\n",ans);
		ans=0;
	}
	return 0;
}

四.宏观思路

那么这道题为什么能想到这个

首先这道题实际上是变相的要求你计算出最短路径的具体参数而非单单的长度,你只有知道了这些才能回答问题

那么多元最短路径在不止一条的时候怎么全部“输出” ?

这就好逼问你,最长不下降子序列/最长公共子序列到第长啥样一个道理

DP倒推得到路径

这是一个很重要的知识点

知识这个地方不止一条路径,所以没法追根溯源(计算机同时只能执行一个,无法真正达到层次遍历)

那么就寻找所有的中间状态

那么这个地方就需要证明一个东西

那就是为什么有且只有f[i][k]+f[k][j]==f[i][j]的时候k是i到j的垫脚石,

问题一:对于为什么没有其他方面是的问题

证明:如果有不是这样条件是的话,那么最短路径就会变短,那么反之最短路径又变成了f[i][k']+f[k'][j]==f[i][j]但如果这样的话在原先的FLOYD算法中就已经应该更新过了

问题二:对于为什么满足则一定是的问题

证明:由于是进行了n轮求最短路松弛的操作猜得到了状态数组,那么最后一次的任何一个f[i][k]或者f[k][j]都一定参与过f[i][j]的计算,也就是说如果最后一次的F[i][k]+f[k][j]==f[i][j],那么f[i][k]+f[k][j]一定松弛过f[i][j],那么就一定是最短路径!

无论对于垫脚石k1还是k2还是路人甲,只要自己作为断点的时候影响到了最优解,就一定是这个嘴有点的一个状态(决策)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值