zoukankan      html  css  js  c++  java
  • 【CF613D】Kingdom and its Cities(重拾虚树)

    点此看题面

    • 给定一棵(n)个点的树。(q)组询问,每次选出(k)个关键点。
    • 问最少删除多少非关键点才能使得关键点两两不连通。
    • (n,sum kle10^5)

    虚树

    既然这篇博客打着“重拾虚树”的名号,自然要提一提虚树了。。。

    考虑我们把所有点按(dfs)序排序,则原本所有的点+排序后相邻两点间的(LCA)就是最终虚树所需的点。

    然后我们把这些点再排序一次,并开一个栈,接着枚举每一个点。

    • 首先,把栈中所有不是当前点祖先的点弹掉。则最后的栈顶就是当前点在虚树上的父节点。
    • 然后,把当前点加到栈中。

    感觉还是比较容易理解的。

    简单的求解

    这道题建出虚树后剩下的就很简单了。

    首先很容易判无解,就是看是否有两个关键点直接相连。

    不然,我们令(g_x)表示(x)子树内仍然联通的点数,根据(x)是否为关键点分类讨论:

    • (x)为关键点。显然要把所有点断开,给答案加上(g_x),同时给(g_{anc[x]})加上(1)(无法断开当前点)。
    • (x)为非关键点。又要分三类讨论:
      • (g_x=0):无需操作。
      • (g_x=1):注意我们在这里把它断开显然不会更优,不如转送给父节点,因此给(g_{anc[x]})加上(1)
      • (g_x>1):我们不得不把当前点断开,因此给(ans)加上(1)

    代码:(O((n+sum k)logn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define LN 20
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
    namespace VirtualTree//虚树
    {
    	int cnt,o[N+5],d,dI[N+5],dO[N+5],dep[N+5],fa[N+5][LN+5];
    	I bool cmp(CI x,CI y) {return dI[x]<dI[y];}//根据dfs序排序
    	I int LCA(RI x,RI y)//倍增LCA
    	{
    		RI i;dep[x]<dep[y]&&(x^=y^=x^=y);
    		for(i=0;dep[x]^dep[y];++i) (dep[x]^dep[y])>>i&1&&(x=fa[x][i]);if(x==y) return x;
    		for(i=LN;~i;--i) fa[x][i]^fa[y][i]&&(x=fa[x][i],y=fa[y][i]);return fa[x][0];
    	}
    	I void dfs(CI x=1,CI lst=0)//预处理
    	{
    		RI i;for(dI[x]=++d,i=1;i<=LN;++i) fa[x][i]=fa[fa[x][i-1]][i-1];
    		for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
    			(dep[e[i].to]=dep[fa[e[i].to][0]=x]+1,dfs(e[i].to,x),0);dO[x]=d;
    	}
    	int vis[N+5],S[N+5],anc[N+5],g[N+5];I void Solve()//求解一次询问的答案
    	{
    		RI i;for(sort(o+1,o+cnt+1,cmp),i=1;i<=cnt;++i) vis[o[i]]=1;//vis=1表示关键点
    		#define Ins(x) (!vis[x]&&(vis[o[++cnt]=x]=2))//vis=2表示虚树上的非关键点
    		RI x,t=cnt,ans=0;for(i=1;i^t;++i) x=LCA(o[i],o[i+1]),Ins(x);//加入相邻两点LCA
    		RI T=0;for(sort(o+1,o+cnt+1,cmp),i=1;i<=cnt;++i)//排序后枚举建树
    			{W(T&&dO[o[S[T]]]<dI[o[i]]) --T;anc[i]=S[T],S[++T]=i;}//维护一个栈,求出各点在虚树上的父节点
    		for(i=2;i<=cnt;++i) if(vis[o[anc[i]]]==1)
    			if(vis[o[i]]==1&&dep[o[anc[i]]]+1==dep[o[i]]) {puts("-1");goto Cl;}//判无解
    		for(i=cnt;i;--i) vis[o[i]]==1?(ans+=g[i],++g[anc[i]]):g[i]&&(g[i]==1?++g[anc[i]]:++ans);//从叶节点向上转移
    		printf("%d
    ",ans);Cl:for(i=1;i<=cnt;++i) vis[o[i]]=g[i]=0;cnt=0;//注意多组询问要清空
    	}
    }
    int main()
    {
    	using namespace VirtualTree;
    	RI i,x,y;for(scanf("%d",&n),i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);dfs();//读入全树
    	RI Qt;scanf("%d",&Qt);W(Qt--) {for(scanf("%d",&x);x;--x) scanf("%d",o+(++cnt));Solve();}//处理询问
    	return 0;
    }
    
  • 相关阅读:
    Node.js/Python爬取网上漫画
    webpack2配置
    Node多进程相关
    文件上传更新服务相关
    自己的php函数库
    记录
    jquery 小知识点
    自己写算法---java的堆的非递归遍历
    转虚函数,写的相当好啊
    公钥私钥 ssl/tsl的概念
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF613D.html
Copyright © 2011-2022 走看看