I_am_Accepted
2022-02-05 18:36:16
我和许多同学赛后一直讨论这道题,有的说网络流,有的说拓扑,有的说不可做,而我用了应该是最简单的思路,所以有底气来发题解主要是我比较闲。
这篇题解写在官方题解出来之前,所以如果官方用了非常巧妙的算法,那么请各位抽烟那么算我输。
建图:
请永远记住:边是奶牛,点是麦片,
当然,由于边要存较多的信息(奶牛的编号,正反方向等),请使用前向星。(我之前写的是 vector,
算法开始。
我们将每一个连通块(不是强连通分量)分别考虑。现在考察连通块
必然是棵树(忽略定向)。
构造(即证明):
由于
必然有一个基环树(环可能是重边)的子图(忽略定向)。
证明:
由于麦片一共就
接下来 基环树子图上每条边都有对应点 的构造 即证明了挨饿的奶牛数
构造:
任取一基环树子图
然后我们将每个基环上的点为根的子树分别 DFS,类似情况(一)求解。
算法结束。
总时间
不带注释 2.27KB 代码提交
注释版:
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define fir first
#define sec second
#define mkp make_pair
#define pb emplace_back
#define For(i,j,k) for(int i=j;i<=k;i++)
#define Fe(x,y) for(int x=h[y];x;x=e[x].nxt) //for:edge 跟铁没关系 qwq
#define N 100100
struct edge{
int to,nxt,id;
bool fw;//is it forward?
}e[2*N];//正反都要
int n,m,tot=1,h[N],sum,f[N];//tot:从 1 开始,方便找反向边 h:前向星的头 sum:该联通块的边数 f[i]:搜索树中 i 的父亲
bool vis[N],out[N],ban[N],ins[N];//vis[i]:i 所在连通块是否被算过 out[i]:边(奶牛)是否被输出了 ban[i]:点 i DFS 是否被达咩了 ins[i]:是否在搜索树中的祖先集合
vector<int> v,con,cy;//v:待输出的方案 con:当前连通块 cy:基环上依次的点
pair<int,int> key;//环的两端(环上相邻)
inline void print(int x){v.pb(x);out[x]=1;}
inline void adde(int x,int y,int id,bool z){e[++tot]={y,h[x],id,z};h[x]=tot;}//加边
void see(int rt){//查看整个连通块
vis[rt]=1;con.pb(rt);
Fe(i,rt){
sum++;
if(!vis[e[i].to]) see(e[i].to);
}
}
void tree(int rt,int fa){//DFS tree
Fe(i,rt){
int to=e[i].to;
if(to==fa) continue;
print(e[i].id);
tree(to,rt);
}
}
void solve1(int x){//树(边有向)
int root=-1;bool flag;
for(int i:con){//找无入度的点
flag=1;
Fe(j,i)if(!e[j].fw){flag=0;break;}
if(flag){root=i;break;}
}
tree(root,0);
}
bool dfs(int x,int fr,int fa){//找到一个环(可能是重边,但不能是正向边和对应的反向边)
ins[x]=1;f[x]=fa;
Fe(i,x){
if((i^fr)==1) continue;//fa来的反向边,达咩
if(ins[e[i].to]){
key=mkp(x,e[i].to);
return true;
}
if(dfs(e[i].to,i,x)) return true;
}
ins[x]=0;return false;
}
void subt(int rt){//基环树的子树求解
Fe(i,rt){
if(ban[e[i].to]) continue;
print(e[i].id);
ban[e[i].to]=1;
subt(e[i].to);
}
}
inline bool fang(int x,int y){Fe(i,x)if(e[i].to==y)return e[i].fw;}//x=>y 的方向
inline int cow(int x,int y){Fe(i,x)if(e[i].to==y && !out[e[i].id])return e[i].id;}//x=>y 的奶牛编号
void solve2(int x){//最优秀为基环树(基环可能是重边)
cy.clear();
dfs(x,0,0);//True
int tmp=key.fir;
while(true){
cy.pb(tmp);
if(tmp==key.sec) break;
tmp=f[tmp];
}
int len=cy.size();
if(!fang(cy[0],cy[1])){//翻转环(镜像)
key=mkp(cy[0],cy[1]);
For(i,2,len-1) cy[i-2]=cy[i];
cy[len-2]=key.fir;
cy[len-1]=key.sec;
reverse(cy.begin(),cy.end());
}
For(i,0,len-1) print(cow(cy[i],cy[(i+1)%len])); //环上解决
for(int i:cy) ban[i]=1;
for(int i:cy) subt(i);//子树解决
}
signed main(){IOS;
cin>>n>>m;
int x,y;
For(i,1,n){
cin>>x>>y;
adde(y,x,i,1);//正向边 (sec->fir)
adde(x,y,i,0);//反向边 (fir->sec)
}
For(i,1,m){
if(vis[i]) continue;
sum=0;con.clear();
see(i);
sum>>=1;//双向边数量折半
if((int)con.size()==sum+1) solve1(i);
else solve2(i);
}
int ans=0;
For(i,1,n) if(!out[i]) ans++;//饥饿的
cout<<ans<<endl;
for(int i:v) cout<<i<<endl;//输出有的吃了
For(i,1,n) if(!out[i]) cout<<i<<endl;//输出挨饿的
return 0;}