小C与桌游
题目背景
小C是一个热爱桌游的高中生,现在他被一个桌游难住了,快来帮帮他!
题目描述
这个桌游的地图可以被抽象成一个(n)个点,(m)条边的有向无环图(不保证连通),小C在这个地图上行走,小C能走到某个点当且仅当能够到达这个点的所有点都已经被小C走到。小C会走到每个点恰好(1)次,并且他能走到哪些点与他当前所在的点没有关系(即可以走到与当前所在的点没有连边的点,只要满足之前的条件)。
小C每走到一个标号比之前走到的点都大的点,他就会有(frac{1}{2})的概率从对手那里拿到(1)块筹码,有(frac{1}{2})的概率给对手(1)块筹码,双方初始各有(1919810)个筹码。
小C的运气时好时坏,所以他希望你帮他计算出:
- 在最优情况下,即他每次都能从对手那里拿到筹码时,他采取最优的行走方式能得到的筹码数。
- 在最劣情况下,即对手每次都能从他那里拿到筹码时,他采取最优的行走方式会失去的筹码数。
输入格式
第一行两个正整数(n,m)。
接下来(m)行,每行两个正整数(u, v),表示地图上有一条有向边((u, v)),不保证无重边。
输出格式
输出两行,每行一个正整数,第一行表示最优情况下小C能拿到的筹码数,第二行表示最劣情况下小C会失去的筹码数。
样例输入
3 2 1 2 1 3
样例输出
3 2
样例解释
最优情况下的行走方式是(1-2-3),最劣情况下的行走方式是(1-3-2)。
计分方式
对于每一个测试点:
- 如果你输出格式错误或者两行都不正确,将会得到(0)分。
- 如果你的输出第一行正确,第二行错误或第二行正确,第一行错误,将会得到这个测试点(40\%)的分数。
- 否则,你将会得到这个测试点(100\%)的分数。
数据范围
对于(20\%)的数据,(1 le n,m le 10)。
对于(40\%)的数据,(1 le n,m le 2000)。
对于(100\%)的数据,(1 le n, m le 5 imes 10^5),(1 le u, v le n)。
题解
题意:给你一张(n)个点的有向无环图,这个图的每种拓扑排序有一个价值,每当在拓扑序列中加入一个节点编号比已加入的所有节点编号都打的节点的时候,这个拓扑序的价值就会加一。
要求这张图拓扑排序中最大的价值和最小的价值。
既然求最大的价值和最小的价值,因为枚举每一种拓扑序时间复杂度很高(其实是太麻烦,懒得打),所以我们在这里考虑贪心的算法。
因为只有在序列中加入比之前加入的所有节点编号都大的节点时价值才会升高,所以如果有一个可以加入的节点编号小于已加入的最大的节点编号(max),那么加入这个节点的作用是扩展能加入的节点。(显然的)
那么我们很容易想到最大的价值是从小到大加入拓扑序,最小的价值是从大到小加入拓扑序。
这里注意一下:这里提到的从小到大加入以及从大到小加入的前提是(max)编号以下已经没有可以加入的点了。
更多细节可以看代码理解。
上代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,u,v;
int s[500009],ss[500009];
int q1[500009],l1,q2[500009],l2;
int mx,mn,sss;
int ans1,ans2=1;
bool k[500009];
struct aa{
int to,nxt;
}p[500009];
int h[500009],len;
void add(int u,int v){
p[++len].to=v;
p[len].nxt=h[u];
h[u]=len;
}
void dd(int u,int mx){//删除节点u以及删除它之后可以删除的编号比max小的节点
for(int j=h[u];j;j=p[j].nxt){
s[p[j].to]--;
if(p[j].to<mx && s[p[j].to]==0)
dd(p[j].to,mx);
}
}
void d2(int u,int mx){//删除节点u并且把大于max的节点加入队列中
for(int j=h[u];j;j=p[j].nxt){
s[p[j].to]--;
if(s[p[j].to]==0){
if(p[j].to<mx) d2(p[j].to,mx);
else q2[++l2]=p[j].to;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int j=1;j<=m;j++){
scanf("%d%d",&u,&v);
add(u,v);
s[v]++;
}
for(int j=1;j<=n;j++)
ss[j]=s[j];
for(int j=1;j<=n;j++)
if(s[j]==0){
ans1++;
dd(j,j);
}
for(int j=1;j<=n;j++)
s[j]=ss[j];
for(int j=1;j<=n;j++)
if(s[j]==0){q1[++l1]=j;sss++;k[j]=1;mx=j;}
for(int j=1;j<=l1;j++)
d2(q1[j],mx);
while(l2!=0){
ans2++;
l1=l2;
for(int j=1;j<=l2;j++){
q1[j]=q2[j];
mx=max(mx,q2[j]);
}
l2=0;
for(int j=1;j<=l1;j++)
d2(q1[j],mx);
}
printf("%d
%d",ans1,ans2);
return 0;
}