三校联考(二) tree

三句话题意:给你一棵树,每个点有它的初始权值,有三种操作(o):
操作1(o=0):询问以x为根的子树中有多少节点的值大于y。
操作2(o=1):将编号为x的节点的值改成y。
操作3(o=2):添加一个点,其编号为树中原有节点数+1,它的父亲为x,权值为y。




给大家看一下正确的题解和正确的算法。

Tree解题报告

对于30%的数据
不多说了,直接暴力,只是你愿不愿意打的问题

对于60%的数据
简单粗暴的办法,对于树上的每个点建立一个数组以保存状态,即这个点用来保存这个点和这个点所有孩子的权值。
对于数据,n*max(权值)=10^9过大,所以还要打个离散化权值(即离线做)。
由于数据比较水,所以可能会有些奇奇怪怪的方法卡了高分。

对于100%的数据
核心思想:树上分块(据说某大神会用可修改的dfs序来分块,有兴趣的同学可以试试)
首先设定一个块大小的界限
对于点修改操作(即op=1),修改这个点所属块的状态
对于点插入操作(即op=2),判断此点的父节点所在块是否满了:若满了则新建一个块,并将两个块(新建块和父节点所属块)建立连边;若没有满则将此点添加入块中,并修改块的状态。
对于点查询操作(即op=3),当查询某个点时,若查询点是其所属块的根节点(插入操作保证每个块有且仅有一个根),则直接对块查询,此后一直进行块查询(通过块的连边),否则继续对孩子点查询,直到查询到块为止。
这样做可以保证对于任何操作,平均的时间复杂度为 (在块更新的时候有个暴力重构也许要再乘一个lg(n)),然后就稳稳的卡进了时限(╹▽╹)。

然后是标程:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define Maxn 300010
#define limit 200
using namespace std;
int n,m,u,v,last,op;char ch;
int sum1,sum2,sum3,ad1,ad2;
int w[Maxn<<1],father[Maxn<<1],rec[Maxn],nxt1[Maxn<<1],pre1[Maxn<<1],nxt2[Maxn<<1],pre2[Maxn<<1],end1[Maxn],end2[Maxn];
struct Node
{
    int length,a[limit];
}B[40000];
void add1(int x,int y)
{
    nxt1[++ad1]=y;pre1[ad1]=end1[x];end1[x]=ad1;
}
void add2(int x,int y)
{
    nxt2[++ad2]=y;pre2[ad2]=end2[x];end2[x]=ad2;
}
inline void read(int&x)
{
    ch=getchar();x=0;
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
}
void build(int u)        //dfs加点,按操作op=2的方式处理孩子 
{
    for(int i=end1[u];i>0;i=pre1[i]) 
        if(father[u]!=nxt1[i])
        {
            int T;
            if(B[rec[u]].length<limit) 
            {
            T=rec[nxt1[i]]=rec[u];
            B[T].a[B[T].length++]=w[nxt1[i]];
            }
            else 
            {
            T=rec[nxt1[i]]=++sum2;
            B[sum2].a[B[sum2].length++]=w[nxt1[i]];
            add2(rec[u],T);
            }
        father[nxt1[i]]=u;sort(B[T].a,B[T].a+B[T].length);
        build(nxt1[i]);
        }
}
void update(int u,int v)        //当块内节点更新时, 暴力重构当前块 
{
    int T=rec[u];
    int k=lower_bound(B[T].a,B[T].a+B[T].length,w[u])-B[T].a;
    B[T].a[k]=v;w[u]=v;
    sort(B[T].a,B[T].a+B[T].length);
}
int bdfs(int u,int x)        //树上块查询 
{
    int sum=B[u].length-(upper_bound(B[u].a,B[u].a+B[u].length,x)-B[u].a);
    for(int i=end2[u];i>0;i=pre2[i])
        sum+=bdfs(nxt2[i], x);
    return sum;
}
int pdfs(int u, int x)        //树上点查询 
{
    int sum=0;
    if(w[u]>x) sum++;
    for(int i=end1[u];i>0;i=pre1[i]) 
        if(nxt1[i]!=father[u]) 
        { 
            if(rec[u]==rec[nxt1[i]]) sum+=pdfs(nxt1[i],x);
                else sum+=bdfs(rec[nxt1[i]],x);
        }
    return sum;
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    read(n);
    for(int i=1;i<n;i++) read(u),read(v),add1(u,v),add1(v,u);
    for(int i=1;i<=n;i++) read(w[i]);
    father[1]=0;
    B[++sum2].length=1;
    B[sum2].a[0]=w[1];
    rec[1]=sum2;
    build(1);
    read(m);
    while (m--)
    {
        read(op);read(u);read(v);

        if(op==0) last=pdfs(u,v),printf("%d\n",last);
        if(op==1) update(u,v);
        if(op==2)
        {
            w[++n]=v;
            add1(u,n);
            father[n]=u;
            int T=rec[u];
            if(B[T].length<limit) B[T].a[B[T].length++]=v,rec[n]=rec[u];
            else T=++sum2,B[T].a[B[T].length++]=v,add2(rec[u],sum2),rec[n]=sum2;   //添加新的块 
            sort(B[T].a,B[T].a+B[T].length);        //暴力构建状态 
        }
    }
 }


怎么样,是不是很难?

But

暴力的访问也就是3~4*10^8次,所以
这里写图片描述

暴力是必然的。

以下是满分代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=6e6+1000;
struct node{
    int v,nxt;
}edge[maxn<<1];
int head[maxn],tot=0;
int n,m;
int a[maxn],fa[maxn];
void add_edge(int x,int y){
    edge[tot].nxt=head[x];
    edge[tot].v=y;
    head[x]=tot++;
    return ;
}
void init(int u,int d){
    fa[u]=d;
    for(int i=head[u];i!=-1;i=edge[i].nxt){
        if(edge[i].v==d) continue;
        init(edge[i].v,u);
    }
    return ;
}
int query(int u,int y){
    int ret=a[u]>y;
    for(int i=head[u];i!=-1;i=edge[i].nxt){
        if(edge[i].v==fa[u]) continue;
            ret+=query(edge[i].v,y);
    }
    return ret;
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    memset(head,-1,sizeof(head));
    cin>>n;
    for(int i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add_edge(x,y);
        add_edge(y,x);
    }
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    init(1,-1);
    cin>>m;
    for(int i=1;i<=m;i++){
        int o,x,y;scanf("%d%d%d",&o,&x,&y);
        if(o==0){
            cout<<query(x,y)<<endl;
        }
        else if(o==1){
            a[x]=y;
        }
        else if(o==2){
            add_edge(x,++n);
            fa[n]=x;
            a[n]=y;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值