日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

中石油训练赛 - One-Way Conveyors(边双缩点+树上差分)

發布時間:2024/4/11 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 中石油训练赛 - One-Way Conveyors(边双缩点+树上差分) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

題目鏈接:點擊查看

題目大意:給出一張 n 個點 m 條邊的無向圖,現在需要將這張圖轉換為有向圖,并且使得 k 個可達條件成立,輸出一種構造方案

題目分析:如果在無向圖中出現環的話,那么在轉換為有向圖后,環上的點一定是可以使得互相可達的,所以我們考慮 tarjan 邊雙縮點,將整個圖縮成一棵樹,在縮邊的時候,只需要在 dfs 樹上一直加邊就可以構造環了

現在只需要考慮縮邊后的樹邊方向即可,對于一個可達條件的限制 ( x , y ) ,設是需要從 x -> y,因為在一棵樹上路徑唯一,我們先求出 lca = LCA( x , y ) ,維護兩個數組 in 和 out ,其意義分別是:

  • in[ i ] :有 in[ i ] 條邊的方向需要從 fa[ i ]?-> i
  • out[ 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(边双缩点+树上差分)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。