Week8 作业

A - 区间选点 II

原题链接

http://poj.org/problem?id=1201

题目大意

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

输入

输入第一行一个整数 n n n 表示区间的个数,接下来的 n n n 行,第i行有三个数 a i , b i , c i a_i,b_i,c_i aibici,用空格隔开。 1 < = n < = 50000 , 0 < = a i < = b i < = 50000 1 <= n <= 50000, 0 <= ai <= bi <= 50000 1<=n<=50000,0<=ai<=bi<=50000 并且 1 < = c i < = b i − a i + 1 1 <= ci <= bi - ai+1 1<=ci<=biai+1

输出

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

基本思路

本题的题意可以抽象为一系列的不等式约束,因此可以考虑使用差分约束系统的方法来解决。

假设 s u m [ i ] sum[i] sum[i]是区间 [ 0 , i − 1 ] [0, i-1] [0,i1]的最少选点个数,那么题意可以描述为 s u m [ b i + 1 ] − s u m [ a i ] ≥ c i   ( 1 ≤ i ≤ n ) sum[b_i+1]-sum[a_i]\geq c_i \ (1\leq i \leq n ) sum[bi+1]sum[ai]ci (1in)

但是,除此之外,还要注意一些隐含的条件: 0 ≤ s u m [ i + 1 ] − s u m [ i ] ≤ 1 0\leq sum[i+1]-sum[i] \leq 1 0sum[i+1]sum[i]1 s u m [ i + 1 ] − s u m [ i ] ≤ 1 sum[i+1]-sum[i] \leq 1 sum[i+1]sum[i]1可以转换为 s u m [ i ] − s u m [ i + 1 ] ≥ − 1 sum[i]-sum[i+1]\geq-1 sum[i]sum[i+1]1

于是,将该问题转化为求最长路的问题,通过spfa算法即可求得答案: s u m [ max ⁡ ( b i ) + 1 ] ( 1 ≤ i ≤ n ) sum[\max(b_i)+1](1\leq i \leq n) sum[max(bi)+1](1in)

完整代码

#include<iostream>
#include<queue>
#include<set> 
using namespace std;
#define inf 500000000
struct Edge{
	int v, w, nxt;
}e[500005];
int head[50005];
int cnt=1;
int dis[50005], inq[50005];
queue<int> Q;
void add(int u, int v, int w)
{
	e[cnt].v = v;
	e[cnt].w = w;
	e[cnt].nxt = head[u];
	head[u] = cnt;
	cnt++;
}
void spfa()
{
	inq[0]=1;
	dis[0]=0;
	Q.push(0);
	while(!Q.empty())
	{
		int u = Q.front();Q.pop();inq[u]=0;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v = e[i].v;
			int w = e[i].w;
			if(dis[v] < dis[u] + w)
			{
				dis[v] = dis[u] + w;
				if(!inq[v])
				{
					inq[v]=1;
					Q.push(v);
				}
			}
		}
	}
}
int n, a, b, c;
int maxy;
int main ()
{
	cin>>n;
	
	for(int i=0;i<n;i++)
	{
		cin>>a>>b>>c;
		maxy = max(b, maxy); 
		add(a, b+1, c);
	}
	for(int i=0;i<=maxy;i++)
	{
		dis[i] = -inf;
		inq[i] = 0;
	}
	for(int i=0;i<=maxy;i++)
	{
		add(i, i+1, 0);
		add(i+1, i, -1);
	}
	spfa();
	cout<<dis[maxy+1]<<endl;
	return 0;
} 

B - 猫猫向前冲

原题链接

http://acm.hdu.edu.cn/showproblem.php?pid=1285

题目大意

N只小猫咪,从1到N编号,它们进行了M场比赛,每场比赛的胜负已知,现在要按照比赛结果对小猫咪进行排序(比赛结果具有传递性,获胜的猫咪排在当场比赛失败的猫咪前面),要求输出最小字典序的序列。

输入

输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示猫猫的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即编号为 P1 的猫猫赢了编号为 P2 的猫猫。

输出

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

基本思路

本题是一个典型的拓扑排序问题,把每只猫咪看成一个点,猫咪A赢了B看做A到B有一条有向边,从而可以建立起有向图。

维护一个数组indeg记录每个点的入度,以及一个拓扑序列数组(起初为空。本题也可直接输出,不使用拓扑序列数组),入度为0的点可取出并添加到拓扑序列的最后,并在图中删除该点及其关联的有向边。

为了使得字典序最小,可以使用一个最小堆,将所有入度为0的点加入最小堆中,每次从堆顶取出点,添加到拓扑序列的最后,并在图中删除。

完整代码

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
vector<int> G[505];
int N, M, A, B;
int indeg[505];
priority_queue<int> pq;
int main ()
{
	while(cin>>N>>M)
	{
		for(int i=0;i<=N;i++)
		{
			G[i].clear();
			indeg[i]=0;
		}
		for(int i=0;i<M;i++)
		{
			cin>>A>>B;
			G[A].push_back(B);
			indeg[B]++;
		}
		for(int i=1;i<=N;i++)
		{
			if(!indeg[i])	pq.push(-i);
		}
		bool first = true;
		while(!pq.empty())
		{
			if(!first)	cout<<" ";
			else first = false;
			int u = -pq.top();	pq.pop();
			for(int i=0, s=G[u].size();i<s;i++)
			{
				int v = G[u][i];
				indeg[v]--;
				if(!indeg[v])	pq.push(-v);
			}
			cout<<u;
		}
		cout<<endl;	
	}
	
	return 0;
} 

C - 班长竞选

题目链接

https://vjudge.net/problem/HDU-3639

题目大意

大学班级选班长,N 个同学(编号从0到N-1)均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适。求最高票数和获得最高票数的同学编号。

输入

本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。

输出

对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!

基本思路

这个题可以看做一个图论问题,每个同学当做一个点,如果同学A认为同学B合适,则认为点A和点B之间有一条有向边。

由于投票具有传递性,一个强连通分支中的点一定会获得相同的票数。而且如果存在从强连通分支S1到达强连通分支S2的有向边,那么S2中的点获得票数一定高于S1。

我们可以将每个强连通分支看成一个点,若存在从S1到S2的有向边,则看做两点之间存在一条有向边,从而实现缩点,得到一个缩点后的图sG。这个图中,只有出度为0的点对应的强连通分量才可能获得最大票数,而这类点的票数 = 所有可以到达该点的强连通分量包含的人数之和 + 该点对应强连通分量包含人数 - 1。

具体可分为以下过程:

  1. 将题中给出的关系用图G表示,并求其反图rG;
  2. 在G中通过dfs求逆后序序列;
  3. 按照逆后序序列在rG中进行dfs,找到每个强连通分量包含点数,以及每个点属于哪个强连通分量;
  4. 将每个强连通分量看做一个点,进行缩点,得到缩点后的图sG的反图rsG,并记录rsG中入度为0的点;
  5. 在rsG中以每个入度为0的点v为起点进行dfs,找到所有可达点v的点,从而计算点v对应连通分量的票数;
  6. 找到最大票数,统计获得最大票数的同学编号,并输出。

解题时遇到的坑

  1. 找强连通分支一定要使用逆后序序列在反图中寻找
  2. 缩点时要考虑重复的边的处理
  3. 缩点后的图中出度不为0的点必不可能包含答案
  4. 在缩点后的反图中搜索之前一定要把vis数组清零
  5. 可能有多个强连通分支同时为最大值

完整代码

#include<iostream>
#include<vector>
#include<set>
#define C 5005
using namespace std;
vector<int> G[C], rG[C], rsG[C];
set<pair<int, int> > S;
int f[C], sc[C], sidx[C], indeg[C];
int vis1[C], vis3[C];
int T, M, N, A, B;
int fcnt, scnt, maxscc;
int tit[C];
void dfs1(int x)
{
	vis1[x]=1;
	for(int i=0, s=G[x].size();i<s;i++)
	{
		if(!vis1[G[x][i]])	dfs1(G[x][i]);
	}
	f[fcnt++] = x;
} 
void dfs2(int x)
{
	sidx[x] = scnt;
	sc[scnt]++;
	for(int i=0, s=rG[x].size();i<s;i++)
	{
		if(sidx[rG[x][i]] == -1)	dfs2(rG[x][i]);
	}
}
void dfs3(int x, int tidx)
{
	vis3[x]=1;
	tit[tidx] += sc[x];
	for(int i=0, s=rsG[x].size();i<s;i++)
	{
		if(!vis3[rsG[x][i]])	dfs3(rsG[x][i], tidx);
	}
}

void clear()
{
	S.clear();
	for(int i=0;i<C;i++)
	{
		G[i].clear();
		rG[i].clear();
		rsG[i].clear();
		tit[i] = vis1[i] = vis3[i] = f[i] = sc[i] = indeg[i] = 0;
		sidx[i] = -1;
	}
	fcnt = scnt = maxscc = 0;
}

void read()
{
	cin>>N>>M;
	for(int i=0;i<M;i++)
	{
		cin>>A>>B;
		G[A].push_back(B);
		rG[B].push_back(A);
	}
}

void findscc()
{
	for(int i=0;i<N;i++)
	{
		if(!vis1[i])	dfs1(i);
	}
	 
	for(int i=N-1;i>=0;i--)
	{
		if(sidx[f[i]] == -1)	dfs2(f[i]), scnt++;
	}
	
}

void build_scc_graph()
{
	for(int i=0;i<N;i++)
	{
		for(int j=0,s=rG[i].size();j<s;j++)
		{
			int u = rG[i][j];
			if(sidx[i] != sidx[u] && !S.count({sidx[i], sidx[u]}))
			{
				S.insert({sidx[i], sidx[u]});
				rsG[sidx[i]].push_back(sidx[u]);
				indeg[sidx[u]]++;
			}
		}
	}
}
void statistics_and_output(int Case)
{
	for(int i=0;i<scnt;i++)
	{
		if(!indeg[i])
		{
			tit[i]=-1;
			for(int j=0;j<scnt;j++)	vis3[j]=0;
			dfs3(i, i);
			if(tit[i] > maxscc)	maxscc = tit[i];
		}
	}
	
	cout<<"Case "<<Case<<": "<<maxscc<<endl;
	bool first=true;
	for(int i=0;i<N;i++)
	{
		if(!indeg[sidx[i]] && tit[sidx[i]] == maxscc)
		{
			if(!first)	cout<<" ";
			else first = false;
			cout<<i;
		}
	}
	cout<<endl;
}
int main ()
{
	ios::sync_with_stdio(false);
	cin>>T;
	for(int h=1;h<=T;h++)
	{
		clear();
		read();
		findscc();
		build_scc_graph();
		statistics_and_output(h); 
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值