洛谷4606 SDOI2018戰略遊戲(圓方樹+虛樹)
阿新 • • 發佈:2018-12-22
QWQ深受其害
當時在現場是真的絕望......
現在再重新來看這個題
QWQ
根據題目所說,我們可以發現,對於每一個集合中的節點,我們實際上就是要求兩兩路徑上的割點的數目
考慮到又是關於點雙的題目,而且在圖上,我們並沒有很好的辦法去做。
這時候就要考慮建出來圓方樹,然後我們對於圓方樹 的每個點,維護他到根的路徑上的圓點個數
那麼,我們該怎麼求兩兩路徑的割點總數呢(一看到資料範圍,就想到虛樹了啊)
冷靜分析一下,發現真的直接把虛樹中的點弄出來就是合法的,因為兩兩的路徑一定會通過\(lca\),而建出來虛樹,正好只會保留有用的邊。
那麼我們對於虛樹上的兩個相連的點,他們的邊權的就是兩個點到根的圓點個數的差。
這裡有一個關於虛樹的
奇技淫巧
為了忽略\(1\)號節點對答案的影響,我們可以直接選擇把所有關鍵點的\(lca\)放到虛樹的棧裡面當第一個元素,而不是1
最後對於一次詢問,我們只需要求虛樹的邊權和即可(還需要特判根的問題)
// luogu-judger-enable-o2 #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #include<map> #include<set> #define mk makr_pair #define ll long long using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();} while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return x*f; } const int maxn = 4e5+1e2; const int maxm = 2*maxn; int point[maxn],nxt[maxm],to[maxm]; int point1[maxn],nxt1[maxm],to1[maxm]; int cnt,cnt1; int n,m; int dfn[maxn],deep[maxn],low[maxn]; int f[maxn][20]; int st[maxn],tot,top; int num; int a[maxn],k; int dnf[maxn],size[maxn]; void addedge1(int x,int y) { nxt1[++cnt1]=point1[x]; to1[cnt1]=y; point1[x]=cnt1; } void addedge(int x,int y) { nxt[++cnt]=point[x]; to[cnt]=y; point[x]=cnt; } void tarjan(int x,int fa) { dfn[x]=low[x]=++tot; st[++top]=x; for (int i=point1[x];i;i=nxt1[i]) { int p=to1[i]; if (p==fa) continue; if (!dfn[p]) { tarjan(p,x); low[x]=min(low[x],low[p]); if (low[p]>=dfn[x]) { num++; addedge(num,x); addedge(x,num); do{ addedge(num,st[top]); addedge(st[top],num); top--; }while (st[top+1]!=p); } } else low[x]=min(low[x],dfn[p]); } } int tmp; void dfs(int x,int fa,int dep) { int now=0; if (x<=n) now++; deep[x]=dep; dnf[x]=++tmp; size[x]=size[fa]+now; for (int i=point[x];i;i=nxt[i]) { int p = to[i]; if(p==fa) continue; f[p][0]=x; dfs(p,x,dep+1); } } void init() { for (int j=1;j<=19;j++) for (int i=1;i<=num;i++) { f[i][j]=f[f[i][j-1]][j-1]; } } int go_up(int x,int d) { for (int i=0;i<=19;i++) { if ((1<<i) & d) x=f[x][i]; } return x; } int lca(int x,int y) { if(deep[x]>deep[y]) x=go_up(x,deep[x]-deep[y]); else y=go_up(y,deep[y]-deep[x]); if (x==y) return x; for (int i=19;i>=0;i--) { if (f[x][i]!=f[y][i]) { x=f[x][i]; y=f[y][i]; } } return f[x][0]; } bool cmp(int a,int b) { return dnf[a]<dnf[b]; } struct xvtree{ int point[maxn],nxt[maxm],to[maxm]; int val[maxm]; int cnt; int s[maxn],top; int sum; void init() { cnt=0; sum=0; } void addedge(int x,int y,int w) { //cout<<x<<" ** "<<y<<" "<<w<<endl; nxt[++cnt]=point[x]; to[cnt]=y; val[cnt]=w; point[x]=cnt; } void dfs(int x,int fa) { for (int &i=point[x];i;i=nxt[i]) { int p = to[i]; if (p==fa) continue; sum+=val[i]; dfs(p,x); } } int solve() { init(); top=1; sort(a+1,a+1+k,cmp); int root=a[1]; for (int i=2;i<=k;i++) root=lca(root,a[i]); s[top]=root; for (int i=1;i<=k;i++) { int l = lca(s[top],a[i]); if (l!=s[top]) { while (top>1) { if (dnf[s[top-1]]>dnf[l]) { addedge(s[top-1],s[top],size[s[top]]-size[s[top-1]]);//size表示他在圓方樹上和根的路徑上的圓點個數 top--; } else { if (dnf[s[top-1]]==dnf[l]) { addedge(s[top-1],s[top],size[s[top]]-size[s[top-1]]); top--; break; } else { addedge(l,s[top],size[s[top]]-size[l]); s[top]=l; break; } } } } if (s[top]!=a[i]) s[++top]=a[i]; } while (top>1) { addedge(s[top-1],s[top],size[s[top]]-size[s[top-1]]); top--; } dfs(root,0); if(root<=n) sum++; return sum; } }; xvtree xv; int t; int main() { t=read(); while (t--) { tmp=0;tot=0;top=0; cnt=0;cnt1=0; xv.init(); memset(point,0,sizeof(point)); memset(point1,0,sizeof(point1)); memset(f,0,sizeof(f)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(dnf,0,sizeof(dnf)); n=read(),m=read(); num=n; for (int i=1;i<=m;i++) { int x=read(),y=read(); addedge1(x,y); addedge1(y,x); } for (int i=1;i<=n;i++) { if (!dfn[i]) tarjan(i,0); } dfs(1,0,1); init(); int q=read(); for (int i=1;i<=q;i++) { k=read(); for (int j=1;j<=k;j++) a[j]=read(); xv.init(); cout<<xv.solve()-k<<"\n"; } } return 0; }