【Week 8 作业】A - 区间选点 II、B - 猫猫向前冲、C - 班长竞选

A - 区间选点 II

题意:

给定一个数轴上的 n n n 个区间,要求在数轴上选取最少的点使得第 i i i 个区间 [ a i , b i ] [ai, bi] [ai,bi] 里至少有 c i ci ci 个点

使用差分约束系统的解法解决这道题

Input

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

Output

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

Sample Input

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

Sample Output

6

思路做法:

要求不能使用贪心法,需要用差分约束的方法将原题转化为图的最短路或最长路问题。
令sum[i]代表[0, i)区间内的点的个数(点都是整数值),则sum[b+1]-sum[a]表示[a, b]内点的个数。
原限制就可以表示为 s u m [ b + 1 ] − s u m [ a ] > = c sum[b+1]-sum[a]>=c sum[b+1]sum[a]>=c,因为点的个数要尽可能的少,所以式子变化为 s u m [ b + 1 ] > = s u m [ a ] + c sum[b+1]>=sum[a]+c sum[b+1]>=sum[a]+c,其中 s u m [ b + 1 ] sum[b+1] sum[b+1]取最小值即为 s u m [ a ] + c sum[a]+c sum[a]+c,若 b b b是总区间最大值,则 s u m [ b ] sum[b] sum[b]就是答案。
除了以上一系列输入的限制外,系统自带的限制有 0 < = s u m [ i ] − s u m [ i − 1 ] < = 1 0<=sum[i]-sum[i-1]<=1 0<=sum[i]sum[i1]<=1,转化为 s u m [ i ] > = s u m [ i − 1 ] + 0 , s u m [ i − 1 ] > = s u m [ i ] + ( − 1 ) sum[i]>=sum[i-1]+0, sum[i-1]>=sum[i]+(-1) sum[i]>=sum[i1]+0,sum[i1]>=sum[i]+(1),将其作为边放入图中,并从0号结点运行SPFA(判断负环)计算最长路

总结:

当跑最短路或最长路时,要注意inf值得选取,不是任一个很大的值都可以。
另外当[a, b]区间内点的个数用sum[b]-sum[a-1](其中sum[i]表示[0, i]区间内点的个数)表示时,碰到a=0的情况难以处理,因为SPFA算法是从0开始的,所以要改变一下形式。

代码:

#include <stdio.h>
#include <vector>
#include <queue>
using namespace std;

const int N = 5e4+50;
const int inf = 1e8;  // 0x3f3f3f3f
int sum[N];  // b <= 50000  [0,i)区间内的点数 点为整数 
struct Edge{
	int v, w;
	Edge(){}
	Edge(int _v, int _w):v(_v), w(_w){}
};
vector<Edge> E[N];
queue<int> inq;
int dis[N], vis[N], n;

void addEdge(int a, int b, int c){
	E[a].push_back(Edge(b, c));
}

void spfa(int s){
	for(int i = 0; i <= n; ++i){
		dis[i] = -inf; vis[i] = 0;  // 初始化 
	}
	inq.push(s); dis[s] = 0; vis[s] = 1;
	while(!inq.empty()){
		int u = inq.front(); inq.pop(); vis[u] = 0;
		for(int i = 0, len = E[u].size(); i < len; ++i){
			int v = E[u][i].v, w = E[u][i].w;
			if(dis[v] < dis[u] + w){  // 最长路 
				dis[v] = dis[u] + w;
				if(!vis[v]){
					inq.push(v); vis[v] = 1;  // 防止重复 
				}
			}
		}
	}
}

int main(){
	int m, max_b = 0; scanf("%d", &m);  // m个区间 
	for(int i = 1; i <= m; ++i){
		int a, b, c;  //[a,b+1)=[a,b]里至少有c个点 
		scanf("%d%d%d", &a, &b, &c);
		if(b+1 > max_b) max_b = b+1;  //更新 max_b 最大点 
		addEdge(a, b+1, c);  // 防止-1出现 
	}
	n = max_b;
	for(int i = 1; i <= n; ++i){
		addEdge(i, i-1, -1); addEdge(i-1, i, 0);
	}
	spfa(0);
//	for(int i = 0; i <= n; ++i){
//		for(int j = 0; j < E[i].size(); ++j)
//			printf("%d %d %d   ", i, E[i][j].v, E[i][j].w);
//		printf("\n");
//	}
	printf("%d\n", dis[max_b]);
	return 0;
}

B - 猫猫向前冲

题意:

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

Input

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

Output

给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!

其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名

Sample Input

4 3
1 2
2 3
4 3

Sample Output

1 2 4 3

思路做法:

将边用vector存入后跑拓扑排序算法,值得注意的是题目要求输出要编号小的队伍在前,因此用优先级队列,priority_queue为大根堆,因此为了先拿出编号小的队伍,把队伍编号取负号放入队列中,拿出来后再正回来放入答案数组中。

总结:

很基础的拓扑排序算法问题,并且没有任何变化,直接跑代码即可,注意多组输入要初始化。

代码:

#include <stdio.h>
#include <vector>
#include <queue>
using namespace std;

const int N = 5e2+5;
vector<int> E[N];
int n, in[N], ans[N], cnt;
priority_queue<int> q;  // 大根堆 

void toposort(){
	while(!q.empty()) q.pop();  // 初始化 
	for(int i = 1; i <= n; ++i){
		if(in[i] == 0) q.push(-i);  // 取负号 变小根堆 
	}
	cnt = 0;
	while(!q.empty()){
		int u = -q.top(); q.pop();
		ans[cnt++] = u;  // 放到结果数组中
		for(int i = 0, len = E[u].size(); i < len; ++i){
			int v = E[u][i]; --in[v];
			if(in[v] == 0) q.push(-v);
		} 
	}
}

int main(){
	int m;
	while(~scanf("%d%d", &n, &m)){
		for(int i = 1; i <= n; ++i){
			in[i] = 0; E[i].clear();  // 初始化 
		}
		for(int i = 1; i <= m; ++i){
			int p1, p2; scanf("%d%d", &p1, &p2);
			E[p1].push_back(p2);
			in[p2]++;  // 入度加 
		}
		toposort();
		if(cnt == n){
			for(int i = 0; i < cnt-1; ++i)
				printf("%d ", ans[i]);
			printf("%d\n", ans[cnt-1]);
		}
	}
	return 0;
}

C - 班长竞选

题意:

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

Input

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

Output

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

Sample Input

2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2

Sample Output

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

思路做法:

首先在存储的图上先跑一遍dfs(dfs1函数)记录后序计数(用vis1数组记录是否访问过),根据反过来的后序计数(即逆后序)再遍历一遍(dfs2函数)整个图记录下强连通分量的个数(scnt)及每个强连通分量内部节点数(SCC[scnt])由于每个强连通分量内的节点的投票数相同,在反图上将其缩点,并且在缩点后的图中找入度为0的点放入数组中(因为反图的每条边都与原来的边方向相反,因此入度为0说明在原图中出度为0,容易想到得票数最多的一定是出度为0的点),对数组中每个点执行一遍便历(dfs3)计算总投票数(用vis2记录是否访问过),找出最大的来,但注意最大的不一定只有1个,所以用vector记录下所有符合的点,输出总投票数减1(不能算自己投自己的票),并便历所有原图中的点,判断其是否在结果强连通分量中,最后注意输出的格式。

总结:

多组输入必须考虑各个变量的初始化,尤其是代码中需要的变量很多,但是大部分都要先清空再使用。

代码:

#include <stdio.h>
#include <vector>
using namespace std;

const int N = 5e3 + 50;
vector<int> E1[N], E2[N], E3[N], cc;  // E2反图 E3缩点后的图 
int n, vis[N], c[N], dfn[N], SCC[N];
int dcnt, scnt, in[N], vis2[N], sum;

void dfs1(int u){
	vis[u] = 1;
	for(int i = 0, len = E1[u].size(); i < len; ++i){
		int v = E1[u][i];
		if(!vis[v]) dfs1(v);
	}
	dfn[++dcnt] = u;  // 遍历完 记录下来 后序计数 
}

void dfs2(int u){
	c[u] = scnt; SCC[scnt]++;  // 强连通分量内点数 
	for(int i = 0, len = E2[u].size(); i < len; ++i){
		int v = E2[u][i];  // 反图
		if(c[v] == 0) dfs2(v); 
	}
}

void dfs3(int u){
	vis2[u] = 1; sum += SCC[u];
	for(int i = 0, len = E3[u].size(); i < len; ++i){
		int v = E3[u][i];
		if(!vis2[v]) dfs3(v);
	}
}

void Kosaraju(){
	dcnt = 0; scnt = 0;  // dfs序计数,scc计数 
	for(int i = 0; i < n; ++i){
		c[i] = 0; vis[i] = 0;  // 初始化 c: scc编号 
	}
	for(int i = 1; i <= n; ++i) SCC[i] = 0;
	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]);
		} 
	}
}

bool incc(int u){
	for(int i = 0, len = cc.size(); i < len; ++i){
		if(c[u] == cc[i]) return true;
	}
	return false;
}

int main(){
	int T; scanf("%d", &T);
	for(int t = 1; t <= T; ++t){
		int m; scanf("%d%d", &n, &m);
		for(int i = 0; i < n; ++i){  // 编号 0 ~ n-1 
			E1[i].clear(); E2[i].clear(); E3[i].clear();
		}
		for(int i = 1; i <= m; ++i){
			int a, b; scanf("%d%d", &a, &b);
			E1[a].push_back(b); E2[b].push_back(a);
		}
		Kosaraju();  // 确定强连通分量
		for(int i = 1; i <= scnt; ++i) in[i] = 0;  // 初始化 
		for(int i = 0; i < n; ++i){  // 缩点 
			for(int j = 0, len = E2[i].size(); j < len; ++j){
				if(c[i] == c[E2[i][j]]) continue;
				E3[c[i]].push_back(c[E2[i][j]]);
				in[c[E2[i][j]]]++;  // 入度加 
			}
		}
		int ans = 0, num = 0, temp[N];
		for(int i = 1; i <= scnt; ++i){
			if(in[i] == 0) temp[num++] = i;  // 入度为0的强连通分量 
		}
		cc.clear();
		for(int i = 0; i < num; ++i){
			for(int j = 1; j <= scnt; ++j) vis2[j] = 0;
			sum = 0; dfs3(temp[i]);
			if(sum > ans){
				ans = sum; cc.clear(); cc.push_back(temp[i]);
			}else if(sum == ans) cc.push_back(temp[i]);
		}
		printf("Case %d: %d\n", t, ans-1);
		bool occur = 0;
		for(int i = 0; i < n; ++i){
			if(incc(i)){
				if(!occur){
					printf("%d", i); occur = 1;	
				}else printf(" %d", i);
			}
		}
		printf("\n");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

容嬷嬷当年一枝花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值