zoukankan      html  css  js  c++  java
  • [BZOJ2164]采矿【模拟+树链剖分+线段树】

    Online JudgeBzoj2164

    Label:模拟,树链剖分,线段树

    题目描述

    浩浩荡荡的cg大军发现了一座矿产资源极其丰富的城市,他们打算在这座城市实施新的采矿战略。这个城市可以看成一棵有n个节点的有根树,我们把每个节点用1到n的整数编号。为了方便起见,对于任何一个非根节点v,它任何一个祖先的编号都严格小于v。树上的每个节点表示一个矿点,每条边表示一条街道。作为cg大军的一个小队长,你拥有m个部下。你有一张二维的动态信息表,用Ti,j表示第i行第j列的数据。当你被允许开采某个区域时,你可以将你的部下分配至各个矿点。在第i个矿点安排j个人可以获得Ti,j单位的矿产。允许开采的区域是这样描述的:给你一对矿点(u,v),保证v是u的祖先(这里定义祖先包括u本身);u为你控制的区域,可以在以u为根的子树上任意分配部下;u到v的简单路径(不包括u但包括v,若u=v则包括u)为探险路径,在该路径上你可以选择至多一个矿点安排部下。你这次开采的收益为安排有部下的矿点的收益之和。

    对于100%的数据

    (1≤n≤20000)(1≤m≤50)(1≤C≤2000。)

    对于满足2≤i≤n的整数i,(1≤Fi<i)(1≤A,B≤2^{31}-1)(1≤Q≤10000)

    输入

    输入的第一行包含5个正整数n、m、A、B、Q。n为矿点的个数,m为部下的数量。A、B、Q是与动态信息表有关的数据。第二行包含n-1个正整数,第i个数为Fi+1,表示节点i+1的父亲。接下来需要你用下文的方法依次生成n组数据,每组数据共m个。其中第i组的m个数据为信息表中第i行的m个数据。紧接着一行包含一个正整数C,表示事件的数量。最后给出C行,每行描述一个事件。每个事件会先给出一个0或1的整数。如果该数为0,则后面有一个正整数p,表示动态信息表有更新,你需要生成一组m个数据,来替换信息表中第p行的m个数据。如果该数为1,则后面有两个正整数u、v,表示出现了一个你可以开采的区域,你需要回答这次开采的收益。同一行的各个数之间均用一个空格隔开,没有多余的空格和换行。

    数据的生成方法如下:每次生成一组m个从小到大排列的数据,替换动态信息表的一行。其中,从小到大第j个数替换信息表中第j列的数。调用以下代码m次并排序得到一组数据。(注意可能会出现重复的数)函数GetInt A←((A xor B)+(B div X)+(B * X))and Y B←((A xor B)+(A div X)+(A * X))and Y 返回(A xor B)mod Q 其中A、B、Q均用32位有符号整数保存(C/C++的signed long int类型,pascal的longint类型),X=(2^{16})(2的16次方),Y=(2^{31}-1)(2的31次方-1),xor为位异或运算,div为整除运算,and为位且运算,mod为取余运算。由于只保留了低31位,易得我们不用考虑数据的溢出问题。(注意每次A和B都会被改变)

    输出

    对于每个开采事件(开头为1的事件),输出一行一个整数,为每次的收益。

    样例

    Input

    10 5 1 2 10
    1 1 3 3 4 4 6 6 9
    4
    1 6 3
    1 9 1
    0 1
    1 1 1
    

    Output

    1
    9
    12
    

    题解

    题面信息比较乱。下面大致理了一下题意

    有一座城市是一个含n个节点的有根树,且保证祖先编号小于子孙编号。

    你有m个人,在第i个点安排j个人可以获得Tij的收益(Tij用一张二维表给出)。但你安排的位置有限制。如果给你的矿点为(u,v)(v为u的祖先),则你可以在u的子树中随意安排人;而在vu路径上你最多只能选一个矿点放人(当u!=v时不包括u)。其他任何地方都不能放人。请你求出最大的收益。

    第一行是(n,m,A,B,Q)。(其中(A,B,Q)用于生成数据)

    第二行给n-1个数,第i个数表示节点i+1的父亲。

    第三行一个整数(C),表示操作数。

    接下来的(C)行,如果改行开头数字为(0)请你按题面要求更新(Tij)表,如果为(1)后面的一对整数((u,v))表示当前可开采矿点。

    对于每一个(1)操作,请输出当时的最大收益。至于数据生成的方式看原题面。

    (n,m)的数据范围其实不太大。(1≤n≤20000)(1≤m≤50)(1≤C≤2000。)

    我们先按暴力思路模拟一遍,不考虑更新什么的,每次询问都重新做一遍。

    对于当前的询问((u,v)):先去解决(u)的子树,这一部分用树形Dp解决,定义状态(dp[x][i])表示在以(x)为根的子树中排布了(i)个人的最大收益(由于n,m乘积不大,空间没什么问题)。转移时用背包Dp的方式转移就好了,这一遍树形Dp下来时间复杂度为(O(Ncdot M^2))。再去考虑uv路径,直接最暴力的(O(N))往上跳,枚举在当前点(x)排布的人数(i),用(T[x][i])来更新答案,(ans=max(ans,dp[u][m-i]+T[x][i]))

    综上,最暴力的去搞,单次操作的复杂度为(O(Ncdot M^2+N cdot M))


    这样子看,单次操作的时间复杂度似乎还过得去,重点就是有多组操作、还会随时更新(Tij)表。

    既然是树上问题,很容易想到用线段树去维护。

    按上面的暴力思路再走一遍。对于询问((u,v)),先来解决(u)的子树。仍然考虑树形Dp,只是将所有值封装起来放节点上,用(dfs)将子树表示在区间上,用线段树完成区间的合并操作。

    搞一个结构体(Mine)来封装。

    struct Mine{
        int a[52];//x.a[i] (1<=i<=m) 表示在x这个点放i个人的最大收益
        void init(){//初始化,更新数据
            for(int i=1;i<=m;++i)a[i]=getnum();
    		sort(a+1,a+m+1);
        }
        Mine operator +(const Mine &b)const{//运算符重载,表示合并操作
            Mine res;memset(res.a,0,sizeof(res.a));
            for(register int i=1;i<=m;++i)for(register int j=0;j<=i;++j)
            res.a[i]=max(res.a[i],a[j]+b.a[i-j]);
    		return res;
        }
    };
    

    对应的建树操作如下,线段树上每个节点(node)上存两个值,summa(注意到都是用上面的结构体(Mine)表示的)。

    sum是现在求子树要用的,ma是待会弄uv路径用的。

    对于线段树上的每个节点(node),设其对应区间为([l,r]),则sum是dfs序在[l,r]间的所有树上的节点合并后的结果,也就是只在这些点中放人的最大收益;而ma是在这些点中的一个点,放人的最大收益(你大概知道接下来怎么求路径上的贡献了,就是树剖往上跳,然后线段树区间找个最值ma)。

    struct node{
        Mine ma,sum;
    }b[N<<2];
    
    inline Mine Max(Mine x,Mine y){
        for(int i=1;i<=m;++i)x.a[i]=max(x.a[i],y.a[i]);
        return x;
    }
    
    inline void Up(int o){
        b[o].ma=Max(b[o<<1].ma,b[o<<1|1].ma);
        b[o].sum=b[o<<1].sum+b[o<<1|1].sum;
    }
    
    void build(int o,int l,int r){
    	if(l==r){
    		b[o].ma=b[o].sum=st[l];
    		return;
    	}
    	int mid=l+r>>1;
    	build(o<<1,l,mid);build(o<<1|1,mid+1,r);
    	Up(o);
    }
    

    那么对于u的子树,每次操作时查询个区间和sum就好了。

    而求uv路径的贡献,就是上面剧透的那样,直接套个树链剖分(O(logN))的时间内往上跳,然后查询区间最值,并更新答案。

    完整代码如下,注意细节,注意卡常。

    #include<bits/stdc++.h>
    #define N 20010
    using namespace std;
    const int X=1<<16,Y=(1<<31)-1;
    inline int read(){
        int x=0;char c=getchar();
        while(c<'0'||c>'9')c=getchar();
        while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
        return x;
    }
    int n,m,A,B,C;
    
    
    struct edge{
    	int to,nxt;
    }e[N<<1];
    int head[N],cnt;
    inline void link(int u,int v){
    	e[++cnt].to=v,e[cnt].nxt=head[u];
    	head[u]=cnt;
    }
    
    inline int getnum(){
        A=((A^B)+B/X+B*X)&Y;
    	B=((A^B)+A/X+A*X)&Y;
    	return (A^B)%C;
    }
    struct Mine{
        int a[52];
        void init(){
            for(int i=1;i<=m;++i)a[i]=getnum();
    		sort(a+1,a+m+1);
        }
        Mine operator +(const Mine &b)const{
            Mine res;memset(res.a,0,sizeof(res.a));
            for(register int i=1;i<=m;++i)for(register int j=0;j<=i;++j)
            res.a[i]=max(res.a[i],a[j]+b.a[i-j]);
    		return res;
        }
    }st[N];
    inline Mine Max(Mine x,Mine y){
        for(int i=1;i<=m;++i)x.a[i]=max(x.a[i],y.a[i]);
        return x;
    }
    
    struct node{
        Mine ma,sum;
    }b[N<<2];
    
    inline void Up(int o){
        b[o].ma=Max(b[o<<1].ma,b[o<<1|1].ma);
        b[o].sum=b[o<<1].sum+b[o<<1|1].sum;
    }
    
    void build(int o,int l,int r){
    	if(l==r){
    		b[o].ma=b[o].sum=st[l];
    		return;
    	}
    	int mid=l+r>>1;
    	build(o<<1,l,mid);build(o<<1|1,mid+1,r);
    	Up(o);
    }
    void change(int o,int l,int r,int x){
        if(l==r){
    		b[o].ma.init();
    		b[o].sum=b[o].ma;
    		return;
    	}
        int mid=l+r>>1;
        if(x<=mid)change(o<<1,l,mid,x);
        else change(o<<1|1,mid+1,r,x);
    	Up(o);
    }
    Mine asksum(int o,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr)return b[o].sum;
    	int mid=l+r>>1;
        if(qr<=mid)return asksum(o<<1,l,mid,ql,qr);
        if(ql>mid)return asksum(o<<1|1,mid+1,r,ql,qr);
        return asksum(o<<1,l,mid,ql,mid)+asksum(o<<1|1,mid+1,r,mid+1,qr);	
    }
    Mine askma(int o,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr)return b[o].ma;
    	int mid=l+r>>1;
        if(qr<=mid)return askma(o<<1,l,mid,ql,qr);
        if(ql>mid)return askma(o<<1|1,mid+1,r,ql,qr);
        return Max(askma(o<<1,l,mid,ql,mid),askma(o<<1|1,mid+1,r,mid+1,qr));
    }
    
    int L[N],R[N],fa[N],dfn=0;
    int son[N],dep[N],top[N],sz[N];
    void dfs(int x){
        sz[x]=1;
        for(int i=head[x];i;i=e[i].nxt){
            int y=e[i].to;
    		fa[y]=x;dep[y]=dep[x]+1;
    		dfs(y);
            sz[x]+=sz[y];
    		if(sz[y]>sz[son[x]])son[x]=y;
        }
    }
    void redfs(int x,int tp){
    	L[x]=++dfn;top[x]=tp;
        if(son[x])redfs(son[x],tp);
        for(int i=head[x];i;i=e[i].nxt){
            int y=e[i].to;
    		if(y!=son[x])redfs(y,y);
        }
    	R[x]=dfn;
    }
    inline Mine jump(int x,int y){
    	Mine res;memset(res.a,0,sizeof(res));
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]])swap(x,y);
            res=Max(res,askma(1,1,n,L[top[x]],L[x]));
    		x=fa[top[x]];
        }
    	if(L[x]>L[y])swap(x,y);
        res=Max(res,askma(1,1,n,L[x],L[y]));
    	return res;
    }
    int main(){
    	n=read(),m=read(),A=read(),B=read(),C=read();
    	for(int i=2;i<=n;i++){
    		int u=read();link(u,i);
    	}
    	
    	dfs(1);redfs(1,1);
    	
    	for(int i=1;i<=n;i++)st[L[i]].init();
    	
    	build(1,1,n);
    	
    	int T=read();
        while(T--){
            int op=read(),x=read();
    		if(op==0)change(1,1,n,L[x]);
    		else{
    			int y=read();
    			Mine tmp=asksum(1,1,n,L[x],R[x]);
    			if(x!=y)tmp=tmp+jump(fa[x],y);
    			printf("%d
    ",tmp.a[m]);
    		}	
    	}
    }
    
  • 相关阅读:
    CString详细讲解
    常用的函数调用约定 stdcall/cdecl/fastcall
    near指针,far指针,huge指针
    md /mdd /ml /mt/mtd
    VC通过ADO链接ORACLE数据库
    c++ 线程同步
    typedef struct与struct的区别
    BeginPaint&&GetDc(转)
    left join 命令详解
    sqlserver IO 监测
  • 原文地址:https://www.cnblogs.com/Tieechal/p/11549623.html
Copyright © 2011-2022 走看看