week8作业/差分约束/拓扑排序/强连通图

A,D-区间选点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

题目分析:

对于区间选点问题,可以采用按右端点排序然后贪心的算法,但在这里采用另一种方法,差分约束系统。

  • 因为对于给定一个数轴上的n个区间,要求在数轴上选取最少的点使得第i个区间里至少有ci个点,所以可以将各区间抽象成图的数据结构,对每一个区间的左右端点抽象成一条有向边的两个端点,对需要满足的点的个数抽象成从一个端点到另一个端点的dis,即用dis[i]数组来表示[0,i]之间选点的个数。
  • 对于第i个区间[𝑎i,𝑏i]需要满足dis[bi]−dis[ai-1]≥𝑐i(闭区间)。(因为 0 <= ai <= bi <= 50000,即端点是从0开始的,那么dis[i-1]中的i-1就会出现负数-1,所以在这里为了避开这个问题,将区间整体右移一位)
  • 同时又因为每个点要么取,要么不取,所以又可得到0<=dis[i]-dis[i-1]<=1。
  • 因为存在>=,<=,所以有两种转换形式,要么将>=都转换成<=求最短路径,要么将<=都转换成>=求最长路;在这里选择第二种转换形式,即最终不等式组为:
  • dis[b+1]-dis[a]>=ci;
  • dis[i+1]-dis[i]>=0;
  • dis[i]-dis[i+1]>=-1;

代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=5e5+5;
const int inf=1e8+8;
struct edge
{
	int to,next,w;
}e[maxn];
int head[maxn],tol;

void add(int u,int v,int w)//前向星存储图结构 
{
	e[++tol].to=v;
	e[tol].next=head[u];
	e[tol].w=w;
	head[u]=tol;
}

int sum[maxn];//表示数轴上[s,t]之间选点的个数 
int dis[maxn],vis[maxn];
int n;int minn;int maxx;
queue<int> q; 
void spfa(int s,int t)//最长路 
{
	while(q.size()) q.pop();
	for(int i=s;i<=t+1;i++)
	{
		dis[i]=-inf;
		vis[i]=0;
		sum[i]=0;
	}
	q.push(s);
	dis[s]=0;
	vis[s]=1;
	while(!q.empty())
	{
		int x=q.front();q.pop();
		vis[x]=0;sum[x]++;
		if(sum[x]>maxx-minn+1)//判断终点
		{
			return ;
		}
		for(int i=head[x];i;i=e[i].next)
		{
			int y=e[i].to;int w=e[i].w;
			if(dis[y]<dis[x]+w)
			{
				dis[y]=dis[x]+w;
				if(!vis[y])
				{
					q.push(y);
					vis[y]=1;
				}
			}
		}
	}
}
int main()
{
	memset(head,0,sizeof(head));
	tol=0;
	scanf("%d",&n);
	minn=inf;maxx=0;
	for(int i=0;i<n;i++)//dis[b]-dis[a]<=ci
	{
		int a,b,c;//区间左右端点
		scanf("%d%d%d",&a,&b,&c);
		minn=min(minn,a);
		maxx=max(maxx,b);
		add(a,b+1,c);
	}
	for(int i=minn;i<=maxx;i++)//0<=dis[i]-dis[i-1]<=1
	{
		add(i,i+1,0);
		add(i+1,i,-1);
	}
	spfa(minn,maxx+1);
	printf("%d\n",dis[maxx+1]);	
		
	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

题目分析:

猫猫们之间的胜负关系可以构成一张有向无环图 :P1 赢了P2 等价于结点P1到结点P2有一条边 , P1 赢了P2意味着在最终的名次序列中P1要在P2的前面。

所以问题转化为求字典序最小的拓扑序 ,将胜负关系以有向图的形式存储在前向星结构中,记录每个点的入度,将入度为0的点组成一个集合S , 每次从S里面取出一个顶点u放入L,然后遍历顶点u的所有边(u, v), 并删除之,并判断如果该边的另一个顶点v,如果在移除这一条边后入度为0, 那么就将这个顶点放入集合S中。不断地重复取出顶点然后重复这个过程…… 任一时刻队列中所有结点意味着已经可以确定名次关系并且互相之间没有依赖,最后当集合为空后,就检查图中是否存在任何边。如果有,那么这个图 一定有环路,否者返回L,L中顺序就是拓扑排序的结果。依次输出L中结果即可。

(因为最后要求出字典序最小的拓扑序,所以使用优先队列替换队列C++优先队列的使用方法与描述

代码:

#include<iostream>
#include<stdio.h>
#include<queue>
#include<functional>
#include<string.h>
using namespace std; 
const int maxn=1e3+5;
struct edge
{
	int to,next;
}e[maxn];
int head[maxn],tol;
int inside[maxn];int ans[maxn];int cnt;
int n,m;
void add(int x,int y)
{
	e[++tol].to=y;
	e[tol].next=head[x];
	head[x]=tol;
}
priority_queue<int,vector<int>,greater<int> > q;//优先队列
void toposort()//拓扑排序
{
	while(q.size()) q.pop();
	for(int i=1;i<=n;i++)
	{
		if(inside[i]==0)
		{
			q.push(i);
		}
	}
	while(!q.empty())
	{
		int x=q.top(); q.pop();
		ans[cnt]=x;cnt++;
		for(int i=head[x];i;i=e[i].next)
		{
			int y=e[i].to;
			inside[y]--;
			if(inside[y]==0)
			{
				q.push(y);
			}	
		}
	}
	if(cnt==n)
	{
		for(int i=0;i<n;i++)
		{
			if(i<n-1)
			{
				printf("%d ",ans[i]);
			}
			else
			{
				printf("%d\n",ans[i]);
			}
		}	
	}	
}
int main()
{
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		memset(head,0,sizeof(head));
		memset(inside,0,sizeof(inside));
		tol=0;
		cnt=0;
		for(int i=0;i<m;i++)
		{
			int p1,p2;
			scanf("%d%d",&p1,&p2);
			add(p1,p2);
			inside[p2]++;
		}
		toposort();
	}
	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

题目分析:

该题要求得票最多的同学及编号,因为票选具有传递性,所以需要使用到SCC强连通图。

  • 先Kosarsju模拟对N个数据进行SCC的划分与判断, 第一遍 dfs 确定原图的逆后序序列, 第二遍 dfs 在反图中按照逆后序序列进行遍历 ,每次由起点遍历到的点即构成一个 SCC,记录每一个点所属的SCC编号,及每一个SCC中的点的数目。
  • 缩点,即将属于一个SCC中的点,用相应的SCC 编号来整体描述,构成一张缩点之后的新图,对新图进行反向存储(反图),并记录每个点的入度。
  • (因为最终答案一定出现在出度为0的SCC中,但是为了计算方便,存储反图,对每个入度为0的点进行dfs,计算其能达到的sum(scc[j])总的点数即可,其中sum(scc[j])分为两部分,该点自身在的那个SCC,ans+=scc[i]-1(去除自己)以及其他能到达的SCC,ans+=sum[j])
  • 最终找到票数最多的票,并标记得到该数量票数的SCC,输出所有被标记的SCC中的编号即可。

代码:

#include<iostream>
#include<stdio.h>
#include<string.h> 
#include<algorithm>
using namespace std;
const int maxn=3e5+5;
struct edge
{
	int to,next;
}e[maxn],e1[maxn];//e1原图,e2反图 
int inside[maxn];
int head[maxn],tol;int head1[maxn],tol1;
int n,m;
void add(int x,int y)
{
	e[++tol].to=y;
	e[tol].next=head[x] ;
	head[x]=tol;
} 
void add1(int x,int y)
{
	e1[++tol1].to=y;
	e1[tol1].next=head1[x] ;
	head1[x]=tol1;
}
int vis[maxn];
int dfn[maxn];int dcnt;//后序序列 dfn[i]dfs后序列中第i个点 
void dfs(int x)
{
	vis[x]=1;
	for(int i=head[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(!vis[y])
		{
			dfs(y);
		}
	}
	dfn[++dcnt]=x;
} 
int c[maxn],scnt;int sum[maxn];//c[i]i号点所在的scc编号 sum[i]记录每个scc的数的数目 
void dfs1(int x)
{
	c[x]=scnt;
	sum[scnt]++; 
	for(int i=head1[x];i;i=e1[i].next )
	{
		int y=e1[i].to;
		if(!c[y])
		{
			dfs1(y);
		}
	}
} 
void kosaraju()
{
	dcnt=scnt=0;
	memset(c,0,sizeof(c));
	memset(vis,0,sizeof(vis));
	memset(sum,0,sizeof(sum));
	for(int i=0;i<n;i++)//第一遍dfs 
	{
		if(!vis[i]) dfs(i); 
	}
	for(int i=n;i>=1;i--)//第二遍dfs 
	{
		if(!c[dfn[i]])
		{
			++scnt; 
			dfs1(dfn[i]);
		}	 
	}
}
int ans[maxn]; 
void dfs_ans(int x,int o)
{
	vis[x]=1;
	for(int i=head1[x];i;i=e1[i].next)
	{
		int y=e1[i].to;
		if(!vis[y])
		{
			vis[y]=1;
			ans[o]+=sum[y];
			dfs_ans(y,o);
		}
	}
}
int re[maxn]; 
int main()
{
	int T;
	scanf("%d",&T);
	int num=0;
	while(T--)
	{
		num++;
		memset(head,0,sizeof(head));tol=0;
		memset(head1,0,sizeof(head1));tol1=0;
		
		memset(re,0,sizeof(re));
		scanf("%d%d",&n,&m);
		for(int i=0;i<m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			add(a,b);//原图 
			add1(b,a);//反向图,将边进行反向 
		}
		kosaraju();

		memset(head1,0,sizeof(head1));tol1=0;
		memset(inside,0,sizeof(inside));
	
		for(int i=0;i<n;i++)//缩点 
		{
			for(int j=head[i];j;j=e[j].next)
			{
				int y=e[j].to;
				if(c[i]!=c[y])//不属于同一个scc 
				{
					add1(c[y],c[i]);
					inside[c[i]]++;
				}
			}
		}

		memset(ans,0,sizeof(ans));int result=0;

		for(int i=1;i<=scnt;i++)
		{
			if(inside[i]==0)
			{	
				memset(vis,0,sizeof(vis));
				dfs_ans(i,i);//其他scc中的点 
				ans[i]+=sum[i]-1;//去除自身的当前scc中的点
			}
			if(ans[i]>result)
			{
				result=ans[i];
			}		
		}
		memset(vis,0,sizeof(vis));
		for(int i=1;i<=scnt;i++)
		{
			if(ans[i]==result)
			{
				vis[i]=1;
			}
		}
		
		printf("Case %d: %d\n",num,result);//输出数据编号及最高票数
		int r=0;
		for(int i=0;i<n;i++)
		{
			if(vis[c[i]])
			{
				re[r]=i;
				r++;
			}
		} 

		for(int i=0;i<r;i++)
		{
			if(i<r-1)
			{
				printf("%d ",re[i]);
			}
			else
			{
				printf("%d\n",re[i]);
			}
		}		
	} 
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值