【XSY2667】摧毁图状树 贪心 堆 DFS序 线段树
題目大意
給你一棵有根樹,有\(n\)個(gè)點(diǎn)。還有一個(gè)參數(shù)\(k\)。你每次要刪除一條長度為\(k\)(\(k\)個(gè)點(diǎn))的祖先-后代鏈,問你最少幾次刪完。現(xiàn)在有\(q\)個(gè)詢問,每次給你一個(gè)\(k\),問你答案是多少。
\(n\leq {10}^5,k\leq {10}^9\)
題解
設(shè)\(l\)為這棵樹的葉子個(gè)數(shù),顯然當(dāng)\(k>\)樹的深度時(shí)答案都是\(l\)。
下面要證明:答案是\(O(l+\frac{n-l}{k})\)的。
我們從下往上貪心,每次選擇一個(gè)未被覆蓋的深度最深的點(diǎn),覆蓋這個(gè)點(diǎn)網(wǎng)上的一條鏈。我們把這些選擇的點(diǎn)稱為關(guān)鍵點(diǎn)。把所有關(guān)鍵點(diǎn)到父親的連邊斷開。
包含根的那個(gè)連通塊和葉子節(jié)點(diǎn)的貢獻(xiàn)是\(O(l)\)的。
對于其他連通塊,顯然深度最深的點(diǎn)到關(guān)鍵點(diǎn)的距離是\(k-1\),所以連通塊的大小不小于\(k\)。又因?yàn)槊總€(gè)連通塊只有一個(gè)關(guān)鍵點(diǎn),所以這部分的貢獻(xiàn)是\(O(\frac{n-l}{k})\)的。
對于每個(gè)\(k\),從葉子節(jié)點(diǎn)的上一層關(guān)鍵點(diǎn)開始暴力做就行了。查詢一個(gè)點(diǎn)是否被覆蓋就用DFS序+線段樹(查詢這個(gè)點(diǎn)的子樹深度最淺的點(diǎn)深度是多少)。
時(shí)間復(fù)雜度:\(O(n\log^2 n)\)(調(diào)和級數(shù)求和再加上數(shù)據(jù)結(jié)構(gòu)的\(\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_JUDGEchar 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);elseadd(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);elseprintf("%d\n",ans[x]);}return 0; }轉(zhuǎn)載于:https://www.cnblogs.com/ywwyww/p/8513756.html
總結(jié)
以上是生活随笔為你收集整理的【XSY2667】摧毁图状树 贪心 堆 DFS序 线段树的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Tornado-Lesson05-模版继
- 下一篇: C 指针常量 和常量指针 指向常量的指针