暑假笔记·拓扑排序

拓扑排序

由于博主太懒,详细讲解见详细讲解

例题:P1137 旅行计划

原题地址

题目描述

小明要去一个国家旅游。这个国家有N个城市,编号为1至N,并且有M条道路连接着,小明准备从其中一个城市出发,并只往东走到城市i停止。

所以他就需要选择最先到达的城市,并制定一条路线以城市i为终点,使得线路上除了第一个城市,每个城市都在路线前一个城市东面,并且满足这个前提下还希望游览的城市尽量多。

现在,你只知道每一条道路所连接的两个城市的相对位置关系,但并不知道所有城市具体的位置。现在对于所有的i,都需要你为小明制定一条路线,并求出以城市ii为终点最多能够游览多少个城市。

输入输出格式

输入格式:
第1行为两个正整数N,M。

接下来M行,每行两个正整数x,y,表示了有一条连接城市x与城市y的道路,保证了城市x在城市y西面。

输出格式:
N行,第i行包含一个正整数,表示以第i个城市为终点最多能游览多少个城市。

输入输出样例

输入样例#1:
5 6
1 2
1 3
2 3
2 4
3 4
2 5
输出样例#1:
1
2
3
4
3

说明:
对于110%的数据,N≤100000,M≤200000。

代码:
还记得DP需要满足无后效性的原则。如果不是在拓扑序后进行DP,会破坏无后效性。正是因为拓扑序u在前,v在后的性质,这才选择使用拓扑排序,毕竟它的代码实现很轻松,而且运行时间也不差。

至于拓扑序具体实施过程,就是把入度为0(就是没有边把它作为终点)的点入队,并加入拓扑序。之后断掉以这个点为起点的边,即将这些边的终点的入度减一,直到队为空就好。

入度即统计多少条边以此点为终点。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;
struct road
{
    int to,nxt;
}qq[200020];
queue<int>ww;
int dp[100010],ts[100010],head[100010],ru[100010];
//ru[]为入度,dp[]为答案,ts[]为拓扑排序后的数组。
int n,m,tot=0,sum=0;
void add(int u,int v)
{
    qq[++tot].to=v;
    qq[tot].nxt=head[u];
    head[u]=tot;
}
void topsort()
{
    int i,g,t;
    for(i=1;i<=n;i++)
    {
        if(!ru[i])
        {
            ww.push(i);
            ts[++sum]=i;
        }
    }
    while(!ww.empty())
    {
        g=ww.front();
        ww.pop();
        for(i=head[g];i;i=qq[i].nxt)
        {
            t=qq[i].to;
            ru[t]--;
            if(!ru[t])
            {
                ts[++sum]=t;
                ww.push(t);
            }
        }
    }
}
int main()
{
    int i,j,k,a,b,h;
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        add(a,b);
        ru[b]++;
    }
    topsort();//DP前进行拓扑排序,因为要保持
    for(i=1;i<=n;i++) dp[i]=1;
    for(i=1;i<=n;i++)
    {
        k=ts[i];
        for(j=head[k];j;j=qq[j].nxt)
        {
            h=qq[j].to;
            dp[h]=max(dp[h],dp[k]+1);
        }
    }
    for(i=1;i<=n;i++)
        printf("%d\n",dp[i]);
    return 0;
}

例题:D. Gourmet choice

原题地址

在这里插入图片描述
在这里插入图片描述
**人话翻译: **给定两个数组及其每个元素的大小关系,求能否构造这两个数组(要求构造所用的最大数最小)。

代码:
对相同的数字进行并查集,然后用拓扑排序。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<vector>
#include<utility>
#include<set>
#include<stack>
using namespace std;
int n,m,fa[2020],ru[2020],ss[2020],head[2020],tot=0,vis[2020],vi[2020][2020];
char ww[1010][1010];
queue<int>tp;
struct node
{
	int to,nxt;
}qq[1000010];
void add(int a,int b)
{
	if(vi[a][b]) return;
	qq[++tot].to=b;
	qq[tot].nxt=head[a];
	head[a]=tot;
	vi[a][b]=1;
	ru[b]++;
}
int father(int x)
{
	if(x==fa[x]) return x;
	else return fa[x]=father(fa[x]);
}
void unit(int a,int b)
{
	fa[father(a)]=father(b);
}
int main ()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	int i,j,k,t;
	cin>>n>>m;
	for(i=1;i<=n;i++)
		scanf("%s",ww[i]+1);
	for(i=1;i<=n+m;i++) fa[i]=i;
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			if(ww[i][j]=='=') unit(i,n+j);
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			if(ww[i][j]=='<') {add(father(i),father(j+n));}
			if(ww[i][j]=='>') {add(father(j+n),father(i));}
		}
	}
	for(i=1;i<=n+m;i++) if(!ru[father(i)]&&!vis[father(i)]) {tp.push(fa[i]);ss[fa[i]]=1;vis[fa[i]]=1;}
	while(!tp.empty())
	{
		k=tp.front();
		tp.pop();
		for(i=head[k];i;i=qq[i].nxt)
		{
			t=qq[i].to;
			ru[t]--;
			if(!ru[t])
			{
				ss[t]=ss[k]+1;
				tp.push(t);
				vis[t]=1;
			}
		}
	}
	for(i=1;i<=m+n;i++)
		if(!vis[fa[i]])
		{
			cout<<"No"<<endl;
			return 0;
		}
	cout<<"Yes\n";
	for(i=1;i<n;i++)
		cout<<ss[father(i)]<<' ';
	cout<<ss[father(n)]<<endl;
	for(i=1;i<m;i++)
		cout<<ss[father(i+n)]<<' ';
	cout<<ss[father(m+n)]<<endl;
    return 0;
}

本题博主犯了不少错误,调试了好久才调出来。

E. Andrew and Taxi

原题地址

在这里插入图片描述
在这里插入图片描述

**人话翻译: **一个有向图(不一定联通),给你m条有向边,每条边都有权值,如果确定一个代价v,则权值小于等于v的边都可以被修改。求使得图变得没有环所需要的最小v,改变边的个数,和一种方案。

代码:
参考博客

拓扑找环,运用二分找权值。值得注意的是本题测试数据貌似不强。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<set>
#include<string>
#include<vector>
#include<queue>
using namespace std;
#define inf 0x3f3f3f3f
const int maxn = 1e5+7;
int n,m,in[maxn],top[maxn];
vector<int> ee[maxn];
struct node
{
	int s,e,w;
}ed[maxn];
bool pd(int mid)
{
	int cnt=0;
	queue<int> q;
	for(int i=1;i<=n;++i)
		ee[i].clear();//vector数组存图
	memset(in,0,sizeof(in));
	for(int i=1;i<=m;++i)
	{
		if(ed[i].w>mid)
		{
			ee[ed[i].s].push_back(ed[i].e);
			in[ed[i].e]++;
		}
	}
	for(int i=1;i<=n;++i)
	{
		if(!in[i])
		{
			q.push(i);
			top[i]=++cnt;
		}
	}
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=0;i<(int)ee[u].size();++i)
		{
			int t=ee[u][i];
			--in[t];
			if(!in[t])	{q.push(t);top[t]=++cnt;}
		}
	}
	for(int i=1;i<=n;++i)
	{
		if(in[i])	return false;
	}
	return true;
}
int main()
{
	std::ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=m;++i)
	{
		cin>>ed[i].s>>ed[i].e>>ed[i].w;
	}
	int l=0,r=inf,res;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(pd(mid))	r=mid-1,res=mid;
		else l=mid+1;
	}
	pd(res); //top序列是由res跑出 
	vector<int> ans;
	for(int i=1;i<=m;++i)
	{
		if(ed[i].w<=res && top[ed[i].s] > top[ed[i].e])
		{
			ans.push_back(i);
		}
	}
	cout<<res<<" "<<(int)ans.size()<<endl;
	for(int i=0;i<(int)ans.size();++i){
		cout<<ans[i]<<" ";
	}
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值