程序设计思维与实践 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

思路
将它转成图的问题。
用dis[i]来表示在数轴上[0,i]之间选择的点的个数。
对于第i个区间[ai,bi]则需要满足dis[bi]-dis[ai-1]>=ci
要保证dis是有意义的即0<=dis[i]-dis[i-1]<=1
最终将求该差分约束的最小解,转化成跑一条最长路即可。

代码

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

struct Edge 
{
	int to;
	int w;
	int next;
};

Edge edge[200010];
int total;
int head[50010];
int N;
long long dis[50010];
bool vis[50010];

//添加一条边
void addEdge(int x, int y, int w)
{
	edge[++total].to = y;
	edge[total].w = w;
	edge[total].next = head[x];
	head[x] = total;
}

void MaxlenthPath(int u)
{
	queue<int> q;

	while (!q.empty())
	{
		q.pop();
	}
	dis[u] = 0;
	vis[u] = true;
	q.push(u);
	while (!q.empty())
	{
		int v = q.front();
		q.pop();

		vis[v] = false;
		for (int i = head[v]; i != 0; i = edge[i].next)
		{

			if (dis[edge[i].to] < dis[v] + edge[i].w)
			{
				dis[edge[i].to] = dis[v] + edge[i].w;
				
				if (vis[edge[i].to] == false)
				{
					q.push(edge[i].to);
					vis[edge[i].to] = true;
				}

			}
		}
	}
}


int main()
{
	total = 0;
	memset(head, 0, sizeof(head));
	//memset(edge, 0, sizeof(edge));
	//memset(vis, false, sizeof(vis));
	//memset(dis, -1e18, sizeof(dis));
	int mmax = -1;
	cin >> N;
	for (int i = 0; i < N; i++)
	{
		int a, b, w;
		cin >> a >> b >> w;
		addEdge(a, b + 1, w);
		
		if (b+1 > mmax)
		{
			mmax = b + 1;
		}
	}
	for (int i = 1; i <= mmax; i++)
	{
		addEdge(i - 1, i, 0);
		addEdge(i, i - 1, -1);
	}
	for (int i = 0; i <= mmax; i++)
	{
		dis[i] = -1e16;
		vis[i] = false;
	}

	MaxlenthPath(0);
	cout << dis[mmax];

}

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

思路
用拓扑排序的方法。
将猫猫之间的胜负关系转成有向无环图。
即:p1赢了p2就是存在一条p1->p2的有向边。

将所有入度为0的点放入一个集合里面。
每次取出以个点,将其删除后,更改它所指向的点的入度,有入度为0的放入集合中。
依次进行,直至集合为空。

因为进行的过程中,已经删除了图中的有向边。通过判断删除过后图中是否还存在边,来判断是否能够符合要求。

代码

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

/*B*/
struct Edge
{
	int to;
	int next;
};

Edge edge[5010];
int head[510];
int indeg[510];
int total;
int N, M;
priority_queue<int> q;
vector<int> vec;

void addEdge(int x, int y)
{
	edge[++total].to = y;
	edge[total].next = head[x];
	head[x] = total;
	indeg[y]++;
}

void Kahn()
{
	for (int i = 1; i <= N; i++)
	{
		if (indeg[i] == 0)
		{
			q.push(-i);
		}
	}

	while (!q.empty())
	{
		int v = -q.top();
		q.pop();
		vec.push_back(v);
		for (int i = head[v]; i; i = edge[i].next)
		{
			if (--indeg[edge[i].to] == 0)
			{
				q.push(-edge[i].to);
			}
		}
	}


}


int main()
{
	while (cin >> N >> M)
	{
		total = 0;
		memset(head, 0, sizeof(head));
		memset(indeg, 0, sizeof(indeg));
		vec.clear();

		for (int i = 0; i < M; i++)
		{
			int p1, p2;
			cin >> p1 >> p2;
			addEdge(p1, p2);
		}

		Kahn();

		int ssize = (int)vec.size();
		for (int i = 0; i < ssize - 1; i++)
		{
			cout << vec[i] << " ";
		}
		cout << vec[ssize - 1] << endl;
	}
}

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

思路
每个人看作一个点,支持关系可以看作是一条有向边。
可以互相到达的两个点他们被支持的票数是一样的,因此通过考虑求出SCC并缩点来求解。

因此在找到强连通分量之后,在原图遍历所有出度为0的SCC,我们将边反向,对每个出度为0的点进行bfs,计算它能够到达的点的个数,就能得到答案。

代码

#include<iostream>
#include<queue>
#include<stdio.h>
#include<cstring>
#define MAXM 30010
#define MAXN 5010
using namespace std;

/*C*/

struct Edge
{
	int to;
	int next;
};

Edge edge1[MAXM], edge2[MAXM], edge3[MAXM];//原图、反图、去重后图
int head1[MAXN], head2[MAXN], head3[MAXN];
int indeg[MAXN];//入度
int dfn[MAXN];
int c[MAXN], scc[MAXN];
int num;
int number[MAXN];
int vis[MAXN];
int tot1, tot2, tot3;
int dcnt, scnt;
int T, N, M;


//加原图反图
void add12(int x, int y)
{
	//原图
	edge1[++tot1].to = y;
	edge1[tot1].next = head1[x];
	head1[x] = tot1;
	//反图
	edge2[++tot2].to = x;
	edge2[tot2].next = head2[y];
	head2[y] = tot2;
}

//加去重后的图
void add3(int x, int y)
{
	edge3[++tot3].to = y;
	edge3[tot3].next = head3[x];
	head3[x] = tot3;
}

//初始化
void init()
{
	tot1 = -1, tot2 = -1, tot3 = -1;
	dcnt = 0, scnt = 0;
	num = 0;
	memset(head1, -1, sizeof(head1));
	memset(head2, -1, sizeof(head2));
	memset(head3, -1, sizeof(head3));
	memset(indeg, 0, sizeof(indeg));
	memset(dfn, 0, sizeof(dfn));
	memset(c, 0, sizeof(c));
	memset(scc, 0, sizeof(scc));
	memset(number, 0, sizeof(number));
	memset(vis, 0, sizeof(vis));

	
}

void dfs1(int u)
{
	vis[u] = 1;
	for (int i = head1[u]; i != -1; i = edge1[i].next)
	{
		if (vis[edge1[i].to] == 0)
		{
			dfs1(edge1[i].to);
		}
	}

	dfn[++dcnt] = u;
}

void dfs2(int u)
{
	c[u] = scnt;
	scc[scnt]++;
	for (int i = head2[u]; i != -1; i = edge2[i].next)
	{
		if (c[edge2[i].to] == 0)
		{
			dfs2(edge2[i].to);
		}
	}
}

void dfs3(int u)
{
	num += scc[u];
	vis[u] = 1;
	for (int i = head3[u]; i != -1; i = edge3[i].next)
	{
		if (vis[edge3[i].to] == 0)
		{
			dfs3(edge3[i].to);
		}
	}

}

void kosaraju()
{
	for (int i = 0; i < N; i++)
	{
		if (vis[i] == 0)
		{
			dfs1(i);
		}
	}

	for (int i = N-1; i >= 0; i--)
	{
		if (c[dfn[i]] == 0)
		{
			++scnt;
			dfs2(dfn[i]);
		}
	}
}

void solve(int k)
{
	
	for (int i = 0; i < N; i++)
	{
		for (int j = head2[i]; j != -1; j = edge2[j].next)
		{
			if (c[i] != c[edge2[j].to])
			{
				add3(c[i], c[edge2[j].to]);
				indeg[c[edge2[j].to]]++;
			}
		}
	}

	int result = 0;
	for (int i = 1; i <= scnt; i++)
	{
		if (indeg[i] == 0)
		{
			memset(vis, 0, sizeof(vis));
			num = 0;
			dfs3(i);
			number[i] = num - 1;//除了自己
			if (result < number[i])
			{
				result = number[i];
			}	
		}
	}

	printf("Case %d: %d\n", k, result);
	//cout << "Case " << k << ": " << result << endl;

	int flag = 0;
	for (int i = 0; i < N; i++)
	{
		if (number[c[i]] == result)
		{
			if (flag == 0)
			{
				printf("%d", i);
				//cout << i;
				flag = 1;
			}
			else
			{
				printf(" %d", i);
				//cout << " " << i;
			}
		}
	}

	printf("\n");
	//cout << endl;

}

int main()
{
	

	scanf("%d", &T);
	//cin >> T;
	for (int i = 1; i <= T; i++)
	{
		//cin >> N >> M;
		scanf("%d %d", &N, &M);
		init();
		for (int j = 0; j < M; j++)
		{
			int A, B;
			scanf("%d %d", &A, &B);
			//cin >> A >> B;
			add12(A, B);
		}

		kosaraju();
		solve(i);
	}


}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值