中石油训练赛 - Russian Dolls on the Christmas Tree(树上启发式合并/主席树)

题目链接:点击查看

题目大意:给出一棵 n 个节点的树,以点 1 为根,现在对于每个节点作为根的子树求解:子树中有多少个编号不相交的连续子段,如:1 2 4 5 7,共有三个连续的段,分别为 [ 1 , 2 ] , [ 4 , 5 ] , [ 7 ]

题目分析:树上启发式合并的模板题,cal 函数中直接维护一个数组用来统计加入或删除掉一个数字后对于贡献的影响即可:

  1. 如果编号 x - 1 和 x + 1 早已存在,那么加入 x 后总段数减一
  2. 如果编号 x - 1 或 x + 1 早已存在,那么加入 x 后总段数不变
  3. 如果编号 x - 1 和 x + 1 都不存在,那么加入 x 后总段数加一

删除的话正好反过来

然后,今下午在和 zx 学长闲聊的时候,意外发现这个题目可以用主席树乱搞,因为子树对应的刚好是 dfs 序,区间内有多少个不相交的连续子段也可以用线段树的区间合并来解决,兴致勃勃来到电脑前面实现,却发现了些许问题:

  1. 对于每个节点如果只维护 sum(有多少个子段),ll(最左端是否有数字),rr(最右端是否有数字),无法直接计算出区间内的答案
  2. 由上可知,如果想要维护出正确的答案,需要从叶子节点再自底向上 pushup 一次
  3. 那么总时间复杂度为 n^2logn,还不如直接 n*n 的暴力跑得快

于是可持久化线段树的区间合并就没办法了,也可能是我知识浅薄不会实现,抱着试一试的心态去百度了一下题解,发现这个题目真的可以用主席树来实现,只不过需要转换一下模型:

对于每个数字 x 来说,假设其只与前驱,也就是 x - 1 有关联,再假设当前如果有 num 个数,如果其中有 cnt 个数的前驱也在这 num 个数当中,那么这 cnt 个数都可以和前驱合并,对答案不做贡献,所以最后的不相交连续子段的个数是 num - cnt 个

这样问题就转换为了:对于某个节点来说,其子树中有多少个节点的前驱也在子树中,答案就是子树的大小与这个做差了

代码:

树上启发式合并

#pragma GCC optimize(2)
#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
     
typedef long long LL;
     
typedef unsigned long long ull;
     
const int inf=0x3f3f3f3f;
   
const int N=2e5+100;
 
vector<int>node[N];
 
int ans[N],son[N],num[N],sum;
 
bool vis[N],book[N];
 
void dfs_son(int u,int fa)//树链剖分跑出重链
{
    son[u]=-1;
    num[u]=1;
    for(auto v:node[u])
    {
        if(v==fa)
            continue;
        dfs_son(v,u);
        num[u]+=num[v];
        if(son[u]==-1||num[v]>num[son[u]])
            son[u]=v;
    }
}
  
void cal(int u,int fa,int val)//对于每个节点计算其子树的贡献
{
    if(val==1)
    {
        book[u]=true;
        if(book[u-1]&&book[u+1])
            sum--;
        else if(book[u-1]||book[u+1])
            ;
        else
            sum++;
    }
    else
    {
        book[u]=false;
        if(book[u-1]&&book[u+1])
            sum++;
        else if(book[u-1]||book[u+1])
            ;
        else
            sum--;
    }
    for(auto v:node[u])
    {
        if(v==fa||vis[v])
            continue;
        cal(v,u,val);
    }
}
  
void dfs(int u,int fa,int keep)//启发式合并
{
    for(auto v:node[u])
    {
        if(v==fa||v==son[u])
            continue;
        dfs(v,u,0);
    }
    if(son[u]!=-1)
    {
        dfs(son[u],u,1);
        vis[son[u]]=true;
    }
    cal(u,fa,1);
    ans[u]=sum;
    if(son[u]!=-1)
        vis[son[u]]=false;
    if(!keep)
        cal(u,fa,-1);
}
 
void init(int n)
{
    for(int i=1;i<=n;i++)
        node[i].clear();
    memset(vis,false,n+5);
    memset(book,false,n+5);
    sum=0;
}
   
int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
    int w;
    cin>>w;
    int kase=0;
    while(w--)
    {
        int n;
        scanf("%d",&n);
        init(n);
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            node[u].push_back(v);
            node[v].push_back(u);
        }
        dfs_son(1,-1);
        dfs(1,-1,1);
        printf("Case #%d:",++kase);
        for(int i=1;i<=n;i++)
            printf(" %d",ans[i]);
        puts("");
    }
 
 
 
 
 
 
 
 
 
 
 
 
 
    return 0;
}

主席树

#pragma GCC optimize(2)
#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
     
typedef long long LL;
     
typedef unsigned long long ull;
     
const int inf=0x3f3f3f3f;
   
const int N=2e5+100;
/*主席树*/
struct Node
{
	int l,r;
	int sum;
}tree[N*20];

int cnt,root[N];

void update(int pos,int &k,int l,int r)
{
	if(pos<l||pos>r)
		return;
	tree[cnt++]=tree[k];
	k=cnt-1;
	tree[k].sum++;
	if(l==r)
		return;
	int mid=l+r>>1;
	if(pos<=mid)
		update(pos,tree[k].l,l,mid);
	else
		update(pos,tree[k].r,mid+1,r);
}

int query(int i,int j,int l,int r,int L,int R)//[l,r]:目标区间,[L,R]:当前区间 
{
	if(R<l||L>r)
		return 0;
	if(L>=l&&R<=r)
		return tree[j].sum-tree[i].sum;
	int mid=L+R>>1;
	return query(tree[i].l,tree[j].l,l,r,L,mid)+query(tree[i].r,tree[j].r,l,r,mid+1,R);
}
/*主席树*/
/*dfs序*/
vector<int>node[N];

int L[N],R[N],tot,id[N],sz[N]; 

void dfs(int u,int fa)
{
	sz[u]=1;
	L[u]=++tot;
	id[tot]=u;
	for(auto v:node[u])
	{
		if(v==fa)
			continue;
		dfs(v,u);
		sz[u]+=sz[v];
	}
	R[u]=tot;
}
/*dfs序*/
void init(int n)
{
	root[0]=0;
	tree[0].l=tree[0].r=tree[0].sum=0;
	cnt=1;
	tot=0;
	for(int i=1;i<=n;i++)
		node[i].clear();
}

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	int w;
	cin>>w;
	int kase=0;
	while(w--)
	{
		int n;
		scanf("%d",&n);
		init(n);
		for(int i=1;i<n;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			node[u].push_back(v);
			node[v].push_back(u);
		}
		dfs(1,-1);
		for(int i=1;i<=n;i++)//遍历dfs序建树 
		{
			root[i]=root[i-1];
			update(L[id[i]-1],root[i],1,n);//第i个dfs序表示的节点是id[i],其前驱为id[i]-1,将其前驱的dfs序标记一下
		}
		printf("Case #%d:",++kase);
		for(int i=1;i<=n;i++)
			printf(" %d",sz[i]-query(root[L[i]-1],root[R[i]],L[i],R[i],1,n));//查找时只需要查找子树中的点的前驱个数即可
		puts("");
	}













    return 0;
}
扫描二维码关注公众号,回复: 11904296 查看本文章

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/109032281