week8作业

题目一 区间选点II

题目描述

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

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

input

输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。

output

输出一个整数表示最少选取的点的个数

example

input 1

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

output 1

6

做法与思路

差分约束的标准题目,记 𝑠𝑢𝑚[𝑖] 表示数轴上 [0, 𝑖] 之间选点的个数,对于第 i 个区间 [𝑎𝑖, 𝑏𝑖] 需要满足 𝑠𝑢𝑚[𝑏𝑖] − 𝑠𝑢𝑚 [𝑎𝑖 − 1] ≥ 𝑐𝑖。
然后接下来是比较容易踩坑的一部分,我们还要保证 sum 要有意义,所以还要加上 0 ≤ 𝑠𝑢𝑚 [𝑖] − 𝑠𝑢𝑚[𝑖 − 1] ≤ 1 这个约束项。事实上如果不是上课时提到了这个问题我估计wa 100次也想不到。
然后求该差分约束系统的最小解,可以转化为求出最长路问题,用spfa算法进行即可。

代码

#include <iostream>
#include <queue> 
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
const int inf=-999999999;
int total=1;
int head[60000];
bool inq[60000];
int dis[60000];
struct edge{
	int u,v,w,next;
	edge(int tu=0,int tv=0,int tw=0,int tnext=0)
	{
		u=tu; v=tv; w=tw; next=tnext;
	} 
};
edge e[500000];
void add(int a,int b,int c)
{
	e[total]=edge(a,b,c,head[a]);
	head[a]=total;
	total++;
}
int main(int argc, char** argv) {
	int numble;
	cin>>numble;
	int maxx=inf,minn=-inf;
	for(int i=0;i<numble;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		if(a<minn)	minn=a;
		if(b>maxx)	maxx=b;
		add(a-1,b,c);
	}
	for(int i=0;i<=maxx;i++)	dis[i]=inf;
	for(int i=minn;i<=maxx;i++)
	{
		add(i-1,i,0);
		add(i,i-1,-1);
		//区间内至多一个点,至少0个点 
	} 
	dis[minn-1]=0;
	inq[minn-1]=1;
	queue<int> q;
	q.push(minn-1);
	while(!q.empty())
	{
		int now=q.front();
		q.pop();
		inq[now]=0;
		for(int i=head[now];i!=0;i=e[i].next)
		{
			int v=e[i].v,w=e[i].w;
			if(dis[v]<dis[now]+w)
			{
				dis[v]=dis[now]+w;	
				if(inq[v]==0)
				{
					q.push(v);
					inq[v]=1;
				}
			}
		}
	}
	cout<<dis[maxx];
	return 0;
}

题目二 猫猫向前冲

题目描述

众所周知, 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

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

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

example

input 1

4 3
1 2
2 3
4 3

output 1

1 2 4 3

做法与思路

按照名次排序。我们把a战胜b看作是有向图a->b,c战胜b看作是c->b,那么b输出时a,c一定输出了。换句话说,输出a时我们拿走a->b,输出c时我们拿走c->b,此时b的入度为0,即可输出。以这样的方式进行下去,那么我们每次都拿出入度为0的点,同时将该点所相邻的点入度减一即可。
所以这道题应该使用拓扑排序进行求解,但是同时题目还要求字典序最小,所以我们每次取出入度为零的点时,应该把他们放进小根堆中,每次弹出堆顶元素。

代码

#include <iostream>
#include <queue>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
struct node
{
	int n,judge;
	node(int a=0,int b=0)
	{
		n=a;judge=b;
	}
	void operator ++(int)
	{
		judge++;
	} 
	void operator --(int)
	{
		judge--;
	}
	bool operator <(const node& b) const
	{
		return n>b.n;
	}
};
node no[1000];
int head[1000];
int total=1;
struct edge{
	int u,v,next;
	edge(int a=0,int b=0,int c=0)
	{
		u=a;v=b;next=c;
	}
};
edge e[1000000];
void add(int a,int b)
{
	e[total]=edge(a,b,head[a]);
	head[a]=total;
	total++;
}
int ans[510];
int main(int argc, char** argv) {
	for(int i=0;i<510;i++) no[i].n=i;
	int n,m;
	while(cin>>n>>m)
	{
		int numble=0;
		priority_queue<node> q;
		for(int i=0;i<=n;i++)	
		{
			ans[i]=0;
			head[i]=0;
			no[i].judge=0;
		}
		for(int i=0;i<m;i++)
		{
			int a,b;
			cin>>a>>b;
			add(a,b);
			no[b]++;
		}
		for(int i=1;i<=n;i++)
		{
			if(no[i].judge==0)
				q.push(no[i]);
		}
		while(!q.empty())
		{
			int now=q.top().n;
			q.pop();
			ans[numble]=now;
			numble++;
			for(int i=head[now];i!=0;i=e[i].next)
			{
				int v=e[i].v;
				no[v]--;
				if(no[v].judge==0)
				{
					q.push(no[v]);
				}
			}
		}
		for(int i=0;i<n-1;i++)
		{
			cout<<ans[i]<<" ";
		}
		cout<<ans[n-1];
		cout<<endl;
	}
	return 0;
}

题目三 班长竞选

题目描述

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

example

input 1

2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2

output 1

Case 1: 2
0 1
Case 2: 2
0 1 2

做法与思路

较为综合的一道题。
事实上第一眼看到这个题我的第一思路是直接求传递闭包,但是由于数据量过大所以会超时,这就需要进行缩点。
我们试想这么一种情况,a->b,b->c,c->a,这样构成一个强连通分量,则这三个点是等价的,他们都收到了本分量内两个成员各一票,同时如果外来人员投票给任意一人则其他两人也相当于收到了该票。
这样一来我们可以把数量较多的点划分成一个个强连通分量,达成了缩点。
寻找联通分量需要使用Kosaraju算法,先进行第一遍dfs确定原图的逆后序序列,第二遍dfs在反图中按照逆后序序列进行遍历,标记点所属的强连通集。
然后缩点,对于当前scc中的点,ans+=scc[i]-1(去除自己);对于其他scc中的点,是sum即scc[j] (j可到达i);最后答案在出度为0的scc中,对入度为0的点进行第三遍dfs,计算其能到达点的sum。
变量较多,所以初始化时要注意每组数据结束后的清空操作。

代码

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

int scc[5010];//每个强联通分量的内部点的个数 
int zheng;
int fan;
int dfs_z[5010];//逆序序列 
int father[5010];//记录每个点所属的联通分量的索引 
int numble[5010];//记录每个联通分量的总出度 
int n,m;
int numble_of_scc;//记录scc的数量 
bool vis[5010];
bool vis2[5010];//记录某个联通图是否走过 
int ans[5010];
int maxx;
bool vis3[5010];//与vis2类似 
struct edge{
	int u,v,next;
	edge(int a=0,int b=0,int c=0)
	{
		u=a;v=b;next=c;
	}
};
edge e1[30100],e2[30100];
int h1[5010],h2[5010];
int total;
void add(int a,int b)
{
	e1[total]=edge(a,b,h1[a]);
	e2[total]=edge(b,a,h2[b]);//正图和反图
	h1[a]=total;
	h2[b]=total;
	total++; 
}
void dfs_zheng(int p)//第一次dfs遍历 
{	
	vis[p]=1;
	for(int i=h1[p];i!=-1;i=e1[i].next)
	{
		int now=p,to=e1[i].v;
		if(vis[to]==0)
		{	
			dfs_zheng(to);
		}
	}
	dfs_z[zheng]=p;
	zheng++; 
}
void dfs_fan(int p,int t)//第二次dfs遍历 
{		
	vis[p]=1;
	father[p]=t;
	scc[t]++;
	for(int i=h2[p];i!=-1;i=e2[i].next)
	{
		int now=p,to=e2[i].v;
		if(vis[to]==0)
		{	
			//vis[to]=1;
			dfs_fan(to,t);
		}
	}

}
void dfs3(int p,int fat)
{
	if(vis2[father[p]]==0)
	{
		ans[fat]+=scc[father[p]];
	}
	vis[p]=1;
	vis2[father[p]]=1; 
	for(int i=h2[p];i!=-1;i=e2[i].next)
	{
		if(vis[e2[i].v]==0)
			dfs3(e2[i].v,fat);
	}
}
int main(int argc, char** argv) {
	int zushu;
	scanf("%d",&zushu);
	for(int cishu=0;cishu<zushu;cishu++)
	{		
		zheng=0;fan=0;total=0,numble_of_scc=0;maxx=0;
		cin>>n>>m;
		for(int i=0;i<=n;i++)
		{
			h1[i]=-1; h2[i]=-1;
			dfs_z[i]=0; scc[i]=0;
			vis[i]=0; vis2[i]=0;
			ans[i]=0; vis3[i]=0;
			numble[i]=0; father[i]=0;
		}
		for(int i=0;i<m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			add(a,b);
		}
		for(int i=0;i<n;i++)
		{
			if(vis[i]==0)
				dfs_zheng(i);
		}
		for(int i=0;i<=n;i++)	vis[i]=0;
		int t=0;
		for(int i=n-1;i>=0;i--)
		{
			if(vis[dfs_z[i]]==0)
			{
				dfs_fan(dfs_z[i],t);
				t++;
			}
		}	
		numble_of_scc=t;
		for(int i=0;i<m;i++)
		{
			int fat1=father[e1[i].u];
			int fat2=father[e1[i].v];
			if(fat1!=fat2)
			{
				numble[fat1]++;
			}
		}
		for(int i=0;i<n;i++)
		{
			if(numble[father[i]]==0)//该点所在的联通分量的出度为0 
			{
				for(int i=0;i<=n;i++) vis[i]=0;
				for(int i=0;i<=numble_of_scc;i++) vis2[i]=0;
				dfs3(i,father[i]);
				numble[father[i]]=1;
				if(ans[father[i]]>maxx)
					maxx=ans[father[i]];
			}
		}
		printf("Case %d: %d\n",cishu+1,maxx-1);
		int tem[5000];
		int tt=0;
		for(int i=0;i<n;i++)
		{
			int fath=father[i];
			if(ans[fath]==maxx)
			{
				tem[tt]=i;
				tt++;
			}
		}
		for(int i=0;i<tt-1;i++)
		{
			printf("%d ",tem[i]);
		}

		printf("%d\n",tem[tt-1]);
	}
	return 0;
}














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值