• Luogu P2495 [SDOI2011]消耗战


    题目
    我们可以很快的想到一个单次(O(n))的dp。
    然后我们注意到这个dp有很多无用的操作,比如一条没有关键点的链可以直接去掉。
    所以我们可以尝试一次dp中只管那些有用的点。
    题目给的关键点显然是要算进去的,但是只算这些的我们不足以构成一棵树,所以我们还需要任意两点的lca。
    如果我们把关键点排序,那么我们需要的就是相邻两个点的lca。
    这样我们就把需要的点给拿了出来,显然单次询问点数是(k)级别的。
    但是我们还需要一个方法来让我们把这(k)个点给建一棵树(也就是掌握它们的相对关系),不然我们没办法做树形dp。
    建出来的这个树叫做虚树,接下来我们将给出一个构建虚树的方法。
    (其实对于一棵较小的树而言,手玩构造虚树是十分容易的,但是我们需要用程序化的语言来表达它)
    我们知道建树的过程是一个dfs,而dfs的实现是栈,所以我们用栈来模拟建树。
    按dfs序枚举每个点,假设当前点是(p),栈顶元素是(x),第二个元素是(y)
    首先如果栈大小只有(1)即没有(y)元素的话我们可以直接把(p)入栈。
    然后我们求出(p,x)的lca,记为(l)
    如果(l=x),即(p)(x)的子树中的话,我们就直接把(p)入栈。(在这一道题可以不用入栈,因为覆盖一个点一定会覆盖它的子树中的所有点)
    否则我们再考虑(l)(y)的关系。
    如果(dfn_lle dfn_y),即那么此时(y)(l-x)的链上且为(x)的“父亲”(这个父亲指的是虚树中的父亲)(如果单纯只有这个限制条件的话,还有可能(y)并不在(l-x)链上,但是根据我们之后的一些操作,我们会保证这个栈里面的节点一定是一条链,这样的话(y)就一定会在(l-x)的链上了。),那么我们就把(x)退栈并且加一条((y,x))的边。然后重复这个操作。
    直到(dfn_yge dfn_l)了,我们再判断(x,l)的关系。
    如果此时(x=l),那么我们就可以直接把(p)入栈。
    否则我们连一条((l,x))的边并把(l)入栈,再把(p)入栈。
    看上去非常玄学的过程,自己画一棵树模拟一下会好理解很多,这东西太抽象了
    这样我们就把单次dp的复杂度降到了(O(k))
    然后根据实现lca的方式,我们有(O(nlog n+sum klogsum k),O(nlog n+sum k),O(n+sum k))的三种复杂度。

    #include<bits/stdc++.h>
    #define pi pair<int,int>
    #define pb push_back
    #define ll long long
    using namespace std;
    namespace IO
    {
        char ibuf[(1<<21)+1],obuf[(1<<21)+1],st[19],*iS,*iT,*oS=obuf,*oT=obuf+(1<<21);
        char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
        void Flush(){fwrite(obuf,1,oS-obuf,stdout),oS=obuf;}
        void Put(char x){*oS++=x;if(oS==oT)Flush();}
        int read(){int x=0,c=Get();while(!isdigit(c))c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return x;}
        void write(ll x){int top=0;if(!x)Put('0');while(x)st[++top]=(x%10)+48,x/=10;while(top)Put(st[top--]);Put('
    ');}
    }using namespace IO;
    ll min(ll a,ll b){return a<b? a:b;}
    const int N=250007;const ll inf=1e18;int a[N];
    namespace Graph
    {
        vector<pi>E[N];
        int T,dfn[N],fa[N],size[N],tpa[N],son[N],dep[N];ll mn[N];
        void add(int u,int v,int w){E[u].pb(pi(v,w));}
        void dfs(int u)
        {
    	size[u]=1,dep[u]=dep[fa[u]]+1,dfn[u]=++T;
    	for(auto [v,w]:E[u]) if(v^fa[u]) mn[v]=min(mn[u],w),fa[v]=u,dfs(v),size[u]+=size[v],son[u]=size[v]>size[son[u]]? v:son[u];
        }
        void dfs(int u,int tp)
        {
    	tpa[u]=tp;
    	if(son[u]) dfs(son[u],tp);
    	for(auto [v,w]:E[u]) if(v^fa[u]&&v^son[u]) dfs(v,v);
        }
        int lca(int u,int v)
        {
    	while(tpa[u]^tpa[v]) dep[tpa[u]]>dep[tpa[v]]? u=fa[tpa[u]]:v=fa[tpa[v]];
    	return dep[u]>dep[v]? v:u;
        }
        void build(){dfs(1),dfs(1,1);}
    }using namespace Graph;
    namespace ITree
    {
        int stk[N],top;vector<int>G[N];
        void push(int u)
        {
    	if(top==1) return (void)(stk[++top]=u);
    	int l=lca(u,stk[top]);
    	if(l==stk[top]) return;
    	while(top>1&&dfn[stk[top-1]]>=dfn[l]) G[stk[top-1]].pb(stk[top]),--top;
    	if(stk[top]^l) G[l].pb(stk[top]),stk[top]=l;
    	stk[++top]=u;
        }
        ll dp(int u)
        {
    	if(!G[u].size()) return mn[u];
    	ll sum=0;
    	for(int v:G[u]) sum+=dp(v);
    	return G[u].clear(),min(mn[u],sum);
        }
    }using namespace ITree;
    int main()
    {
        int n=read();
        for(int i=1,u,v,w;i<n;++i) u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
        mn[1]=inf,build();
        for(int i,k,m=read();m;--m)
        {
    	for(k=read(),i=1;i<=k;++i) a[i]=read();
    	sort(a+1,a+k+1,[](int i,int j){return dfn[i]<dfn[j];}),stk[top=1]=1;
    	for(i=1;i<=k;++i) push(a[i]);
    	while(top) G[stk[top-1]].pb(stk[top]),--top;
    	write(dp(1));
        }
        return Flush(),0;
    }
    
  • 相关阅读:
    学习笔记之Linux开发(C语言)
    Shell脚本文件操作
    学习笔记之Shell脚本的输出重定向
    学习笔记之正则表达式 (Regular Expressions)
    学习笔记之#pragma
    eclipse启动不了报错java was started but returned exit code=13
    学习笔记之APACHE ANT
    学习笔记之Linux / Shell
    学习笔记之DB2 9 Fundamentals 730
    windows远程关机重启
  • 原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/11994382.html
Copyright © 2020-2023  润新知