程序设计思维与实践 Week8 作业

A - 区间选点 II

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

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


Input
输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+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]内的点数。对于区间[ai,bi],需要满足sum[bi]>=sum[ai-1]+ci,则问题可以转化为图:sum[ai]作为点,两点之间的点数作为线,需要求最长路,单源最长路用spfa,原点为0点,就是最小点1前面的虚拟点。还必须满足条件0<=sum[i]-sum[i-1]<=1


#include <iostream>
#include <queue>
using namespace std;
const int MAXM = 300000, MAXN = 50000 + 10, inf=0x7fffffff;
struct Edge
{
	int to, next, w;
}Edges[MAXM];
int head[MAXN], tot, MAX = -1, sum[MAXN];
bool vis[MAXN];
void addEdge(int u, int v, int w)
{
	Edges[tot].to = v;
	Edges[tot].w = w;
	Edges[tot].next = head[u];
	head[u] = tot++;
}
void spfa(int s)
{
	queue<int> q;
	for (int i = 0; i <= MAX; i++) sum[i] = -inf;
	q.push(0); sum[0] = 0;; vis[0] = true;
	while (q.size())
	{
		int u = q.front(); q.pop(); vis[u] = false;
		for (int i = head[u]; i != -1; i = Edges[i].next)
		{
			Edge e = Edges[i];
			int to = e.to, w = e.w;
			if (sum[u] + w > sum[to])
			{
				sum[to] = sum[u] + w;
				if (!vis[to])
				{
					q.push(to);
					vis[to] = true;
				}
			}
		}
	}
}
int main()
{
	memset(head, -1, sizeof(int) * MAXN);
	int n; cin >> n;
	while (n--)
	{
		int u, v, w; 
		cin >> u >> v >> w;
		v++;
		addEdge(u, v , w);
		MAX = MAX > v ? MAX : v;
	}
	for (int i = 0; i <= MAX; i++)
	{
		addEdge(i, i + 1, 0);
		addEdge(i + 1, i, -1);
	}
	spfa(0);
	cout << sum[MAX];
}

总结:发现16进制给int赋值的时候是当作补码的。
int inf=0xffffffff==-1(×)
int inf=0x7fffffff(√)


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
给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!

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


思路:拓扑排序问题,一次从队列里拿出入度为0的点,入度为0的含义是他没有前置条件了,拿出点后意味着这点作为条件已经完成了,那么将它的临界点入度减一,如果入度为0则入队列。为了满足编号小的队伍在前的条件,用优先队列,优先队列默认大根堆,注意比较函数用greater。


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

const int MAXN = 500 + 5, MAXM = 5000;
struct Edge
{
	int to, next;
}Edges[MAXM];
int head[MAXN], tot, deg[MAXN], N, ans[MAXN], index;
void addEdge(int u, int v)
{
	Edges[tot].to = v;
	Edges[tot].next = head[u];
	head[u] = tot++;
}
void init()
{
	index = tot = 0;
	memset(head, -1, sizeof(int) * (N + 1));
}
void toposort()
{
	priority_queue <int, vector<int>, greater<int> >q;
	for (int i = 1; i <= N; i++)
		if (deg[i] == 0) q.push(i);
	while (q.size())
	{
		int u = q.top(); q.pop();
		ans[index++] = u;
		for (int i = head[u]; i != -1; i = Edges[i].next)
		{
			int to = Edges[i].to;
			if (--deg[to] == 0) q.push(to);
		}
	}
}
void output()
{
	for (int i = 0; i < N; i++)
	{
		cout << ans[i];
		if (i != N - 1) cout << " ";
	}
	cout << '\n';
}
int main()
{
	int M;
	while (cin >> N >> M)
	{
		init();
		for (int i = 1; i <= M; i++)
		{
			int u, v; cin >> u >> v;
			addEdge(u, v);
			deg[v]++;
		}
		toposort();
		output();
	}
}

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开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!
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

思路:互相py的同学看作一个强连通图,他们互相承认,看作一个整体即可。把所有强连通图缩为点,构成新的图。一个人的票数等于缩点内出了他自己的人数,和指向他的点(这个指向具有继承关系)人数的总和。答案一定是出度为0的缩点。
第一步要求强连通图,方法是kosaraju 算法,具体是这样的:

  1. 第一遍dfs,求出逆后序
  2. 第二遍dfs,按照反图逆后序遍历,把每个连通图标上号,并顺便记录下每个连通图的点数sum[i]和出度deg[i],
  3. 遍历每一个缩点,找到出度为0的,对这个点dfs,记录下他的的票,如果大于ans,更新,并记录下这个缩点的编号,记录在vector a种。

最后输出阶段,输出ans,然后遍历每个点,如果他所在缩点编号在a里,输出。


#include <iostream>
#include <vector>
using namespace std;
 
const int MAXN = 5000 + 5, MAXM = 30000 + 5;
vector<int> G1[MAXN], G2[MAXN], G3[MAXN];
int N, M, dfn[MAXN], dcnt, c[MAXN], scnt, sum[MAXN], deg[MAXN], ans, temp, order;
vector<int> a;
bool vis[MAXN] ,vis2[MAXN], vis3[MAXN];

void init()
{
	for (int i = 0; i < N; i++)
	{
		G1[i].clear();
		G2[i].clear();
		G3[i].clear();
		deg[i] = sum[i] = 0;
		vis[i] = false;
		c[i] = -1;
	}
	a.clear();
	dcnt = scnt = ans = 0;
}
void dfs1(int x)
{
	vis[x] = true;
	for (auto y : G1[x])
		if (!vis[y]) dfs1(y);
	dfn[dcnt++] = x;
}
void dfs2(int x)
{
	c[x] = scnt;
	sum[scnt]++;
	for (auto y : G2[x])
	{
		if (c[y] == -1) dfs2(y);
		else if (c[y] != scnt && !vis2[c[y]])
		{
			deg[c[y]]++, vis2[c[y]] = true;
			G3[scnt].push_back(c[y]);
		}
	}
}
void dfs3(int x)
{
	temp += sum[x];
	for (auto y : G3[x])
		if (!vis3[y])  vis3[y] = true, dfs3(y);
}
void kosaraju()
{
	for (int i = 0; i < N; i++)
		if (!vis[i]) dfs1(i);
	for (int i = N - 1; i >= 0; i--)
		if (c[dfn[i]] == -1)
		{
			memset(vis2, 0, sizeof(bool) * N);
			dfs2(dfn[i]), scnt++;
		}
	for (int i = 0; i <= scnt - 1; i++)
		if (deg[i] == 0)
		{
			memset(vis3, 0, sizeof(bool) * N);
			temp = 0, dfs3(i);
			if (temp - 1 > ans)
			{
				ans = temp - 1;
				a.push_back(i);
			}
		}
}
void output()
{
	cout << "Case " << ++order << ": " << ans << '\n';
	int flag = false;
	for (int i = 0; i < N; i++)
		for (auto f : a)
			if (c[i] == f)
			{
				if (flag) cout << " ";
				else flag = true;
				cout << i;
			}
	cout << '\n';
}
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);

	int K; cin >> K;
	while (K--)
	{
		cin >> N >> M;
		init();
		while (M--)
		{
			int u, v; cin >> u >> v;
			G1[u].push_back(v);
			G2[v].push_back(u);
		}
		kosaraju();
		output();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值