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

A - 区间选点 II

题意

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

解题思路

我们可以使用差分约束的方法来解决这道题,这道题中要求[ai,bi]间至少有ci个点,我们用sum[i]表示0-i的所有点数,则题目要求 s u m [ b i ] s u m [ a i 1 ] > = c i sum[bi] - sum[ai-1] >= ci ,我们只需将其转换为 s u m [ b i ] > = s u m [ a i 1 ] + c i sum[bi] >= sum[ai-1]+ci ,将其转化为图,ci为a[i-1]到bi的边权,从0点开始用spfa跑最长路即可。
注意:给定的区间左端点有可能为0,所以不能使用sum[ai-1],我们将其转换为sum[bi+1]和sum[ai]即可

代码

#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
#define INF -1e9
int dis[50005];
bool inq[50005];
int N, a, b, c, mina, maxb;
struct edge{
    int v, w;
}e1;
vector<edge> graph[50005];
void spfa(int s){
	for(int i = 1; i <= maxb; i++){
		dis[i] = INF;
		inq[i] = 0;
	}
	dis[s] = 0;
	inq[s] = 1;
	queue<int> q;
	q.push(s);
	while(!q.empty()){
		int u = q.front();
		q.pop();
		inq[u] = 0;
		for(int i = 0; i < graph[u].size(); ++i){
			int v = graph[u][i].v;
			if(dis[v] < dis[u] + graph[u][i].w){
				dis[v] = dis[u] + graph[u][i].w;
				if(!inq[v]){
					q.push(v);
					inq[v] = 1;
				}
			}
		}
	}
}
int main()
{
    scanf("%d", &N);
    for(int i = 0; i < N; ++i){
        scanf("%d %d %d", &a, &b, &c);
        maxb = max(b+1, maxb);
        e1.v = b+1;//由于0<=a<=b,使用sum[b+1]保存[0,b]间的点数。
        e1.w = c;
        graph[a].push_back(e1);
    }
    for(int i = 1; i <= maxb; ++i){//添加约束0<=sum[i]-sum[i-1]<=1
        e1.v = i;
        e1.w = 0;
        graph[i-1].push_back(e1);
        e1.v = i-1;
        e1.w = -1;
        graph[i].push_back(e1);
    }
    spfa(0);
    printf("%d",dis[maxb]);
    return 0;
}

B - 猫猫向前冲

题意

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

解题思路

这是一道拓扑排序的模板题,只是要求拓扑序列的字典序最小,我们将拓扑排序中的队列改为优先级队列即可。

代码

#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include<functional>
using namespace std;
int indeg[505];
int n, m;
vector<int> graph[505];
bool topsort()
{
    vector<int> ans;
    priority_queue<int, vector<int>, greater<int>> q;
	for(int i = 1; i <= n; ++i){
		if(indeg[i] == 0)
			q.push(i);
	}
	while(!q.empty()){
		int u = q.top();
		q.pop();
		ans.push_back(u);
		for(int i = 0; i < graph[u].size(); i++){
			if(--indeg[graph[u][i]] == 0)
				q.push(graph[u][i]);
		}
	}
	if(ans.size() == n){
		for(int i = 0; i < n-1; i++)
			cout << ans[i] << ' ';
		cout << ans[n-1] << endl;
		return true;
	}
	return false;
}
void init()
{
	memset(indeg, 0, sizeof(indeg));
    for (int i = 0; i <= n; i++)
        graph[i].clear();
}
int main()
{
    ios_base::sync_with_stdio(false);
    while (cin >> n >> m) {
        init();
        while (m--) {
            int p1, p2;
            cin >> p1 >> p2;
            graph[p1].push_back(p2);
			indeg[p2]++;
        }
        topsort();
    }
    return 0;
}

C - 班长竞选

题意

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

解题思路

因为意见具有传递性,所以可以转换成有向图,我们可以先求出该图的强联通分量,然后来计算。
求取强联通分量,我们使用kosaraju算法,首先通过一遍dfs求出该图的逆后序序列,第二次dfs求出每一个点所在的强联通分量。这里我们可以使用一个数组来记录每一个强联通分量的点数。
我们知道,对于图中任意一个点,得票数与其所在强联通分量的其他任意点相同,为该强联通分量的点数-1(除去本身),再加上其他所有能到达这个强联通分量的强联通分量的点数之和。我们不难证明,票数最多的点所在的强联通分量的出度一定为0,因为若不为0,则其指向的强联通分量的票数更多。
所以我们可以求取每一个强联通分量的出度,对于出度为0的分量再进行筛选。
对于每一个出度为0的强联通分量,我们首先将其票数加上自身点数-1,再对于其上的任意点在反图中进行dfs,将票数加上dfs所能到达的所有连通分量的点数。
最后判断并输出即可。

代码

#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
int n, m, dcnt, scnt, MAX;
vector<int> graph[5005], regraph[5005];//原图与反图
bool vis[5005], reach[5005];
int dfn[5005], c[5005], sccnt[5005], ans[5005], outDeg[5005];
//dcnt - dfs序计数, scnt - scc计数
//dfn[i] - dfs后序列中第i个点
//c[i] - i号点所在scc编号
//sccnt - 第i个scc中点的个数
void init()
{
    for (int i = 0; i <= n; i++){
        graph[i].clear();
		regraph[i].clear();
	}
	memset(sccnt, 0, sizeof(sccnt));
	memset(ans, 0, sizeof(ans));
	memset(outDeg, 0, sizeof(outDeg));
}
void dfs1(int x){
	vis[x] = true;
	for(int y:graph[x])
		if(!vis[y])
			dfs1(y);
	dfn[++dcnt] = x;
}
void dfs2(int x){
	c[x] = scnt;
	sccnt[scnt]++;
	for(int y:regraph[x])
		if(!c[y])
			dfs2(y);
}
void kosaraju(){
	dcnt = scnt = MAX = 0;
	memset(vis, 0, sizeof(vis));
	memset(c, 0, sizeof(c));
	for(int i = 0; i < n; ++i)
		if(!vis[i]) dfs1(i);
	for(int i = n-1; i >= 0; i--)
		if(!c[dfn[i]]){
			scnt++;
			dfs2(dfn[i]);
		}
}
void dfs3(int a, int x){
	vis[a] = true;
	if(!reach[c[a]]){
		ans[x] += sccnt[c[a]];
		reach[c[a]] = true;
	}
	for(int i = 0; i < regraph[a].size(); i++){
		if(!vis[regraph[a][i]])
			dfs3(regraph[a][i], x);
	}
}
void solve(int x){
	memset(vis, 0, sizeof(vis));
	memset(reach, 0, sizeof(reach));//reach[i]表示第i个连通分量是否访问过。
	reach[x] = true;
	ans[x] += sccnt[x] - 1;
	for(int i = 0; i < n; i++){
		if(c[i] == x){
			dfs3(i, x);
			break;
		}
	}
	if(ans[x] > MAX){
		MAX = ans[x];
	}
}
int main()
{
    ios_base::sync_with_stdio(false);
    int T;
	cin >> T;
	for(int cas = 1; cas <= T; cas++){
		cin >> n >> m;
		init();
		while(m--){
			int a, b;
			cin >> a >> b;
			graph[a].push_back(b);
			regraph[b].push_back(a);
		}
		kosaraju();
		for(int i = 0; i < n; i++){//计算每个连通分量出度
			for(int j = 0; j < graph[i].size(); j++){
				if(c[i] != c[graph[i][j]])
					outDeg[c[i]]++;
			}
		}
		for(int i = 1; i <= scnt; i++){
			if(outDeg[i] == 0){
				solve(i);
			}
		}
		cout << "Case " << cas << ": " << MAX << endl;
		bool first = true;
		for(int i = 0; i < n; i++){
			if(ans[c[i]] == MAX){
				if(first){
					cout << i;
					first = false;
				}
				else{
					cout << ' ' << i;
				}
			}
		}
		cout << endl;
	}
    return 0;
}
发布了12 篇原创文章 · 获赞 0 · 访问量 247

猜你喜欢

转载自blog.csdn.net/weixin_43851185/article/details/105568045
今日推荐