題解 回家
阿新 • • 發佈:2021-06-12
考場上陣列開小了90pts爆成60pts
考慮求出點雙,縮點後重新建邊,則新圖一定形成一棵樹
dfs跑一遍,從n點回溯時記錄經過的必經點就行
但是有個坑點:一個點可能不止屬於一個點雙,所以重新連邊時不能用並查集判斷兩點是否屬於同一點雙
所以我改題是就又雜湊又動態陣列亂搞
其實不用那麼麻煩,有結論:只有割點可能同時屬於多個點雙
那建邊時把割點孤立出來,建立一張只有割點和縮點後點雙的圖就好了
而且這樣建完圖後一定是一棵樹,dfs跑就好了
vcc,dcc的板子建議再背一遍
tarjan就是「一算有八板,八板各不同」@Yubai
Code:
#include <bits/stdc++.h> using namespace std; #define INF 0x3f3f3f3f #define N 500010 #define ll long long #define ld long double #define usd unsigned #define ull unsigned long long //#define int long long #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++) char buf[1<<21], *p1=buf, *p2=buf; inline int read() { int ans=0, f=1; char c=getchar(); while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();} while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();} return ans*f; } int n, m; int head[N], size, dfn[N], low[N], dep, now, h[N<<2], size2, sta[N], top, lim; int bel[N<<2], lookup[N<<2]; bool cut[N<<2], con[N<<2], searched[N<<2]; vector<int> dcc[N]; struct edge{int from, to, next;}e[N<<2]; struct edge2{int to, next;}e2[N<<2]; inline void add(int s, int t) {edge *k=&e[++size]; k->from=s; k->to=t; k->next=head[s]; head[s]=size;} inline void add2(int s, int t) {edge2 *k=&e2[++size2]; k->to=t; k->next=h[s]; h[s]=size2;} void tarjan(int u, int fat) { //cout<<"tarjan "<<u<<endl; dfn[u]=low[u]=++dep; sta[++top]=u; int cnt=0; for (int i=head[u],v; i; i=e[i].next) { v = e[i].to; if (!dfn[v]) { tarjan(v, u); low[u] = min(low[u], low[v]); if (dfn[u]<=low[v]) { if (i!=1 || ++cnt>1) cut[u]=1; ++now; int t; do { t=sta[top--]; dcc[now].push_back(t); //, cout<<now<<' '<<t<<endl; bel[t]=now; } while (t!=v) ; dcc[now].push_back(u); } } else low[u]=min(low[u], dfn[v]); } } void dfs(int u) { //cout<<"dfs "<<u<<endl; searched[u]=1; if (u==bel[n]) {con[u]=1; return ;} for (int i=h[u],v; i; i=e2[i].next) { v = e2[i].to; //cout<<v<<endl; if (!searched[v]) { dfs(v); if (con[v]) { con[u]=1; if (u>lim && u!=bel[1]) sta[++top]=lookup[u]; } } } } signed main() { #ifdef DEBUG freopen("1.in", "r", stdin); #endif int T; T=read(); while (T--) { n=read(); m=read(); size=1; dep=0; now=0; size2=1; top=0; memset(head, 0, sizeof(int)*(n+10)); memset(dfn, 0, sizeof(int)*(n+10)); memset(low, 0, sizeof(int)*(n+10)); memset(cut, 0, sizeof(int)*(n+10)); memset(h, 0, sizeof(h)); memset(con, 0, sizeof(int)*(n+10)); memset(searched, 0, sizeof(int)*(n+10)); memset(bel, 0, sizeof(int)*(n+10)); memset(lookup, 0, sizeof(lookup)); for (int i=1; i<=n; ++i) dcc[i].clear(); for (int i=1,u,v; i<=m; ++i) {u=read(); v=read(); if (u==v) continue; add(u, v); add(v, u);} tarjan(1, 0); top=0; #if 0 cout<<"cut: "; for (int i=2; i<=size; ++i) cout<<cut[i]<<' '; cout<<endl; cout<<"dfn: "; for (int i=1; i<=n; ++i) cout<<dfn[i]<<' '; cout<<endl; cout<<"low: "; for (int i=1; i<=n; ++i) cout<<low[i]<<' '; cout<<endl; #endif lim = now; for (int i=1; i<=n; ++i) if (cut[i]) bel[i]=++now, lookup[now]=i; for (int i=1; i<=now; ++i) for (int j=0,t; j<dcc[i].size(); ++j) { t = dcc[i][j]; if (cut[t]) add2(i, bel[t]), add2(bel[t], i); //, cout<<"add "<<i<<' '<<bel[t]<<endl; } //cout<<lim<<' '<<now<<' '<<size<<' '<<size2<<endl; dfs(bel[1]); //for (int i=1; i<=top; ++i) cout<<sta[i]<<' '; cout<<endl; sort(sta+1, sta+top+1); //top = unique(sta+1, sta+top+1)-sta-1; printf("%d\n", top); for (int i=1; i<=top; ++i) printf("%d ", sta[i]); printf("\n"); } return 0; }
但是還有另一種更簡單的做法:
我們縮點是為了使圖形成一棵樹,方便dfs回溯找路徑上的必經點
但我們要找的實際上是1-n路徑上的必經點,
而一個點必經的充分必要條件是它在這條路徑上且它是割點
那就沒有必要縮點了,先tarjan預處理出dfn[\ ]和low[\ ],同時記錄下每個點在搜尋樹上的父親,
然後直接從n回溯到1節點,同時判斷下經過的每個點是不是割點就好
搜尋樹的構建順序無關緊要,因為如果一個點在路徑上且是割點,那無論搜尋樹怎麼構建它都是必經的
Code:
#include <bits/stdc++.h> using namespace std; #define INF 0x3f3f3f3f #define N 200100 #define ll long long #define ld long double #define usd unsigned #define ull unsigned long long //#define int long long #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++) char buf[1<<21], *p1=buf, *p2=buf; inline int read() { int ans=0, f=1; char c=getchar(); while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();} while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();} return ans*f; } int n, m; int head[N], size, dfn[N], low[N], dep, now, back[N], sta[N], top; struct edge{int to, next;}e[N<<2]; inline void add(int s, int t) {edge *k=&e[++size]; k->to=t; k->next=head[s]; head[s]=size;} void tarjan(int u, int fa) { //cout<<"tarjan "<<u<<endl; dfn[u]=low[u]=++dep; for (int i=head[u],v; i; i=e[i].next) { v = e[i].to; if (!dfn[v]) { tarjan(v, u); back[v]=u; low[u] = min(low[u], low[v]); } else low[u] = min(low[u], dfn[v]); } } signed main() { #ifdef DEBUG freopen("1.in", "r", stdin); #endif int T; T=read(); while (T--) { n=read(); m=read(); size=1; dep=0; now=0; top=0; memset(head, 0, sizeof(int)*(n+10)); memset(dfn, 0, sizeof(int)*(n+10)); memset(low, 0, sizeof(int)*(n+10)); memset(back, 0, sizeof(int)*(n+10)); for (int i=1,u,v; i<=m; ++i) {u=read(); v=read(); if (u==v) continue; add(u, v); add(v, u);} tarjan(1, 0); top=0; #if 0 cout<<"dfn: "; for (int i=1; i<=n; ++i) cout<<dfn[i]<<' '; cout<<endl; cout<<"low: "; for (int i=1; i<=n; ++i) cout<<low[i]<<' '; cout<<endl; #endif for (int t=n,k; t!=1; t=k) { k=back[t]; if (dfn[k]<=low[t] && k!=1) sta[++top]=k; //, cout<<k<<' '<<dfn[k]<<' '<<low[t]<<endl; } sort(sta+1, sta+top+1); printf("%d\n", top); for (int i=1; i<=top; ++i) printf("%d ", sta[i]); printf("\n"); } return 0; }