拓扑排序
由于博主太懒,详细讲解见详细讲解
例题: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;
}