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
解题思路:
本周学习了差分约束系统,就单纯的感觉其中的思想很巧妙,这种转化显得有些不可思议,而这种思想正是对于最短路模型深度理解的一种体现,即相邻两个点到原点的最短距离差一定小于该两个点的直接距离,我们巧妙的将不等式转化为两点之间距离,这使得问题的要求并没有发生改变,我们只是为其赋予了一个满足题意的模型,同时又使得问题的求解转化得更简单。不得不说,这种巧妙的转化值得我们深度思考。我们应当理解的是,对于一个转化好的图,利用固定的算法仅能够求出一组固定的解。而对于不等式来讲,其每一组解我们都能够表示为一幅图。而对于为什么跑最短路我们求出上解,这是因为我们利用最短路求出的解,解中的任何一个值都不再能够发生抖动,我们可以将dj算法理解为一种bfs扩散,每一层扩散中都是取的等号,故若解中有点能够向更大值进行抖动,我们很容易利用反证法证明其将违背原不等式。而对于最长路,我们利用相同的思维进行思考就可以,对于本题,我们正是利用的最长路进行求解。而在进行转化时,我们的思维要全面,要注意所有要满足的不等式,也就是说,图要建立的完整且有意义。比如这道题目来讲,我们在建图时,首先要保证图的连通性,这样才能保证与原转化问题保持等价,其中这两句代码缺一不可,add(i,i+1,0);add(i+1,i,-1);也就是说,我们在建图过程中,会根据题目意义得到若干组不等式,以使得问题转化保持等价。但在这些不等式中部分可以选择舍弃,而部分则一定需要保留,这个过程,我们需要通过题目的实际意义进行正确判断。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<stdio.h>
using namespace std;
//此处应当注意对第一个点的处理; 
struct Edge{
	int end,value,next;
}edge[200005];
int tot=0;
int head[50005];
void add(int begin,int end,int value)
{
	edge[tot].end=end;
	edge[tot].value=value;
	edge[tot].next=head[begin];
	head[begin]=tot++;
}
int minpoint=50005,maxpoint=0;
priority_queue<pair<int,int> > qu;
int dis[50005];
int n,a,b,c;
void dj(int start)
{
	dis[start]=0;
	qu.push(pair<int,int>(0,start));
	while(qu.size())
	{
		int func1=qu.top().second,func2=qu.top().first;
		qu.pop();
		if(func2<dis[func1]) continue;
		for(int i=head[func1];i!=-1;i=edge[i].next)
		{
			if(dis[edge[i].end]<dis[func1]+edge[i].value)
			{
				dis[edge[i].end]=dis[func1]+edge[i].value;
				qu.push(pair<int,int>(dis[edge[i].end],edge[i].end));
			}
		}
	}
}
int main()
{
	memset(head,-1,sizeof(head));
	for(int i=0;i<50005;i++)
	{
		dis[i]=-10000000;
	}
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(a<minpoint)
		minpoint=a;
		if(b>maxpoint)
		maxpoint=b;
		add(a,b+1,c);
	//	add(b,a-1,a-b-1);
	}
	for(int i=minpoint;i<maxpoint+1;i++)
	{
		add(i,i+1,0);
		add(i+1,i,-1);
	}
	dj(minpoint);
	cout<<dis[maxpoint+1]<<endl;
} 
在这里插入代码片

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,我们就将其入堆,这道题要求我们按照字典序进行输出,所以我们只需要利用一个优先级队列进行维护即可。对于这种经典问题,我们对其需要维护多少变量,变量怎么用应当有着一定的记忆。

#include<iostream>
#include<string.h>
#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
int M,N;
int a,b;
int stage;
int label[505];
vector<int> vec[505];
priority_queue<int> qu;
int main() {
	while(scanf("%d%d",&N,&M)!=EOF) {
		stage=N;
		memset(label,0,sizeof(label));
		for(int i=0; i<505; i++)
			vec[i].clear();
		while(qu.size()) qu.pop();
		for(int i=0; i<M; i++) {
			scanf("%d%d",&a,&b);
			label[b]++;
			vec[a].push_back(b);
		}
		for(int i=1; i<=N; i++) {
			if(label[i]==0)
				qu.push(-i);
		}
		if(stage==1) {
			cout<<qu.top()<<endl;
		} else {
			while(qu.size()) {
				int func=-qu.top();
				qu.pop();
				cout<<func<<" ";
				for(int i=0; i<vec[func].size(); i++) {
					label[vec[func][i]]--;
					if(label[vec[func][i]]==0)
						qu.push(-vec[func][i]);
				}
				stage--;
				if(stage==1)
				break;
			}
			cout<<-qu.top()<<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
解题思路:
这道题是比较的麻烦,感觉自己是第一次遇见思维逻辑以及处理上需要变化很多次的题目(做题比较少),看见这道题,思路是有的,但是感觉很繁琐,甚至怀疑能不能实现,准确地讲,就是对解决这种问题不具有自信,总感觉会出错。首先讲,这种排斥心理是不对的,题既然是这样,那么按照思路一定能实现,我们需要对写代码保持一定的耐心,而写的过程中也要保持高度的自信,这应当也是编程能力需要提高的一部分。另外,对于本题,我们是利用kosaraju算法并缩点进行实现的,这种传递性质的东西很容易想到强连通分量,对于这个算法,我们也要理解,为什么要取逆后序呢?我们讲,逆后序在意义上可以说是强连通分量的一种拓扑排序,其原因就是,我们在进行dfs时,我们一旦进入一个连通分量,由于联通分量之间只有一条有向边进行连接,这也就意味着我们进入之后,暂时性是出不来的,等其弹出时,要将该联通分量中的元素弹完方可出来,这在一定意义上显现了dfs的强大作用,另外,我们将原图反向,(这时我们已经将连通分量之间锁死)并利用逆后序进行遍历,这是因为我们在原图遍历时,进入某一个连通分量的第一个元素会最后一个被弹出,这意味着在原图中该元素能够到达该联通分量中的任何一个元素,如果在反图中,从该点遍历仍然能够到达的元素理应构成一个连通分量。另外,便是对于缩点的处理,这应当是一种套路,常见问题的套路处理掌握的越多越好。另外,我们在做或者思考这种类似递归的问题时,我们最好在脑海中形成一个模板,不妨将二叉树的遍历作为板子,时刻动态的演示其实怎么工作的,这对于我们理解栈,dfs,以及递归思想有着一定的好处,使我们能够产生更深的认识,另一方面,也能够拓展我们的思路。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
struct Edge {
	int to,next;
} edge1[30005],edge2[30005],edge3[30005];
int tot1=0,tot2=0,tot3=0;
int head1[5005],head2[5005],head3[5005],vis[5005],dev[5005],label[5005];
int SCC[5005];//用来表示点属于哪个连通分量;
int scc=0;//用来表示一共有几个连通分量;
int daoxu[5005];//保存倒序序列;
int t=0;
vector<int> ans; //用来保存可能最大值的连通分量;
vector<int> en;//用来保存最终的答案;
int number=0;
int cc;
void add(int begin,int end) { //加边处理;
	edge1[tot1].next=head1[begin];
	edge2[tot2].next=head2[end];
	edge1[tot1].to=end;
	edge2[tot2].to=begin;
	head1[begin]=tot1++;
	head2[end]=tot2++;
}
void add2(int begin,int end) { //建立缩点图;
	edge3[tot3].next=head3[begin];
	edge3[tot3].to=end;
	head3[begin]=tot3++;
}
void dfs(int start) { //在正向图中进行dfs,获取后序序列;
	vis[start]=1;
	for(int i=head1[start]; i!=-1; i=edge1[i].next) {
		if(!vis[edge1[i].to])
			dfs(edge1[i].to);
	}
	daoxu[t++]=start;//...
}
void rdfs(int start) { //在反图中进行dfs,获取连通分量;
	vis[start]=1;
	for(int i=head2[start]; i!=-1; i=edge2[i].next) {
		if(!vis[edge2[i].to])
			rdfs(edge2[i].to);
	}
	SCC[start]=scc;
	label[scc]++;
}
void dfs2(int start) { //最终在缩点图中再次进行dfs;
	number+=label[start];
	vis[start]=1;
	for(int i=head3[start]; i!=-1; i=edge3[i].next) {
		if(!vis[edge3[i].to])
			dfs2(edge3[i].to);
	}
}
int T,N,M,a,b;
int main() {
	scanf("%d",&T);
	for(int q=1; q<=T; q++) {
		memset(head1,-1,sizeof(head1));
		memset(head2,-1,sizeof(head2));
		memset(head3,-1,sizeof(head3));
		memset(label,0,sizeof(label));
		memset(dev,0,sizeof(dev));
		memset(vis,0,sizeof(vis));
		memset(SCC,0,sizeof(SCC));
		memset(daoxu,-1,sizeof(daoxu));
		tot1=0;
		tot2=0;
		tot3=0,t=0;
		ans.clear();
		en.clear();
		number=0;
		scanf("%d%d",&N,&M);
		for(int i=0; i<M; i++) {
			scanf("%d%d",&a,&b);
			add(a,b);
		}
		for(int i=0; i<N; i++) {
			if(!vis[i])
				dfs(i);
		}
		memset(vis,0,sizeof(vis));
		scc=0;
		for(int i=N-1; i>=0; i--) {
			if(!vis[daoxu[i]]) {
				rdfs(daoxu[i]);
				scc++;
			}
		}

		for(int i=0; i<N; i++) {
			for(int j=head2[i]; j!=-1; j=edge2[j].next) {
				if(SCC[i]!=SCC[edge2[j].to]) {
					add2(SCC[i],SCC[edge2[j].to]);
					dev[SCC[edge2[j].to]]++;
				}
			}
		}
		cc=0;
		for(int i=0; i<scc; i++) {
			if(dev[i]==0) {
				memset(vis,0,sizeof(vis));
				dfs2(i);
				if(number>cc) {
					ans.clear();
					ans.push_back(i);
					cc=number;
				} else if(number==cc) {
					ans.push_back(i);
				}
				number=0;
			}
		}
		for(int i=0; i<ans.size(); i++) {
			for(int j=0; j<N; j++) {
				if(SCC[j]==ans[i])
					en.push_back(j);
			}
		}
		sort(en.begin(),en.end());
		cout<<"Case "<<q<<": "<<cc-1<<endl;
		for(int i=0; i<en.size()-1; i++) {
			cout<<en[i]<<" ";
		}
		cout<<en[en.size()-1]<<endl;
	}
}在这里插入代码片
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值