CSP2019 樹上的樹 口胡
阿新 • • 發佈:2020-10-31
拖了一年, 今天上午終於把這道題做出來了。
基本思路
題目要求字典序最小, 而字典序是有著天然的貪心性質的, 可以比較自然地想到要應用貪心演算法, 進一步地, 要思考 “某個數字最終停留在某個點上” 會對全域性的刪邊順序產生哪些限制。
比如有這樣一條路徑: \(s--^a--o--^b--o--^c--o--^d--t\), 如果初始時點 s 上的數字最終停留在點 t, 那麼一定滿足:
- 邊 a 是與點 s 相鄰的邊中第一個被刪去的邊
- 對於路徑上的點 o(們), 在其相鄰的邊中, b 一定緊接著 a 被刪去, c 一定緊接著 b 被刪去
- 邊 d 是與點 t 相鄰的邊中最後一個被刪去的邊
以上任意一條的任意部分不被滿足, 初始時點 s 上的數字都不會最終停留在點 t; 以上所有條件都滿足, 初始時點 s 上的數字最終就會停留在點 t。
發現以上的限制都是對於 "與某個點相鄰的邊" 之間的限制, 自然地認為要維護這個來輔助貪心演算法的判斷。
進一步的思路
有了基本的思路, 就可以思考出演算法大致的框架了。
首先從小到大列舉數字, 找出在滿足前面數字形成的限制下其可以到達的標號最小的節點, 然後把限制加上。
最樸素的找最小節點的思路就是列舉節點, 優化這個樸素思路的方法建立在如下事實上:
如果以當前數字的初始節點為根, 那麼對於任意非根節點, 其與其父親當作當前數字的最終節點所產生的限制是高度相似的
那麼就可以通過 dfs 來查詢當前列舉到的數字能夠停留的標號最小的節點。
最終思路
僅剩的問題是如何維護與一個點相鄰的邊之間的相對順序。
首先最終這些邊的順序一定是一個序列。
對於 “讓這條邊是這個點相鄰邊中 第一個/最後一個 被刪除的邊” 這種限制, 可以加哨兵, 這樣就把所有的限制都轉化成 “一個邊要緊接著另一個邊之後刪” 了。
程式碼以及註釋
(時間有限, 對於程式碼僅做了一些最基本的註釋 僅供觀賞)
#include<bits/stdc++.h> using namespace std; const int N = 2003; int n, fa[N], p[N], deg[N]; int ct, hd[N], nt[N<<1], vr[N<<1]; void ad(int x,int y) {nt[++ct]=hd[x], hd[x]=ct, vr[ct]=y; } int f[N][N], t[N][N], siz[N][N]; // 用於維護刪邊序列(鏈)的並查集, 一個集合的代表元就是刪邊序列的頭部, 用 t 記錄尾部, siz 記錄序列長度 int fid(int *F, int x) {return F[x]==x ? x: F[x]=fid(F,F[x]); } void mg(int x, int a, int b) { a=fid(f[x],a),b=fid(f[x],b); f[x][a]=b; t[x][b]=t[x][a]; siz[x][b]+=siz[x][a]; } int bst; //這個變數用來記錄當前數字能到達的標號最小的節點 void dfs(int x) { int ff=fid(f[x],fa[x]); if(fa[x]) { int ttt=fid(f[x],n+1); if( !(t[x][ff]==0 && ttt==n+1 && siz[x][ff]+siz[x][ttt]!=deg[x]+2) ) if(ff!=ttt && t[x][ttt]==n+1 && ff==fa[x]) bst=min(bst,x); } for(int i=hd[x],y=vr[i];i;i=nt[i],y=vr[i]) if(y!=fa[x]) { int tt=fid(f[x],y); if(t[x][ff]==0 && tt==n+1 && siz[x][ff]+siz[x][tt]!=deg[x]+2) continue; if( ff!=tt && ff==fa[x] && t[x][tt]==y) fa[y]=x,dfs(y); } } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d",&p[i]); if(n==1) //特判一下 { puts("1"); continue; } ct=0; memset(hd,0,sizeof hd); memset(deg,0,sizeof deg); for(int i=1,x,y; i<n; ++i)scanf("%d%d",&x,&y), ad(x,y),ad(y,x), ++deg[x],++deg[y]; for(int i=1;i<=n;++i) for(int j=0;j<=n+1;++j) f[i][j]=t[i][j]=j, siz[i][j]=1; // 以上基本都是輸入初始化 for(int i=1;i<=n;++i) { fa[p[i]]= 0; bst= n+2; dfs(p[i]); cout << bst << ' '; int y=bst, x=fa[bst], z=n+1; while(y) { mg(y,x,z); z=y, y=x, x=fa[x]; } } putchar('\n'); } return 0; }