1. 程式人生 > 實用技巧 >手銬-牛客(tarjan+dfs)

手銬-牛客(tarjan+dfs)

手銬-牛客(tarjan+dfs)

題意:

給你一個連通無向圖,保證每個點最多屬於一個簡單環,每個點度數最多為3,求這個圖有多少“手銬圖形個數”

其中“手銬圖形個數”,定義為三元組(x,y,S),其中x和y表示圖上的兩個點,S表示一條x到y的簡單路徑,而且必須滿足:

1.x和y分別在兩個不同的簡單環上

2.x所在的簡單環與路徑S的所有交點僅有x,y所在的簡單環與路徑S的所有交點僅有y。

(x,y,S)與(y,x,S)算同一個手銬;原圖:

縮點之後:

縮點之後變成方點:

由1.4構成的手銬有4個,可以走上邊也可以走下邊,

假設在兩個方點之間有x個方點,則構成的手銬數量為2^x;

tarjan縮點成無向圖之後,就變成了樹,然後進行樹形dp;

陣列維護手銬的值:

考慮u的所有子樹中點數超過1(方點)的塊走到u的方案數dp[u],以此考慮u的兒子,對於第一個兒子v,若u也是點大於1的塊,那麼第一個兒子中點數大於1的點到u構成的手銬對答案的貢獻就是dp[v],對於其他兒子,可以經過u點與之前考慮的兒子中點數大於1的點構成手銬,此時對答案的貢獻就是dp[u]⋅dp[v]。

對於dp[u]的維護,如果u點數大於1,那麼dp[u]+=2⋅dp[v],否則dp[u]+=dp[v],時間複雜度O(n+m)

將節點當成半手銬看;dp[u]表示以u為父節點的半手銬數量

1,圓形節點 dp[u]+=dp[v];

2,方形節點 dp[u]+=dp[v]*2;

ans[u]表示以u為父節點的手銬數,v是子節點;

ans[u]+=f[u]*f[v]+ans[v];

程式碼:

#include<bits/stdc++.h>
/*#include<iostream>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<queue>
#include<cstring>
*/ using namespace std; const int maxn=1e6+10; const int mod=19260817; const int inf=0x3f3f3f3f; typedef long long ll; typedef pair<int,int> pii; const int N=5e5+10; inline int read() { int x=0,w=0; char ch=getchar(); while (!isdigit(ch)) w|=ch=='-',ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar(); return w?-x:x; } struct node{ int v,next; }G[maxn<<2]; int head[maxn<<1],cnt; inline void add(int x,int y) { G[cnt]=(node){y,head[x]}; head[x]=cnt++; } int dfn[maxn],low[maxn],tot; stack<int> st; //int Stack[maxn],top; int vis[maxn]; int belong[maxn]; int num; ll f[maxn]; int cot[maxn]; void tarjan(int u,int pre) { dfn[u]=low[u]=++tot; st.push(u); vis[u]=1; for(int i=head[u]; i!=-1; i=G[i].next) { if(G[i].v==pre) continue; int v=G[i].v; if(!dfn[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); } else if(vis[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { ++num; int t; do{ t=st.top(); st.pop();//縮點 ++cot[num];//判斷方點還是圓點 vis[t]=0; belong[t]=num;//t屬於num所代表的節點 }while(u!=t); } } ll dp[maxn]; vector <int> g[maxn]; //ll tmp[maxn]; void dfs(int u,int fa)//當前節點,父節點 { dp[u]=0;//初始值 f[u]=(cot[u]>1);//1.0標記圓方點 for(int v:g[u]) { //int v=G[i].v; if(v==fa) continue;//與u相連的節點是父節點跳過這個點,剪枝 dfs(v,u); dp[u]=(1ll*dp[u]+f[v]*f[u]+dp[v])%mod;//計算手銬數 f[u]=(1ll*f[u]+f[v]*(cot[u]>1?2:1))%mod; //f[u]=f[u]>1; f[u]=(1ll*f[u]+f[v]*(f[u]==1?2:1))%mod;過不了 //f[u]的值一直在變化,如果不分開儲存,會出現問題。 } } int n,m; int main() { n=read(),m=read(); for(int i=0; i<=n; i++) head[i]=-1; for(int i=1; i<=m; i++) { int u,v; u=read(),v=read(); add(u,v); add(v,u); } for(int i=1; i<=n; i++) { if(!dfn[i]) tarjan(i,0);//縮點 } for(int i=1; i<=n; i++)//構造新圖 { for(int j=head[i]; j!=-1; j=G[j].next) { int u=belong[i],v=belong[G[j].v]; if(u!=v) g[u].push_back(v); } } dfs(1,0); cout<<dp[1]<<endl;//答案 system("pause"); return 0; }