題目鏈接:點擊查看
題目大意:給出一張 n 個點 m 條邊的無向圖,現在需要將這張圖轉換為有向圖,并且使得 k 個可達條件成立,輸出一種構造方案
題目分析:如果在無向圖中出現環的話,那么在轉換為有向圖后,環上的點一定是可以使得互相可達的,所以我們考慮 tarjan 邊雙縮點,將整個圖縮成一棵樹,在縮邊的時候,只需要在 dfs 樹上一直加邊就可以構造環了
現在只需要考慮縮邊后的樹邊方向即可,對于一個可達條件的限制 ( x , y ) ,設是需要從 x -> y,因為在一棵樹上路徑唯一,我們先求出 lca = LCA( x , y ) ,維護兩個數組 in 和 out ,其意義分別是:
in[ i ] :有 in[ i ] 條邊的方向需要從 fa[ i ]?-> iout[ i ] :有 out[ i ] 條邊的方向需要從 i -> fa[ i ]?
對于一條邊 ( x , y ) ,可以用樹鏈剖分,分別維護 x -> lca?和 lca -> y?這兩段的邊上的 in 數組和 out 數組,最后對于某條邊來說,如果 in[ i ] 和 out[ i ] 皆不為 0 的話,那么顯然是無解的,否則根據上面的兩種情況確定一下
不過馬哥有個很棒的想法,就是用樹上差分來代替樹鏈剖分,對于一個要求 ( x , y )?來說,只需要讓 out[ x ] ++ , out[ lca ] -- , in[ y ] ++ , in[ lca ] -- ,最后一遍 dfs 統計一下樹上前綴和就好了,時空復雜度以及代碼難度相對樹鏈剖分來說都優秀太多了
剩下的就是實現了,耐心碼就好了,沒什么坑點
代碼:
?
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=1e4+100;const int M=2e5+100;set<pair<int,int>>ans,st;struct Egde
{int to,next;
}edge1[M],edge2[M];int head1[N],head2[N],low[N],dfn[N],c[N],out[N],in[N],num,cnt1,cnt2,dcc,n,m;bool bridge[M],vis[M];void addedge1(int u,int v)
{edge1[cnt1].to=v;edge1[cnt1].next=head1[u];head1[u]=cnt1++;
}void addedge2(int u,int v)
{edge2[cnt2].to=v;edge2[cnt2].next=head2[u];head2[u]=cnt2++;
}void tarjan(int u,int in_edge)
{dfn[u]=low[u]=++num;for(int i=head1[u];i!=-1;i=edge1[i].next){int v=edge1[i].to;if(!dfn[v]){tarjan(v,i);low[u]=min(low[u],low[v]);if(low[v]>dfn[u])bridge[i]=bridge[i^1]=true;}else if(i!=(in_edge^1))low[u]=min(low[u],dfn[v]);}
}void dfs(int u)
{c[u]=dcc;for(int i=head1[u];i!=-1;i=edge1[i].next){int v=edge1[i].to;if(bridge[i])continue;if(!vis[i]&&!vis[i^1]){ans.insert(make_pair(u,v));vis[i]=vis[i^1]=true;}if(c[v])continue; dfs(v);}
}void solve()
{for(int i=1;i<=n;i++)//找橋 if(!dfn[i])tarjan(i,0);for(int i=1;i<=n;i++)//縮點 if(!c[i]){dcc++;dfs(i);}
}void build()//縮點+連邊
{solve();for(int i=2;i<cnt1;i+=2){int u=edge1[i^1].to;int v=edge1[i].to;if(c[u]==c[v])continue;addedge2(c[u],c[v]);addedge2(c[v],c[u]);}
}int deep[N],dp[N][20],limit;void dfs1(int u,int fa,int dep)//樹上倍增
{deep[u]=dep;dp[u][0]=fa;for(int i=1;i<=limit;i++)dp[u][i]=dp[dp[u][i-1]][i-1];for(int i=head2[u];i!=-1;i=edge2[i].next){int v=edge2[i].to;if(v!=fa)dfs1(v,u,dep+1);}
}int LCA(int x,int y)
{if(deep[x]<deep[y])swap(x,y);for(int i=limit;i>=0;i--)if(deep[x]-deep[y]>=(1<<i))x=dp[x][i];if(x==y)return x;for(int i=limit;i>=0;i--)if(dp[x][i]!=dp[y][i]){x=dp[x][i];y=dp[y][i];}return dp[x][0];
}void dfs2(int u,int fa)//統計樹上差分的前綴和
{for(int i=head2[u];i!=-1;i=edge2[i].next){int v=edge2[i].to;if(v==fa)continue;dfs2(v,u);in[u]+=in[v];out[u]+=out[v];}
}void init()
{ans.clear();limit=log2(n)+1;cnt1=2;cnt2=num=dcc=0;memset(head2,-1,sizeof(head2));memset(head1,-1,sizeof(head1));memset(low,0,sizeof(low));memset(dfn,0,sizeof(dfn));memset(bridge,false,sizeof(bridge));memset(c,0,sizeof(c));memset(out,false,sizeof(out));memset(in,false,sizeof(in));memset(vis,false,sizeof(vis));
}int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);scanf("%d%d",&n,&m);init();while(m--){int u,v;scanf("%d%d",&u,&v);addedge1(u,v);addedge1(v,u);}build();dfs1(1,0,0);int x,y;int k;scanf("%d",&k);while(k--){int x,y;scanf("%d%d",&x,&y);if(c[x]==c[y])//屬于同一個連通分量 continue;int lca=LCA(c[x],c[y]);//c[x]->c[y]out[c[x]]++;out[lca]--;in[c[y]]++;in[lca]--;}dfs2(1,-1);bool flag=true;for(int i=1;i<=dcc;i++)//枚舉每個點與其父節點的關系 {if(out[i]&&in[i]){flag=false;break;}if(out[i])st.insert(make_pair(i,dp[i][0]));elsest.insert(make_pair(dp[i][0],i));}if(!flag)return 0*puts("No");for(int i=2;i<cnt1;i++){int u=edge1[i].to,v=edge1[i^1].to;if(c[u]==c[v])continue;if(st.count(make_pair(c[u],c[v])))ans.insert(make_pair(u,v));}puts("Yes");for(auto it:ans)printf("%d %d\n",it.first,it.second);return 0;
}
?
總結
以上是生活随笔為你收集整理的中石油训练赛 - One-Way Conveyors(边双缩点+树上差分)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。