树上的好题

T1.[LNOI2014]LCA

给出一个n个节点的有根树(编号为0到n-1,根节点为0)。一个点的深度定义为这个节点到根的距离+1。
设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先。
有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[LCA(i,z)]。
(即,求在[l,r]区间内的每个节点i与z的最近公共祖先的深度之和)

 

这道题一上来乍眼一看没什么思路,其实之后的每步都是套路;

对于LCA,我们首先会想到差分,没错,就是差分;

咋查分啊?一个区间的点啊,这复杂度可不是闹着玩的;

但有没有注意到,z只有一个;我们是否可以把整个区间在树上差分,然后用这一个点z来查询;

答案是当然可以:(注意:这里的差分与普通的树形差分不一样)

我们把z到root这条路上的所有点都打上标记(点的权值加1);那么你会发现,这条路径上任意一点的深度就是到根节点的点权和;

所以询问区间[L,R],我们只要询问每个点到根节点的点权和就可以了;

另外,这个结论存在一个优美的性质:若将这个区间的所有点到根节点路径的点权值加1,那么询问时可以找z到根节点的权值和;

通过以上的的操作,我们将闹着玩的复杂度降到了似乎可做的复杂度;

接下来我们接着优化:
这么多区间的查询,是否想到了数位dp的经典处理方法?
没错,这是一个前缀和优化处理;

我们将询问离线掉;然后从1到n加入这个点;并将这个点到根节点的路径上所有点的权值加1;

然后复杂度变成了看起来好好做的样子;

在思维想不动的时候,就换种解题思路,使用数据结构大力维护每个点到根节点的点权和;

你想到了什么?树剖?LCT?没问题!

接下来就是愉快的AC时间了;

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll long long
#define inf 1000000000
#define mod 201314
using namespace std;
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,m,cnt,place;
int bin[20];
int son[50005],last[50005],fa[50005],belong[50005],pl[50005],deep[50005];
struct edge{int to,next;}e[50005];
struct que{int z,ans1,ans2;}q[50005];
struct data{int num,p;bool flag;}a[100005];
struct seg{int l,r,sum,tag,size;}t[200005];
inline bool operator<(data a,data b)
{
	return a.p<b.p;
}
inline void insert(int u,int v)
{
	e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
}
void dfs1(int x)
{
	son[x]=1;
	for(int i=last[x];i;i=e[i].next)
	{
		if(e[i].to==fa[x])continue;
		deep[e[i].to]=deep[x]+1;
		fa[e[i].to]=x;
		dfs1(e[i].to);
		son[x]+=son[e[i].to];
	}
}
void dfs2(int x,int chain)
{
	belong[x]=chain;pl[x]=++place;
	int k=n;
	for(int i=last[x];i;i=e[i].next)
		if(e[i].to!=fa[x]&&son[e[i].to]>son[k])
			k=e[i].to;
	if(k!=n)dfs2(k,chain);
	for(int i=last[x];i;i=e[i].next)
		if(e[i].to!=fa[x]&&e[i].to!=k)
			dfs2(e[i].to,e[i].to);
}
inline void pushdown(int k)
{
	if(t[k].l==t[k].r||!t[k].tag)return;
	int tag=t[k].tag;t[k].tag=0;
	t[k<<1].sum=t[k<<1].sum+t[k<<1].size*tag;
	t[k<<1|1].sum=t[k<<1|1].sum+t[k<<1|1].size*tag;
	t[k<<1].tag=t[k<<1].tag+tag;
	t[k<<1|1].tag=t[k<<1|1].tag+tag;
}
void build(int k,int l,int r)
{
	t[k].l=l;t[k].r=r;t[k].size=r-l+1;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}
void update(int k,int x,int y)
{
	pushdown(k);
	int l=t[k].l,r=t[k].r;
	if(l==x&&y==r)
	{
		t[k].tag++;t[k].sum+=t[k].size;
		return;
	}
	int mid=(l+r)>>1;
	if(y<=mid)update(k<<1,x,y);
	else if(x>mid)update(k<<1|1,x,y);
	else 
	{
		update(k<<1,x,mid);
		update(k<<1|1,mid+1,y);
	}
	t[k].sum=t[k<<1].sum+t[k<<1|1].sum;
}
void solve_update(int x,int f)
{
	while(belong[x]!=belong[f])
	{
		update(1,pl[belong[x]],pl[x]);
		x=fa[belong[x]];
	}
	update(1,pl[f],pl[x]);
}
int query(int k,int x,int y)
{
	pushdown(k);
	int l=t[k].l,r=t[k].r;
	if(l==x&&y==r)return t[k].sum;
	int mid=(l+r)>>1;
	if(y<=mid)return query(k<<1,x,y);
	else if(x>mid)return query(k<<1|1,x,y);
	else return query(k<<1,x,mid)+query(k<<1|1,mid+1,y);
}
int solve_query(int x,int f)
{
	int sum=0;
	while(belong[x]!=belong[f])
	{
		sum+=query(1,pl[belong[x]],pl[x]);
		sum%=mod;
		x=fa[belong[x]];
	}
	sum+=query(1,pl[f],pl[x]);sum%=mod;
	return sum;
}
int main()
{
	//freopen("lca.in","r",stdin);
	//freopen("lca.out","w",stdout);
	bin[0]=1;for(int i=1;i<20;i++)bin[i]=(bin[i-1]<<1);
    n=read();m=read();
	for(int i=1;i<n;i++)
	{
		int x=read();insert(x,i);
	}
	int tot=0;
	for(int i=1;i<=m;i++)
	{
		int l=read(),r=read();
	    q[i].z=read();
		a[++tot].p=l-1;a[tot].num=i;a[tot].flag=0;
	    a[++tot].p=r;a[tot].num=i;a[tot].flag=1;
	}
	build(1,1,n);
	sort(a+1,a+tot+1);
	dfs1(0);dfs2(0,0);
	int now=-1;
	for(int i=1;i<=tot;i++)
	{
		while(now<a[i].p)
		{
			now++;
			solve_update(now,0);
		}
		int t=a[i].num;
	    if(!a[i].flag)q[t].ans1=solve_query(q[t].z,0);
		else q[t].ans2=solve_query(q[t].z,0);
	}
	for(int i=1;i<=m;i++)
		printf("%d\n",(q[i].ans2-q[i].ans1+mod)%mod);
	return 0;
}

 

 T2.我将之命名为树上背包

题意:给你一颗树,一共n个点,每个点有两种形态:A/B,其中A形态的点有m个,但你不知道具体是哪m个;

  显然的,B类点有n-m个;

  如果任意两个点的类别相同,那么就会对答案做出这两个点之间的距离的贡献;

        问答案最大是多少;

 

这道题主要是运用到了背包的思想;

我们做树上的题是要尽量避免枚举任意两个点的思想,要不然再怎么想基本还是暴力;

所以我们枚举LCA(比较常见的操作)

对于作为LCA的点u,设f[u][j]表示在以u为根的字树种,选择j个A类点的价值最大是多少;

那么在转移时,我们枚举以u为根的子树已经选择的A类点的数量j,和在目前的这个以v为根节点的子树中选择A类点的个数k;

显然的,这条边会被A类点对经过(k*(m-k)),会被B类点经过(size[v]-k)*(n-m-size[v]+k);

那么这条边的对答案的贡献值就显而易见了~

 

#include <bits/stdc++.h>
#define inc(i,a,b) for(register int i=a;i<=b;i++)
#define dec(i,a,b) for(register int i=a;i>=b;i--)
using namespace std;
int head[200010],cnt;
class littlestar{
    public:
    int to,nxt;
    long long w;
    void add(int u,int v,long long gg){
        to=v; nxt=head[u];
        head[u]=cnt; w=gg;
    }
}star[400010];
int n,m;
long long f[3010][3010];
long long size[200010];
void dfs(register int u,register int fa)
{
    size[u]=1;
    for(int i=head[u];i;i=star[i].nxt){
        int v=star[i].to;
        if(v==fa) continue;
        dfs(v,u);            
        dec(j,min(m,(int)size[u]),0){
            dec(k,min(m-j,(int)size[v]),0){
                f[u][j+k]=max(f[u][j+k],f[u][j]+f[v][k]+(long long)k*(m-k)*star[i].w+(long long)(n-size[v]-m+k)*(size[v]-k)*star[i].w);
            }
        }
        size[u]+=size[v];            
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    inc(i,1,n-1){
        int u,v;
        long long w;
        scanf("%d%d%lld",&u,&v,&w);
        star[++cnt].add(u,v,w);
        star[++cnt].add(v,u,w);
    }
    dfs(1,0);
    cout<<f[1][m];
}
/*
5 4
1 2 1
2 3 3
2 4 1
1 5 3
*/

 

 

 

 

 

posted @ 2019-11-05 19:44  神之右大臣  阅读(173)  评论(0编辑  收藏  举报