WEEK 8作业 A/D-区间选点 B-猫猫向前冲 C-班长竞选

A/D-区间选点

题目描述

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

输入输出格式及样例

Input
输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。
Output
输出一个整数表示最少选取的点的个数
Input
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
Output
6

思路

区间选点的问题上次使用的是贪心算法,这次使用差分。
将区间的两个端点抽象为图中两个点,对于区间[a,b],要满足该区间中至少有c个点,按照差分的思想就是,dis[b+1]-dis[a]>=c,对于其他点而要保证dis有意义,要满足0<=dis[i+1]-dis[i]<=1,因为相邻两个点之间的区间最多只有一个点,所以只能是0和1.
由于本题是求最小解,对建好的图求最长路即可得到答案。

实验代码

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 50010;
const int inf = 1000000;
int inq[maxn], dis[maxn], n, Max, Min;
struct Edge {//边
	int v;
	int w;
	int nxt;
}edge[maxn];
int head[maxn], tot;
void init() {
	memset(head, -1, sizeof(head));
	tot = 0;
}
void add(int u, int v, int w) {
	edge[++tot].v = v;
	edge[tot].w = w;
	edge[tot].nxt = head[u];
	head[u] = tot;
}
void spfa(int s) {
	queue<int> q;
	for (int i = 1; i < maxn; i++) {
		inq[i] = 0;
		dis[i] = -inf;
	}
	dis[s] = 0;
	inq[s] = 1;
	q.push(s);
	while (!q.empty()) {
		int u = q.front(); q.pop();
		inq[u] = 0;
		for (int i = head[u]; i != -1; i = edge[i].nxt) {
			int v = edge[i].v;
			int w = edge[i].w;
			if (dis[v] < dis[u] + w) {
				dis[v] = dis[u] + w;
				if (!inq[v]) {
					q.push(v);
					inq[v] = 1;
				}
			}
		}
	}
}
int main(){
	init();
	scanf("%d", &n);
	Max = 0;
	Min = inf; 
	int a, b, c;
	for (int i = 0; i < n; i++){
		scanf("%d%d%d", &a, &b, &c);
		Max = max(Max, b);
		Min = min(Min, a);
		add(a, b + 1, c);
	}
	for (int i = Min; i <= Max; i++){
		add(i, i + 1, 0);
		add(i + 1, i, -1);//补全相邻两个点的权值
	}
	spfa(Min);
	cout << dis[Max + 1] << endl;
	return 0;
}

B-猫猫向前冲

题目描述

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

输入输出格式及样例

Input
输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示猫猫的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即编号为 P1 的猫猫赢了编号为 P2 的猫猫。
Output
给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
Input
4 3
1 2
2 3
4 3
Output
1 2 4 3

思路

不同于上次的floyd算法求胜负关系,本题要求给出排名顺序,对于同级中没有比过的猫按编号小的靠前,也就是求字典序最小的排名序列,对于求排名,肯定是比赛赢的猫排名靠前,对于图来说,就是入度数为零的点排名靠前,所以很明显是使用拓扑排序的算法,将入度数为零的点加入队列,然后不断出队列,遍历队首元素的邻接点,使邻接点度数减一,如果减完度数等于零则加入队列,由于字典序要求最小,所以我们用优先级队列替换队列。
本题最开始出现错误,因为优先级队列没有使用cmp导致使用的是大根堆,而且初始化dis数组有问题,更改了之后就过了。

实验代码

#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
const int maxn = 1e3 + 5;
int cnt[maxn], dis[maxn], n, m;
struct Edge {
	int v;
	//int w;
	int nxt;
}edge[maxn];
int head[maxn], tot;
void init() {
	memset(head, -1, sizeof(head));
	tot = 0;
}
void add(int u, int v) {
	edge[++tot].v = v;
	//edge[tot].w = w;
	edge[tot].nxt = head[u];
	head[u] = tot;
}
void toposort(){
	priority_queue<int,vector<int>, greater<int> > q;
	vector<int> ans;
	for (int i = 1; i <= n; i++)
		if (cnt[i] == 0)
			q.push(i);
	while (!q.empty() ){
		int u = q.top(); q.pop();
		ans.push_back(u);
		for (int i = head[u]; i != -1; i = edge[i].nxt){
			int v = edge[i].v;
			cnt[v]--;
			if (cnt[v] == 0)
				q.push(v);
		}
	}
	if (ans.size() == n){
		cout << ans[0];
		for (int i = 1; i < n; i++)
			cout << " " << ans[i];
		cout << endl;
	}
}
int main(){
	while (cin >> n >> m){
		init();
		memset(cnt, 0, sizeof(cnt));
		int p1, p2;
		for (int i = 0; i < m; i++){
			cin >> p1 >> p2;
			add(p1, p2);
			cnt[p2]++;
		}
		toposort();
	}
	return 0;
}

C-班长竞选

题目描述

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

输入输出格式及样例

Input
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。
Output
对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。
接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!
Input
2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2
Output
Case 1: 2
0 1
Case 2: 2
0 1 2

思路

由于选举也具有传递性,所以本题最开始想的是floyd算法,但是floyd算法复杂度太高,对于本题时间不允许。然后是对于环路的处理,环路里的所有点的票数都至少有该环路的点数减一,环路与环路之间的票数可以单独处理,所以本题将环路优化为一个点,求出优化后的点的票数,最后加上环路自身的票数就可。
对于求环的方法,我们采用的是有向图kosaraju算法求强连通分量,kosaraju算法的思想是,先dfs求出后序序列,然后按照逆后序序列的顺序对反图再进行一次dfs就可以求出强连通分量,求出强连通分量后,我们将强连通分量优化为一个点,对优化后的图求反,对反图中入度数为0的点跑一次dfs,计算他的票数,最后求出票数的最大值。
在这里插入图片描述

实验代码

#include <iostream>
#include <stdio.h>
#include <cstring> 
#include <vector>
using namespace std;
const int maxn = 30010;
struct Edge {//边
	int v;
	//int w;
	int nxt;
}edge1[maxn], edge2[maxn];

int head1[maxn], tot1;//原图链式前向星
void init1() {
	memset(head1, -1, sizeof(head1));
	tot1 = 0;
}
void add1(int u, int v) {
	edge1[++tot1].v = v;
	//edge[tot].w = w;
	edge1[tot1].nxt = head1[u];
	head1[u] = tot1;
}

int head2[maxn], tot2;//反图链式前向星
void init2() {
	memset(head2, -1, sizeof(head2));
	tot2 = 0;
}
void add2(int u, int v) {
	edge2[++tot2].v = v;
	//edge[tot].w = w;
	edge2[tot2].nxt = head2[u];
	head2[u] = tot2;
}
int n, m, dcnt, scnt, vis[maxn], dfn[maxn], c[maxn], sum[maxn], ans[maxn], degree[maxn];
void dfs1(int x) {
	vis[x] = 1;
	for (int i = head1[x]; i != -1; i = edge1[i].nxt) {
		int v = edge1[i].v;
		if (!vis[v])
			dfs1(v);
	}
	dfn[++dcnt] = x;
}
void dfs2(int x) {
	c[x] = scnt;
	sum[scnt]++;
	for (int i = head2[x]; i != -1; i = edge2[i].nxt) {
		int v = edge2[i].v;
		if (!c[v])
			dfs2(v);
	}
}
void kosaraju() {
	dcnt = scnt = 0;
	memset(c, 0, sizeof(c));
	memset(vis, 0, sizeof(vis));
	memset(sum, 0, sizeof(sum));
	for (int i = 0; i < n; i++)
		if (!vis[i]) dfs1(i);
	for (int i = n; i >= 1; i--)
		if (!c[dfn[i]]) {
			++scnt;
			dfs2(dfn[i]);
		}
}
void dfs(int x, int u) {
	vis[x] = 1;
	for (int i = head2[x]; i != -1; i = edge2[i].nxt) {
		int v = edge2[i].v;
		if (!vis[v]) {
			vis[v] = 1;
			ans[u] += sum[v];
			dfs(v, u);
		}
	}
}
int main(){
	int T;
	cin >> T;
	int cnt = 0;
	while (T--){
		cnt++;
		init1(); init2();
		memset(degree, 0, sizeof(degree));
		memset(ans, 0, sizeof(ans));
		cin >> n >> m;
		int a, b;
		for (int i = 0; i < m; i++){
			scanf("%d%d", &a, &b);
			add1(a, b);
			add2(b, a); 
		}
		kosaraju();
		init2();
		for (int i = 0; i < n; i++) {
			for (int j = head1[i]; j != -1; j = edge1[j].nxt) {//将强连通分支化简为一个点
				int v = edge1[j].v;
				//cout << c[i] << " " << c[v] << endl;
				if (c[i] != c[v]) {
					degree[c[i]]++;
					add2(c[v], c[i]);//反图
				}
			}
		}
		int Max = 0;
		//在反图中,求每个强连通分支所能到达的强连通分支的点数和,加上自己强连通分支的点数再减一,就是他的票数
		for (int i = 1; i <= scnt; i++) {
			if (degree[i] == 0) {
				memset(vis, 0, sizeof(vis));//初始化vis数组避免dfs出错
				dfs(i, i); 
				ans[i] += sum[i] - 1;
				//cout << ans[i] << endl;
			}
			if (ans[i] > Max)
				Max = ans[i];
		}
		cout << "Case " << cnt << ": " << Max << endl;
		vector<int> q;
		for (int i = 0; i < n; i++) {
			if (ans[c[i]] == Max)
				q.push_back(i);
		}
		n = q.size(); 
		//cout << n << endl;
		cout << q[0];
		for (int i = 1; i < n; i++)
			cout << " " << q[i];//输出答案
		cout << endl;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值