[SDOI2011]消耗战
作者:互联网
O(n^2)的dp很显然
以1为根
f[x]表示把以为根的子树都砍断的最小代价
f[x]=∑min(f[y],e[i].val)
但是对于K=500000的
发现,每次用到的关键点并不多,是所有关键点和dfn序相邻关键点的LCA,
这启示我们用虚树!
虚树的边权就是路径上链的最小值
总点数是2*K的
至于虚树怎么建?
考虑树欧拉序
虚树的欧拉序的相对顺序显然不变
把所有的点的进栈出栈的两个dfn序都放在数组里排序
然后用栈模拟建出树即可
(当然,由于dfs本身就是栈的操作,所以用栈模拟dfs就不用建树了)
O(K*logN)
代码:
// luogu-judger-enable-o2 #include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=250000+5; const int inf=0x3f3f3f3f; int n,m; struct node{ int nxt,to; int val; }e[2*N],b[2*N]; int hd[N],cnt; int pre[N],tot; void add(int x,int y,int z){ e[++cnt].nxt=hd[x]; e[cnt].to=y; e[cnt].val=z; hd[x]=cnt; } int dfn[N],df; int dfn2[N]; int fdfn[2*N]; int fa[N][20],mi[N][20]; int dep[N]; void dfs(int x,int d){ dep[x]=d; dfn[x]=++df; fdfn[df]=x; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa[x][0]) continue; fa[y][0]=x; mi[y][0]=e[i].val; dfs(y,d+1); } dfn2[x]=++df; fdfn[df]=-x; } int lca(int x,int y){ if(dep[x]<dep[y]) swap(x,y); //cout<<" x "<<x<<" : "<<dep[x]<<" "<<fa[x][0]<<endl; ///cout<<" y "<<y<<" : "<<dep[y]<<" "<<fa[y][0]<<endl; for(reg j=19;j>=0;--j){ if(dep[fa[x][j]]>=dep[y]){ x=fa[x][j]; } } if(x==y) return x; for(reg j=19;j>=0;--j){ if(fa[x][j]!=fa[y][j]){ x=fa[x][j],y=fa[y][j]; } } return fa[x][0]; } int fin(int x,int anc){//x!=anc int ret=inf; for(reg j=19;j>=0;--j){ if(fa[x][j]&&dep[fa[x][j]]>=dep[anc]){ ret=min(ret,mi[x][j]); x=fa[x][j]; } } //cout<<x<<" "<<anc<<" ret "<<ret<<endl; return ret; } int mem[2*N]; int id[4*N],num; bool cmp(int x,int y){ return dfn[x]<dfn[y]; } int vis[N],has[N]; int sta[4*N],top; ll f[N]; void sol(int x,int fafa){ f[x]=0; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fafa) continue; sol(y,x); f[x]+=min(f[y],(ll)e[i].val); } if(has[x]) f[x]=inf; } int main(){ rd(n); int x,y,z; for(reg i=1;i<n;++i){ rd(x);rd(y);rd(z); add(x,y,z);add(y,x,z); } memset(mi,inf,sizeof mi); dfs(1,1); for(reg j=1;j<=19;++j){ for(reg i=1;i<=n;++i){ fa[i][j]=fa[fa[i][j-1]][j-1]; mi[i][j]=min(mi[i][j-1],mi[fa[i][j-1]][j-1]); } } int k; memset(hd,0,sizeof hd); cnt=0; rd(m); while(m--){ rd(k); for(reg i=1;i<=k;++i) rd(mem[i]),has[mem[i]]=1; sort(mem+1,mem+k+1,cmp); top=0; tot=k; for(reg i=k-1;i>=1;--i){ if(mem[i]==mem[i+1]) continue; int anc=lca(mem[i],mem[i+1]); // cout<<mem[i]<<" and "<<mem[i+1]<<" anc "<<anc<<endl; mem[++tot]=anc; } mem[++tot]=1; num=0; for(reg i=1;i<=tot;++i){ // cout<<" i "<<mem[i]<<" "<<dfn[mem[i]]<<" "<<dfn2[mem[i]]<<endl; id[++num]=dfn[mem[i]];id[++num]=dfn2[mem[i]]; } sort(id+1,id+num+1); num=unique(id+1,id+num+1)-id-1; top=0; cnt=0; x=0; for(reg i=1;i<=num;++i){ if(fdfn[id[i]]>0){ x=fdfn[id[i]]; if(top) add(sta[top],x,fin(x,sta[top])); sta[++top]=x; }else{ sta[top--]=0; } } sol(1,0); printf("%lld\n",f[1]); for(reg i=1;i<=tot;++i){ has[mem[i]]=0; hd[mem[i]]=0; } cnt=0; } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/31 12:55:30 */
总结:
思路还是直观自然的
我们发现实际用到的点不是很多。考虑值保留关键点。就用到了虚树
虚树的建立,这里欧拉序起到了很大的作用。
因为有欧拉序是可以还原出来一个树的
没有怎么接触过的
标签:dep,cnt,fa,int,虚树,SDOI2011,消耗战,reg 来源: https://www.cnblogs.com/Miracevin/p/10347241.html