【暑假集训模拟DAY8】树上问题
前言
树上问题以为还算了解
但现在看来确实缺的也很多,包括换根DP,树的重心,环套树等等,都不是特别熟悉
期望:0+30+50+0=80pts
实际:0+10+50+0=60pts
中间打疫苗中断也有点影响(回来就有点不想写题了)
T2标准的暴力分是20,然而我又写了一些特判的部分分(比如判链,判两点相同),反而把暴力搞炸了...
题解
T1 reform
理解倒是不难,却感觉无从下手
对于每一个点,如果原来不是重心,那么考虑切掉一个大小>n/2的部分的一部分(好像有点绕)
规定1号点为根,那么对于节点u切掉的部分可能在u子树里,也可能在u子树外,需要分别求出
如果在u子树里,比较好求出能切掉的最大的但是大小不超过n/2的真子树,同时为了下一步求出子树外的最大部分,也要记录子树内能切掉的次大部分
对于子树外的最大部分,有3种转移方式,具体见代码
还有这题用memset清空会TLE,测试了一下觉得是因为memset每次会把N大小的数组全部清空,如果T过大就会TLE;如果循环到n清空,由于n的和不会太大,所以不会TLE
总之测试数据多的时候慎用memset
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 4e5+10;
int head[N<<1],ecnt=-1;
int ins[N][2],outs[N];
int siz[N],n,T;
//inside,outside
//ins[u][0]表示最大子树,ins[u][1]表示次大子树
void init()
{
memset(head,-1,sizeof(head));
ecnt=-1;
}
struct edge
{
int nxt,to;
}a[N<<1];
void add(int x,int y)
{
a[++ecnt].nxt=head[x];
a[ecnt].to=y;
head[x]=ecnt;
}
void dfs1(int u,int fa)//这里是回溯的时候转移ins
{
siz[u]=1;
for(int i=head[u];~i;i=a[i].nxt)
{
int v=a[i].to;
if(v==fa) continue;
dfs1(v,u);
siz[u]+=siz[v];
int tmp;
if(siz[v]<=n/2) tmp=siz[v];
else tmp=ins[v][0];
if(ins[u][0]<=tmp) ins[u][1]=ins[u][0],ins[u][0]=tmp;
else if(ins[u][1]<=tmp) ins[u][1]=tmp;
}//更新需要维护的2个信息:子树内最大值、次大值,为下面求子树外最大值做准备
}
void dfs2(int u,int fa)//这里是从上往下的时候转移outs
{
for(int i=head[u];~i;i=a[i].nxt)
{
int v=a[i].to;
if(v==fa) continue;
//outs[v]有三个转移方式:子树内最大值,子树内次大值,子树外最大值(均为u的)
if(n-siz[v]<=n/2) outs[v]=n-siz[v];
else outs[v]=outs[u];
if(ins[v][0]==ins[u][0]||siz[v]==ins[u][0]) outs[v]=max(outs[v],ins[u][1]);
else outs[v]=max(outs[v],ins[u][0]);
dfs2(v,u);
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
//init();
ecnt=-1;
for(int i=1;i<=n;i++) outs[i]=ins[i][1]=ins[i][0]=siz[i]=0,head[i]=head[i+n]=-1;
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
dfs1(1,-1);
dfs2(1,-1);
for(int i=1;i<=n;i++)
{
bool flag=0;
//printf("i=%d\n",i);
if(n-siz[i]<=n/2)
{
for(int j=head[i];~j;j=a[j].nxt)
{
int v=a[j].to;
if(siz[i]>=siz[v]&&siz[v]-ins[v][0]>n/2)//写下标的时候都要仔细思考
flag=1,printf("0 ");
}
if(!flag) printf("1 ");
}
else
{
if(n-siz[i]-outs[i]>n/2) printf("0 ");
else printf("1 ");
}
}
printf("\n");
}
return 0;
}
/*
2
3
1 2
2 3
5
1 2
1 3
1 4
1 5
*/
T2 build
题意:给出一棵树上两个点,求出sigma(树上所有点到这两点中的较小距离)
暴力过不了:因为多次询问,每次询问都要重新对每个点找到最短距离,复杂度O(nmlogn)(或者大概O(nm),一时间居然忘了我考场怎么写的暴力了...)
正解:对于每个点DP求出子树内和子树外所有点到根的深度之和,倍增找到两个给定点的中点,就可以把树划分成两部分,两部分答案分别是到两个点的距离之和
注意:这里也是用到两次dfs,分别在从下往上回溯的时候和从上往下的时候转移DP
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const int N=1e5+100;
int n,m;
int u,v,x,y;
struct node{
int to,nxt;
}a[N<<1];
int head[N],ecnt=-1;
void add(int x,int y){
a[++ecnt]=(node){y,head[x]};
head[x]=ecnt;
}
int dep[N];
ll dis[N],up[N],siz[N],f[N][22];
void dfs1(int u,int fa)
{
siz[u]=1;
for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1];
for(int i=head[u];~i;i=a[i].nxt)
{
int v=a[i].to;
if(v==fa) continue;
dep[v]=dep[u]+1;
f[v][0]=u;
dfs1(v,u);
siz[u]+=siz[v];
dis[u]+=dis[v]+siz[v];
}
}
void dfs2(int u,int fa)
{
for(int i=head[u];~i;i=a[i].nxt)
{
int v=a[i].to;
if(v==fa) continue;
up[v]=up[u]+dis[u]-dis[v]-siz[v]+n-siz[v];
dfs2(v,u);
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;i--)
{
if(f[x][i]&&dep[f[x][i]]>=dep[y]) x=f[x][i];
}
if(x==y) return x;
for(int i=20;i>=0;i--)
{
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
}
return f[x][0];
}
inline int Dis(int x,int y)
{
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
void getmid(){
int anc=lca(u,v);
int len=Dis(u,v);
int s=len/2;if(len&1==0) s--;
if(dep[u]<dep[v]) swap(u,v);
x=u;
for(int k=20;k>=0;k--)
{
if(s<1<<k) continue;
x=f[x][k],s-=(1<<k);
}
y=f[x][0];
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs1(1,0);
dfs2(1,0);
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
getmid();
ll ans=0;
if(dep[x]<dep[y]) swap(x,y);
ans+=dis[u]+up[u]-up[x]-(n-siz[x])*Dis(x,u);
ans+=dis[v]+up[v]-dis[x]-(siz[x])*Dis(x,v);
//printf("u=%lld v=%lld\n",dis[u]+up[u]-up[x]-(n-siz[x])*Dis(x,u),dis[v]+up[v]-dis[x]-(siz[x])*Dis(x,v));
//printf("dis[u]=%d,dis[v]=%d,up[u]=%d,up[v]=%d\n",dis[u],dis[v],up[u],up[v]);
printf("%lld\n",ans);
}
return 0;
}