题目
- 消耗战
虚树+DP
注意,如果一个节点为资源岛,就一定要被切断,无需讨论它的子节点是不是资源岛。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=250010;
long long inf=1e9+10;
struct EDGE{
int to,nxt,v;
}edge[N*2],ed[N];
int t[N],tt[N],dfn[N],a[N],fa[N][20],st[N],len[N],vist[N];
long long cut[N];
int stop,n,m,cnt,tot,toto;
bool cmp(int x,int y)
{
return dfn[x]<dfn[y];
}
void addedge(int x,int y,int v)
{
edge[++tot].to=y;
edge[tot].nxt=t[x];
edge[tot].v=v;
t[x]=tot;
}
void link(int x,int y)
{
ed[++toto].to=y;
ed[toto].nxt=tt[x];
tt[x]=toto;
}
void dfs(int x,long long minv)
{
int p,y;
dfn[x]=++cnt;
cut[x]=minv;
p=t[x];
while(p)
{
y=edge[p].to;
if(y!=fa[x][0])
{
fa[y][0]=x;
len[y]=len[x]+1;
dfs(y,min(minv,(long long)edge[p].v));
}
p=edge[p].nxt;
}
}
int lca(int x,int y)
{
int i;
if(len[y]<len[x])
swap(y,x);
for(i=19;i>=0;i--)
if(len[y]-len[x]>=(1<<i))
y=fa[y][i];
if(x==y)
return x;
for(i=19;i>=0;i--)
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
return fa[x][0];
}
void insert(int x)
{
int y,i;
vist[x]=1;
if(stop==1)
{
st[++stop]=x;
return;
}
y=lca(st[stop],x);
if(y==st[stop])
{
st[++stop]=x;
return;
}
while(stop>1&&dfn[y]<=dfn[st[stop-1]])
{
link(st[stop-1],st[stop]);
stop--;
}
if(y!=st[stop])
{
link(y,st[stop]);
st[stop]=y;
}
st[++stop]=x;
}
long long dp(int x)
{
/*
if(vist[x]==0)
{
tt[x]=0;
vist[x]=0;
return cut[x];
}
在开头这里直接过掉是不可行的,因为没有将子树节点的出边清零
会导致WA和TLE
*/
long long sum=0;
int p,y;
p=tt[x];
while(p)
{
y=ed[p].to;
sum+=dp(y);
p=ed[p].nxt;
}
tt[x]=0;
if(vist[x])
{
vist[x]=0;
return cut[x];
}
return min(sum,cut[x]);
}
void solve()
{
int i,k;
scanf("%d",&k);
for(i=1;i<=k;i++)
scanf("%d",&a[i]);
sort(a+1,a+k+1,cmp);
stop=0;
toto=0;
st[++stop]=1;
for(i=1;i<=k;i++)
insert(a[i]);
while(stop>1)
{
link(st[stop-1],st[stop]);
stop--;
}
printf("%lld\n",dp(1));
}
int main()
{
int x,y,v,i,j;
inf<<=20; //inf要大于全部边权和
scanf("%d",&n);
for(i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&v);
addedge(x,y,v);
addedge(y,x,v);
}
dfs(1,inf);
for(i=1;i<=19;i++)
for(j=1;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
scanf("%d",&m);
for(i=1;i<=m;i++)
solve();
return 0;
}
/*
10
1 5 13
1 9 6
2 1 19
2 4 8
2 3 91
5 6 8
7 5 4
7 8 31
10 7 9
4
2 10 6
4 5 7 8 3
3 9 4 6
4 3 4 7 9
*/
2.世界树
题解
虚树+胡搞DP
(1) 建虚树(强行插入1号节点)
(2) 求虚树上每一个点的最近关键点mina[x]
(3) 对于虚树上的边<x,y>上的点(不包括x,y),
a) 若mina[x]==mina[y],则边上所有点属于mina[x]
b) 若mina[x]!=mina[y],那么找到路径<mina[x],mina[y]>的中间点,由中间点将边划分成两部分,一部分归mina[x],一部分归mina[y]。注意,特判中心点,以及中间点不在路径<x,y>上(即mina[x]与mina[y]到x距离相等)的情况。
(4)不在虚树边上的点,归其在虚树中出现的祖先节点的最近关键点。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=3e5+10;
const int inf=1e9+10;
struct EDGE{
int nxt,to;
}edge[N*2],ed[N];
int t[N],tt[N],len[N],fa[N][20],dfn[N],size[N],msize[N],st[N],vist[N],ans[N],a[N],b[N],minv[N],mina[N];
int tot,toto,cnt,stop;
void addedge(int x,int y)
{
edge[++tot].to=y;
edge[tot].nxt=t[x];
t[x]=tot;
}
void link(int x,int y)
{
ed[++toto].to=y;
ed[toto].nxt=tt[x];
tt[x]=toto;
}
void dfs(int x)
{
int p,y;
dfn[x]=++cnt;
size[x]=1;
p=t[x];
while(p)
{
y=edge[p].to;
if(fa[x][0]!=y)
{
fa[y][0]=x;
len[y]=len[x]+1;
dfs(y);
size[x]+=size[y];
}
p=edge[p].nxt;
}
}
int lca(int x,int y)
{
int i;
if(len[x]>len[y])
swap(x,y);
for(i=19;i>=0;i--)
if(len[y]-len[x]>=(1<<i))
y=fa[y][i];
if(x==y)
return x;
for(i=19;i>=0;i--)
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
return fa[x][0];
}
void insert(int x)
{
int y;
vist[x]=1;
y=lca(x,st[stop]);
if(y==st[stop])
{
st[++stop]=x;
return;
}
while(stop>1&&dfn[y]<=dfn[st[stop-1]])
{
link(st[stop-1],st[stop]);
stop--;
}
if(st[stop]!=y)
{
link(y,st[stop]);
st[stop]=y;
}
st[++stop]=x;
}
void dp1(int x) //找最近关键点
{
int p,y;
msize[x]=1;
minv[x]=inf;
if(vist[x])
{
minv[x]=0;
mina[x]=x;
}
p=tt[x];
while(p)
{
y=ed[p].to;
dp1(y);
if(minv[y]+len[y]-len[x]<minv[x]||(minv[y]+len[y]-len[x]==minv[x]&&mina[x]>mina[y]))
{
minv[x]=minv[y]+len[y]-len[x];
mina[x]=mina[y];
}
p=ed[p].nxt;
}
ans[x]=0;
vist[x]=0;
}
void solve_mid(int x,int y,int my) //my为<x,y>路径上x的儿子
{
int xx,yy,tmp,tmp2,i;
xx=mina[x];
yy=mina[y];
tmp=lca(xx,yy);
for(i=19;i>=0;i--)
{
if(len[xx]-len[tmp]>=(1<<i))
{
xx=fa[xx][i];
yy=fa[yy][i];
}
}
tmp=len[yy]-len[xx]-1;
if(xx==yy) //中间点在路径以外
{
ans[mina[y]]+=size[my]-size[y];
return;
}
tmp2=tmp/2;
for(i=19;i>=0;i--)
{
if(tmp2>=(1<<i))
{
tmp2-=(1<<i);
yy=fa[yy][i];
}
}
if(tmp%2)
{
ans[mina[x]]+=size[my]-size[fa[yy][0]];
ans[mina[y]]+=size[yy]-size[y];
if(mina[x]<mina[y])
ans[mina[x]]+=size[fa[yy][0]]-size[yy];
else
ans[mina[y]]+=size[fa[yy][0]]-size[yy];
}
else
{
ans[mina[x]]+=size[my]-size[yy];
ans[mina[y]]+=size[yy]-size[y];
}
}
void dp2(int x)
{
int p,y,tmp,my,i;
p=tt[x];
while(p)
{
y=ed[p].to;
if(minv[x]+len[y]-len[x]<minv[y]||(minv[x]+len[y]-len[x]==minv[y]&&mina[x]<mina[y]))
{
minv[y]=minv[x]+len[y]-len[x];
mina[y]=mina[x];
}
my=y;
for(i=19;i>=0;i--)
if(len[my]-len[x]-1>=(1<<i))
my=fa[my][i];
msize[x]+=size[my];
if(mina[x]==mina[y])
ans[mina[x]]+=size[my]-size[y];
else
solve_mid(x,y,my);
dp2(y);
p=ed[p].nxt;
}
ans[mina[x]]+=size[x]-msize[x]+1; //不在虚树上的点
tt[x]=0;
}
bool cmp(int x,int y)
{
return dfn[x]<dfn[y];
}
void solve()
{
int nn,i,l;
scanf("%d",&nn);
for(i=1;i<=nn;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(a+1,a+nn+1,cmp);
l=1;
if(a[1]==1)
{
l=2;
vist[1]=1;
}
st[1]=1;
stop=1;
toto=0;
for(i=l;i<=nn;i++)
insert(a[i]);
while(stop>1)
{
link(st[stop-1],st[stop]);
stop--;
}
dp1(1);
dp2(1);
for(i=1;i<=nn;i++)
printf("%d ",ans[b[i]]);
printf("\n");
}
int main()
{
int n,x,y,i,j,m;
scanf("%d",&n);
for(i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
addedge(x,y);
addedge(y,x);
}
dfs(1);
for(i=1;i<=19;i++)
for(j=1;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
scanf("%d",&m);
for(i=1;i<=m;i++)
solve();
return 0;
}