程序设计思维与实践 Week8 作业 (1/2/智能班)

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]\)之间选点的个数,那么对于第i个区间\([a_i,b_i]\)需要满足\(sum[b_i]-sum[a_i-1]≥c_i\),这样就可以把原问题变为差分约束问题,同时还需要满足\(0≤sum[i]-sum[i-1]≤1\),这样对该差分约束系统求得最小解,将其转化为≥不等式组跑最长路,答案为\(sum[max\{b_i\}]\)

差分约束

对于类似\(x_i-x_j≤c_k\)的不等式组,都可以移项变形为\(x_i≤c_k+x_j\),如果令\(c_k=w(i,j)\)\(dis[i]=x_i\)\(dis[j]=x_j\),那么原式变为最短路问题中的松弛操作。

所以可以把\(x_i\)看作是图中的一个结点,对于每个不等式约束\(x_i-x_j≤c_k\),从结点\(j\)到结点\(i\)连一条长度为\(c_k\)的有向边,那么求解差分约束文艺就变成了求解最短(长)路问题了。

代码

#include<iostream>
#include<stdio.h>
#include<queue>
#include<cmath>
#include<string.h>
using namespace std;

int a,b,c;

int t,n,m,tot=0;
int dis[50010],inq[50010],cnt[50010];
int head[250000];

int MAX_b;
int MIN_a; 
struct edge {
	int to,w,next;
}e[250000];


void add(int u,int v,int w)
{	
	e[++tot].to = v;
	e[tot].w = w;
	e[tot].next = head[u];
	head[u] = tot;
}

void initial(){
	memset(head,-1,sizeof(head));
	memset(inq,0,sizeof(inq));
	for(int i=0;i<50010;i++){
		dis[i] = -1000000;
	}
}

void spfa(int s) {
	dis[s] = 0;
	inq[s] = 1;
	queue<int> q;
	q.push(s);
	while(!q.empty()) {
		int u = q.front();
		q.pop();
		inq[u] = 0;
//		cout<<u<<endl;
		for(int i=head[u];i!=-1;i=e[i].next) {
			int v = e[i].to;
//			cout<<u<<","<<v<<","<<e[i].w<<endl;
			if(dis[v]<(dis[u]+e[i].w)) {
//				cout<<v<<endl;
				dis[v] = dis[u] + e[i].w;
				if(!inq[v]) {
					q.push(v);
					inq[v] = 1;
				}
			}
		}	
	}
}
int main() {
//	freopen("in1.txt","r",stdin);
	cin>>n;
	MAX_b = -10000000;
	MIN_a = 10000000;
	initial();
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&a,&b,&c);
		MAX_b = max(MAX_b,b+1);
		MIN_a = min(a,MIN_a);
		add(a,b+1,c);
	}
//	cout<<MIN_a<<MAX_b<<endl;
	for(int i=MIN_a;i<=MAX_b;i++){
		add(i,i+1,0);
		add(i+1,i,-1);
//		cout<<i<<endl;
	}
	spfa(MIN_a);
//	cout<<"---"<<endl;
	cout<<dis[MAX_b]<<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

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

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

Sample Input

4 3
1 2
2 3
4 3

Sample Output

1 2 4 3

思路

由于输赢的方向性,所以最后的排序一定是按照方向性的顺序排列,所以是典型的拓扑排序问题。

由于题目要求按照字典序进行输出名次,所以使用优先级队列即可。

Kahn算法

• 将入度为 0 的点组成一个集合 S
• 每次从 S 里面取出一个顶点 u (可以随便取)放入 L , 然后遍历顶点 u 的所有边 (u, v) , 并删除之,并判断如果该边的另一个顶点 v,如果在移除这一条边后入度为 0 , 那么就将这个顶点放入集合 S 中。不断地重复取出顶点然后重复这个过程……
• 最后当集合为空后,就检查图中是否存在任何边。如果有,那么这个图一定有环路,否者返回 L , L 中顺序就是拓扑排序的结果。

代码

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


//int e[510][510];
int indeg[510],outdeg[510];
int m,n;
vector<int> G[510];

void initial() {
	for(int i=0; i<510; i++) {
		indeg[i] = 0;
		outdeg[i] = 0;
		G[i].clear();
	}
}
void add(int u,int v) {
	G[u].push_back(v);
	outdeg[u]++;
	indeg[v]++;
}
void Kahn() {
	priority_queue<int,vector<int>,greater<int> > q;
	for(int i=1; i<=n; i++) {
		if(indeg[i]==0) {
//			cout<<i<<endl;
			q.push(i);
		}
	}
//	cout<<"---"<<endl;
	queue<int> ans;
	while(q.size()) {
		int u = q.top();
		q.pop();
		ans.push(u);
		
		for(vector<int>::iterator i= G[u].begin(); i!=G[u].end(); i++) {
			int v = *i;
			indeg[v]--;
			if(indeg[v]==0) q.push(v);
		}
	}
	while(ans.size()>1) {
		int tmp = ans.front();
		ans.pop();
		cout<<tmp<<" ";
	}
	int tmp = ans.front();
	ans.pop();
	cout<<tmp<<endl;
}
int main() {
//	freopen("in2.txt","r",stdin);
	bool firstIn = true;
	while(cin>>n>>m) {
//		if(!firstIn){
//			cout<<endl;
//		}
		firstIn = false;
		initial();
		int u,v;
		while(m) {
			m--;
			cin>>u>>v;
			add(u,v);
		}
		Kahn();
	}
	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开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!

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

思路

首先通过Kosaraju算法获得SCC图,也就是将那些属于同一个SCC的点看作一类,那么所有直接指向或间接指向该SCC的SCC均是票数,将其相加即可,如果将SCC缩成1个点,那么所有到可达该点的点的票数相加加上该点的票数-自身,即为总票数,可以通过反证法证明最终的答案只会出现在出度为0的SCC点上,而对于可达该点的求解只需要将边反向,求得该点可达的SCC点即可。

坑点

没注意读题,没对比输出。

代码

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
int t,m,n,cnt,scnt,st,tans;
int ord[30001],rord[30001];
int scc[30001],dsc[30001],indeg[30001],ans[30001];
bool vis1[30001],vis2[30001],vis[30001];
vector<int> G1[30001],G2[30001],G3[30001];
void initial() {
	cnt = 0;
	st = 0;
	for(int i=0; i<=30000; i++) {
		G1[i].clear();
		G2[i].clear();
		G3[i].clear();
		vis1[i] = 0;
		vis2[i] = 0;
		vis[i] = 0;
		indeg[i] = 0;
		scc[i] = 0;
		dsc[i] = 0;
		ord[i] = 0;
		rord[i] = 0;
		ans[i] = 0;
	}
}
void dfs1(int s) {
	vis1[s] = true;
	for(vector<int>::iterator i= G1[s].begin(); i!=G1[s].end(); i++) {
		int d = *i;
		if(!vis1[d]) {
			dfs1(d);
		}
	}
	cnt++;
	ord[s] = cnt;
	rord[cnt] = s;
}
void dfs2(int s) {
	vis2[s] = true;
	dsc[s] = st;
	for(vector<int>::iterator i= G2[s].begin(); i!=G2[s].end(); i++) {
		int d = *i;
		if(!vis2[d]) {
			scnt++;
			dfs2(d);
		}
	}
}
void dfs(int s) {
	vis[s] = true;
//	cout<<s<<endl;
	tans += scc[s];
	for(vector<int>::iterator i= G3[s].begin(); i!=G3[s].end(); i++) {
		int d = *i;
//		cout<<d<<endl;
		if(!vis[d]) {
			dfs(d);
		}
	}
}
int main() {
//	freopen("in3.txt","r",stdin);
	cin>>t;
	int co = 0;
	while(t) {
		t--;
		co ++;
		cin>>n>>m;
		int a,b;
		initial();
		for(int i=0; i<m; i++) {
			scanf("%d%d",&a,&b);
			G1[a+1].push_back(b+1);
			G2[b+1].push_back(a+1);
//			cout<<a+1<<","<<b+1<<endl;
		}
		for(int i=1; i<=n; i++) {
			if(!vis1[i]) dfs1(i);
		}
		for(int i=n; i>=1; i--) {
			if(!vis2[rord[i]]) {
//				cout<<"ind:"<<rord[i]<<endl;
				scnt = 1;
				st++;
				dfs2(rord[i]);
				scc[st] = scnt;
			}
		}
//		for(int i=1; i<=st; i++) {
//			cout<<scc[i]<<endl;
//		}
		for(int s=1; s<=n; s++) {
			for(vector<int>::iterator i= G2[s].begin(); i!=G2[s].end(); i++) {
				int d = *i;
//				cout<<s<<","<<d<<endl;
//				cout<<dsc[s]<<","<<dsc[d]<<":";
				if(dsc[s]!=dsc[d]) {
					int u = dsc[s],v = dsc[d];
//					cout<<u<<","<<v<<endl;
					indeg[v]++;
					G3[u].push_back(v);
				}
			}
		}
		for(int i=1; i<=st; i++) {
			if(indeg[i]==0) {
				tans = 0;
				for(int j=0;j<5001;j++) vis[j] = 0;
				dfs(i);
				ans[i] = tans - 1;
			}
		}
		int MaxAns = 0;
		queue<int> ansq;
		for(int i=1; i<=st; i++) {
//			cout<<ans[i]<<",";
			MaxAns = max(MaxAns,ans[i]);
		}
//		cout<<endl;
		for(int i=1; i<=st; i++) {
			if(MaxAns==ans[i]) {
				ansq.push(i);
			}
		}
		priority_queue<int,vector<int>,greater<int> > output;
		while(ansq.size()) {
			int tmp = ansq.front();
			ansq.pop();
			for(int i=1; i<=n; i++) {
				if(dsc[i]==tmp) output.push(i-1);
			}
		}
		printf("Case %d: %d\n",co,MaxAns);
		while(output.size()>1){
			printf("%d ",output.top());
			output.pop();
		}
		printf("%d\n",output.top());
	}
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/mopa/p/12717623.html