Week 8作业

A - 区间选点 II

给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点
使用差分约束系统的解法解决这道题

输入

输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。

输出

输出一个整数表示最少选取的点的个数

样例输入

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

样例输出

6

思路

综述

这道题考到差分约束系统;
差分约束系统共需要四步:
在这里插入图片描述
1、找到关系xi<=xj+k
2、建图;
3、找到源点S
4、确定求最短路还是最长路

不等式组转换

在这里插入图片描述

建图

如下图所示:
在这里插入图片描述

最长路最短路问题

在这里插入图片描述

过程

spfa过程和图论相同,差分约束关键在建图和最短和最长路的选择上面;
在这里插入图片描述

	for (int i = 0; i < n; i++) {
		cin >> a >> b >> c;
		add(a, b + 1, c);
	}
	for (int i = 0; i <= 50005; i++) {
		add(i, i + 1, 0);
		add(i + 1, i, -1);
	}

总结

若是x[b]>=x[a]+c 则是求最小值,最长路
若是x[b]<=x[a]+c 则是求最大值,最短路

代码

上文有详细注释

#include <iostream>
#include <queue>
#define inf 1000000
//#include <cstdlib>
#include <cstring> 
using namespace std; 
int n;
const int maxm=1e6;

struct Edge{
	int v,w,nxt;
}e[maxm];

int tot=0;
const int maxn = 5e4+50;
int head[maxn];
void init(){
	for(int i=0;i<5e4+50;i++){
		head[i]=-1;
	}
}

int ans=-inf;
int dis[maxn]; 
int vis[maxn];

void spfa(int s){
	queue<int> qq;
	memset(dis,-inf,sizeof(dis));
	memset(vis,0,sizeof(vis));
	qq.push(s);
	while(qq.size()){
		int u = qq.front();
		qq.pop();
		dis[s]=0;
		vis[u]=0;
		for(int i=head[u];i!=-1;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(dis[v]<dis[u]+w){
				dis[v] = dis[u]+w;
				if(!vis[v])qq.push(v),vis[v]=1;
				if(ans<dis[v]){
					ans = dis[v];
				}
			}
		}
	}
}

void add(int u,int v,int w){
	e[tot].v = v;
	e[tot].w = w;
	e[tot].nxt = head[u];
	head[u] = tot;
	tot++;
}

int main(){
	init();
	cin>>n;
	int a,b,c;
	for(int i=0;i<n;i++){
		cin>>a>>b>>c;
		add(a,b+1,c);
	}
	for(int i=0;i<=50005;i++){
		add(i,i+1,0);
		add(i+1,i,-1);
	}
	spfa(0);
	cout<<ans<<endl;
}

B - 猫猫向前冲

众所周知, TT 是一位重度爱猫人士,他有一只神奇的魔法猫。
有一天,TT 在 B 站上观看猫猫的比赛。一共有 N 只猫猫,编号依次为1,2,3,…,N进行比赛。比赛结束后,Up 主会为所有的猫猫从前到后依次排名并发放爱吃的小鱼干。不幸的是,此时 TT 的电子设备遭到了宇宙射线的降智打击,一下子都连不上网了,自然也看不到最后的颁奖典礼。
不幸中的万幸,TT 的魔法猫将每场比赛的结果都记录了下来,现在他想编程序确定字典序最小的名次序列,请你帮帮他。

输入

输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示猫猫的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即编号为 P1 的猫猫赢了编号为 P2 的猫猫。

输出

给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。

样例输入

4 3
1 2
2 3
4 3

样例输出

1 2 4 3

思路

综述

这是一个拓扑排序的问题
思路如下:
1、记录所有节点的入度;
2、将入度为0的所有节点入大根堆(加负号成为小根堆)
3、取堆顶的元素,将其所有的出边删除,更新入度数组,进入步骤二

过程
Step1:输入

普通的图的存储,采用前向星存储;

Step2:kahn算法
Step2.1 0度节点如堆
	for (int i = 1; i <= N; i++) {
		if (dee[i] == 0)qq.push(-i);
	}
Step2.2 取堆顶元素–>操作

输出

		if (flag) {
			cout << u;
			flag = 0;
		}
		else {
			cout << " " << u;
		}

更新

		for (int i = head[u]; i != -1; i = e[i].nxt) {
			int v = e[i].v;
			dee[v]--;
			if (dee[v] == 0)qq.push(-v);
		}

总结

1、STL中默认为大根堆—>加负号入堆可以置为小根堆
2、多组数据之间注意初始化问题

代码

上文有详细注释

#include <iostream>
#include <queue>
using namespace std;

int N,M;
const int maxn=550;

struct Edge{
	int v,nxt;
}e[maxn];
int head[maxn];
int dee[maxn];
int tot;

void init(){
	for(int i=0;i<maxn;i++){
		head[i]=-1;
		dee[i]=0;
	}
	tot=0;
}

void add(int u,int v){
	e[tot].v = v;
	e[tot].nxt = head[u];
	head[u] = tot;
	tot++;
}

void kanh(){
	
	priority_queue<int> qq;
	
	for(int i=1;i<=N;i++){
		if(dee[i]==0)qq.push(-i);
	}
	int flag=1;
	
	while(qq.size()){
		int u = -qq.top();
		qq.pop();
		if(flag){
			cout<<u;
			flag=0;
		}else{
			cout<<" "<<u;
		}
		for(int i=head[u];i!=-1;i=e[i].nxt){
			int v=e[i].v;
			dee[v]--;
			if(dee[v]==0)qq.push(-v);
		}
	}
	cout<<endl;
}

int main(){
	while(cin>>N){
		cin>>M;
		init();
		int a,b;
		for(int i=0;i<M;i++){
			cin>>a>>b;
			add(a,b);
			dee[b]++;
		}
		kanh();
	}
}

C - 班长竞选

大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?

输入

本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。

输出

对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!

样例输入

2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2

样例输出

Case 1: 2
0 1
Case 2: 2
0 1 2

思路

综述

这道题考察SCC,强连通分量

DFS序

前序序列:dfs的遍历顺序
后序序列:dfs遍历完成的次序
逆后序序列:后序序列的逆序

Kosaraju

1、第一遍dfs确定dfs的逆后序序列
2、在反图中按照逆后序序列进行遍历,每次起点开始的连通点为一个SCC

过程
变量

G1:存原图
G2:存反图
G3:存缩点后的图

函数
dfs1

用于确定逆后序

dfs2

用于kosaraju算法确定SCC

dfs3

用于缩点

kosaraju

用于确定SCC强连通分支

topoint

缩点函数

output 输出

总结

代码

上文有详细注释

#include<iostream>
#include<queue>
#include<vector>
#include <string.h>
#include <algorithm>
using namespace std;

struct point 
{
	vector<int> v;
}Point[5005];


vector<int> G1[5005], G2[5005], G3[5005];
int n, sccnum[5005], dfn[5005], vis[5005], dcnt, scnt, indeg[5005];
int tag = 1;
int T;
int m;
void dfs1(int u)
{
	vis[u] = 1;
	for(int i = 0 ; i < G1[u].size() ; i++)
		if(!vis[G1[u][i]])
			dfs1(G1[u][i]);
	dfn[dcnt++] = u;
}

void dfs2(int u)
{
	sccnum[u] = scnt;
	for(int i = 0 ; i < G2[u].size() ; i++)
		if(!sccnum[G2[u][i]])
			dfs2(G2[u][i]);
}

int dfs3(int u)
{
	vis[u] = 1;
	int v = Point[u].v.size();
	for(int i = 0 ; i < G3[u].size() ; i++)
		if(!vis[G3[u][i]])
			v += dfs3(G3[u][i]);
	return v;
}

void kosaraju()
{
	dcnt = scnt = 0;
	memset(sccnum, 0, sizeof sccnum);
	memset(vis, 0, sizeof vis);
	for(int i = 0 ; i < n ; i++)
		if(!vis[i])
			dfs1(i);
	for(int i = n - 1 ; i >= 0; i--)
		if(!sccnum[dfn[i]])
		{
			++scnt;
			dfs2(dfn[i]);
		}
}

void topoint(){
	for(int i =1 ; i <= scnt ; i++)
	{
		G3[i].clear();
		Point[i].v.clear();
		indeg[i] = 0;
	}		 
	for(int i = 0 ; i < n ; i++)
	{
		Point[sccnum[i]].v.push_back(i);
		for(int j = 0 ; j < G1[i].size() ; j++)
		{
			
			if(sccnum[i] == sccnum[G1[i][j]])
				continue;
			else 
			{
				G3[sccnum[G1[i][j]]].push_back(sccnum[i]);
				indeg[sccnum[i]]++;
			}
		}
	}
}
void SET(){
	for(int i = 1 ; i <= scnt ; i++)
	{
		sort(G3[i].begin(), G3[i].end());
		G3[i].erase(unique(G3[i].begin(), G3[i].end()), G3[i].end());
	}
}

void output(){
		int sum[scnt + 1] = {0};
		int ans[5005] = {0};
		
		int Max = 0;
		for(int i = 1 ; i <= scnt ; i++)
		{
			if(!indeg[i])
			{
				for(int j = 1 ; j <= scnt ; j++)
					vis[j] = 0;
				sum[i] = dfs3(i) - 1;
				if(Max < sum[i])	
					Max = sum[i];
			}
		}
		
		int num = 0;
		for(int i = 1 ; i <= scnt ; i++)
		{
			if(sum[i] == Max)
			{
				for(int j = 0 ; j < Point[i].v.size() ; j++)
				{
					ans[num] = Point[i].v[j];
					num++;
				}
			}
		}
		
		sort(ans, ans + num); 
		cout << "Case " << tag << ": " << Max << endl;
		tag++;
		for(int i = 1 ; i < num ; i++)
			printf("%d ",ans[i-1]); 
		printf("%d\n",ans[num - 1]); 
}

int main(){
	cin >> T;
	for(int qw=0;qw<T;qw++)
	{
		
		scanf("%d%d",&n,&m);
		//初始化 
		for(int i = 0 ; i < n ; i++)
		{
			G1[i].clear();
			G2[i].clear();
		}
		for(int wq=0;wq<m;wq++)
		{
			int a, b;
			scanf("%d%d",&a,&b);
			G1[a].push_back(b);
			G2[b].push_back(a);
		}
		kosaraju();
		topoint();
		SET();
		output();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值