1. 程式人生 > >【XSY2667】摧毀圖狀樹 貪心 堆 DFS序 線段樹

【XSY2667】摧毀圖狀樹 貪心 堆 DFS序 線段樹

printf con 線段 string def body 暴力 clu while

題目大意

  給你一棵有根樹,有\(n\)個點。還有一個參數\(k\)。你每次要刪除一條長度為\(k\)\(k\)個點)的祖先-後代鏈,問你最少幾次刪完。現在有\(q\)個詢問,每次給你一個\(k\),問你答案是多少。

  \(n\leq {10}^5,k\leq {10}^9\)

題解

  設\(l\)為這棵樹的葉子個數,顯然當\(k>\)樹的深度時答案都是\(l\)

  下面要證明:答案是\(O(l+\frac{n-l}{k})\)的。

  我們從下往上貪心,每次選擇一個未被覆蓋的深度最深的點,覆蓋這個點網上的一條鏈。我們把這些選擇的點稱為關鍵點。把所有關鍵點到父親的連邊斷開。

  包含根的那個連通塊和葉子節點的貢獻是\(O(l)\)

的。

  對於其他連通塊,顯然深度最深的點到關鍵點的距離是\(k-1\),所以連通塊的大小不小於\(k\)。又因為每個連通塊只有一個關鍵點,所以這部分的貢獻是\(O(\frac{n-l}{k})\)的。

  對於每個\(k\),從葉子節點的上一層關鍵點開始暴力做就行了。查詢一個點是否被覆蓋就用DFS序+線段樹(查詢這個點的子樹深度最淺的點深度是多少)。

  時間復雜度:\(O(n\log^2 n)\)(調和級數求和再加上數據結構的\(\log\)

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib> #include<ctime> #include<utility> #include<cmath> #include<functional> #include<queue> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int,int> pii; typedef pair<ll,ll> pll; void sort(int &a,int
&b) { if(a>b) swap(a,b); } void open(const char *s) { #ifndef ONLINE_JUDGE char str[100]; sprintf(str,"%s.in",s); freopen(str,"r",stdin); sprintf(str,"%s.out",s); freopen(str,"w",stdout); #endif } int rd() { int s=0,c; while((c=getchar())<'0'||c>'9'); do { s=s*10+c-'0'; } while((c=getchar())>='0'&&c<='9'); return s; } void put(int x) { if(!x) { putchar('0'); return; } static int c[20]; int t=0; while(x) { c[++t]=x%10; x/=10; } while(t) putchar(c[t--]+'0'); } int upmin(int &a,int b) { if(b<a) { a=b; return 1; } return 0; } int upmax(int &a,int b) { if(b>a) { a=b; return 1; } return 0; } vector<int> g[100010]; int f[100010][20]; int st[100010]; int ed[100010]; int d[100010]; int ti; int maxd; int leaves; int s[100010]; vector<int> c[100010]; void dfs(int x,int fa,int dep) { maxd=max(maxd,dep); f[x][0]=fa; d[x]=dep; st[x]=++ti; int i; for(i=1;i<=17;i++) f[x][i]=f[f[x][i-1]][i-1]; int num=0; s[x]=0x7fffffff; for(auto v:g[x]) if(v!=fa) { dfs(v,x,dep+1); s[x]=min(s[x],s[v]+1); num++; } if(!num) { s[x]=0; leaves++; } ed[x]=ti; } int jump(int x,int d) { int i; for(i=17;i>=0;i--) if(d&(1<<i)) x=f[x][i]; return x; } namespace seg { int ls[200010]; int rs[200010]; int s[200010]; int rt,n; void add(int &p,int x,int v,int l,int r) { if(!p) { p=++n; ls[p]=rs[p]=0; s[p]=0x7fffffff; } if(l==r) { s[p]=v; return; } int mid=(l+r)>>1; if(x<=mid) add(ls[p],x,v,l,mid); else add(rs[p],x,v,mid+1,r); s[p]=0x7fffffff; if(ls[p]) upmin(s[p],s[ls[p]]); if(rs[p]) upmin(s[p],s[rs[p]]); } int query(int p,int L,int R,int l,int r) { if(!p) return 0x7fffffff; if(L<=l&&R>=r) return s[p]; int res=0x7fffffff; int mid=(l+r)>>1; if(L<=mid) upmin(res,query(ls[p],L,R,l,mid)); if(R>mid) upmin(res,query(rs[p],L,R,mid+1,r)); return res; } } struct cmp { int operator ()(int a,int b) const { return d[a]<d[b]; } }; priority_queue<int,vector<int>,cmp> q; int ans[100010]; int k; int main() { open("a"); int n; scanf("%d",&n); int i,x,y; for(i=1;i<n;i++) { scanf("%d%d",&x,&y); g[x].push_back(y); g[y].push_back(x); } dfs(1,0,1); for(i=1;i<=n;i++) c[s[i]].push_back(i); for(i=1;i<maxd;i++) { k=i; int res=leaves; for(auto v:c[i]) q.push(v); seg::rt=seg::n=0; while(!q.empty()) { x=q.top(); q.pop(); if(s[x]<=k-1) continue; if(seg::query(seg::rt,st[x],ed[x],1,n)<=d[x]+k-1) continue; seg::add(seg::rt,st[x],d[x],1,n); res++; if(d[x]>k) q.push(jump(x,k)); } ans[i]=res; } int q; scanf("%d",&q); while(q--) { scanf("%d",&x); if(x>=maxd) printf("%d\n",leaves); else printf("%d\n",ans[x]); } return 0; }

【XSY2667】摧毀圖狀樹 貪心 堆 DFS序 線段樹