2020.5.5集训总结

咕的时间真长

T1

P4847 银河英雄传说V2

f h q   t r e a p fhq\ treap fhq treap入门题

M就合并
D就拆开
Q就把那一段拎出来输出 s u m sum sum就好

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=2e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n,m;
int fa[N],son[N][2],siz[N],val[N],treap[N];
ll sum[N];
int rt,a,b,c;

void update(int x){
    siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;
    sum[x]=sum[son[x][0]]+sum[son[x][1]]+val[x];
    if(son[x][0])fa[son[x][0]]=x;
    if(son[x][1])fa[son[x][1]]=x;
    if(son[fa[x]][0]!=x&&son[fa[x]][1]!=x)fa[x]=0;
}

int merge(int u,int v){
    if(!u||!v)return u|v;
    int rt;
    if(treap[u]<treap[v])son[rt=u][1]=merge(son[u][1],v);
    else son[rt=v][0]=merge(u,son[v][0]);
    return update(rt),rt;
}

void split(int o,int &u,int &v,int k){
    if(!o){u=v=0;return;}
    int rank=siz[son[o][0]]+1;
    if(rank<=k)split(son[u=o][1],son[o][1],v,k-rank);
    else split(son[v=o][0],u,son[o][0],k);
    update(o);
}

int getfa(int x){
    while(fa[x])x=fa[x];
    return x;
}

int rnk(int x){
    int res=siz[son[x][0]]+1;
    while(fa[x]){
        if(son[fa[x]][1]==x)res+=siz[son[fa[x]][0]]+1;
        x=fa[x];
    }
    return res;
}

int main()
{
    srand(19260817);
    read(n),read(m);
    Rep(i,1,n){
        read(val[i]);
        sum[i]=val[i];
        fa[i]=son[i][0]=son[i][1]=0;
        siz[i]=1;
        treap[i]=rand();
    }
    Rep(i,1,m){
        char opt[10];
        int x,y;
        scanf("%s",opt);
        read(x);
        if(opt[0]=='M'){
            read(y);
            x=getfa(x),y=getfa(y);
            if(x!=y)merge(y,x);
        }
        if(opt[0]=='D'){
            int fx=getfa(x),rank=rnk(x);
            split(fx,a,b,rank-1);
        }   
        if(opt[0]=='Q'){
            read(y);
            int fx=getfa(x),fy=getfa(y);
            if(fx!=fy){puts("-1");continue;}
            int rx=rnk(x),ry=rnk(y);
            if(rx>ry)swap(rx,ry);
            split(fx,a,c,ry);
            split(a,a,b,rx-1);
            printf("%lld\n",sum[b]);
            merge(merge(a,b),c);
        }
    }
    return 0;
}

T2

P4036 [JSOI2008]火星人

恶心的题
判断两个字符串是否相同,可以哈希
插入需要平衡树
那么怎么找长度呢?二分一下长度就可以

然后这题还卡常…吸氧过的

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=1e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

typedef unsigned int ull;
const int base=29;

int n,m;
char s[N];
int son[N][2],siz[N],val[N],treap[N];
ull haxi[N],poww[N];
int rt,tot;

int newnode(int value){
    int u=++tot;
    siz[u]=1,treap[u]=rand();
    haxi[u]=val[u]=value;
    return u;
}

void update(int x){
    siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;
    haxi[x]=haxi[son[x][0]]*poww[siz[son[x][1]]+1]+val[x]*poww[siz[son[x][1]]]+haxi[son[x][1]];
}

int merge(int u,int v){
    if(!u||!v)return u|v;
    int rt;
    if(treap[u]<treap[v])son[rt=u][1]=merge(son[u][1],v);
    else son[rt=v][0]=merge(u,son[v][0]);
    return update(rt),rt;
}

void split(int o,int &u,int &v,int k){
    if(!o){u=v=0;return;}
    int rank=siz[son[o][0]]+1;
    if(rank<=k)split(son[u=o][1],son[o][1],v,k-rank);
    else split(son[v=o][0],u,son[o][0],k);
    update(o);
}

ull gethash(int l,int r){
    int x,y,z;
    split(rt,x,z,r);
    split(x,x,y,l-1);
    ull res=haxi[y];
    rt=merge(merge(x,y),z);
    return res;
}

int main()
{
    srand(19260817);
    poww[0]=1;
    Rep(i,1,1e5)poww[i]=poww[i-1]*base;
    scanf("%s",s+1);
    n=strlen(s+1);
    Rep(i,1,n)rt=merge(rt,newnode(s[i]-'a'));
    read(m);
    Rep(i,1,m){
        char opt[10];
        scanf("%s",opt);
        if(opt[0]=='Q'){
            int x,y;
            read(x),read(y);
            if(x>y)swap(x,y);
            int l=1,r=siz[rt]-y+1,res=0;
            while(l<=r){
                int mid=l+r>>1;
                if(gethash(x,x+mid-1)==gethash(y,y+mid-1))res=mid,l=mid+1;
                else r=mid-1;
            }
            printf("%d\n",res);
        } 
        if(opt[0]=='R'){
            int x,y,z;read(x);
            char ch;cin>>ch;
            int pos=x,value=ch-'a';
            split(rt,x,z,pos);
            split(x,x,y,pos-1);
            haxi[y]=val[y]=value;
            rt=merge(merge(x,y),z);
        }
        if(opt[0]=='I'){
            int x,y;read(x);
            char ch;cin>>ch;
            int k=x,val=ch-'a';
            split(rt,x,y,k);
            rt=merge(merge(x,newnode(val)),y);
        }
    }
    return 0;
}

T3

P3224 [HNOI2012]永无乡

其实可以线段树合并,为啥一个 log ⁡ \log log比平衡树套启发式合并还快呢/kk

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=1e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n,m,q;
int son[N][2],siz[N],val[N],treap[N],num[N];
int fa[N],root[N];

int find(int x){
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}

void update(int x){
    siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;
}

int merge(int u,int v){
    if(!u||!v)return u|v;
    int rt;
    if(treap[u]<treap[v])son[rt=u][1]=merge(son[u][1],v);
    else son[rt=v][0]=merge(u,son[v][0]);
    return update(rt),rt;
}

void split(int o,int &u,int &v,int k){// split by val
    if(!o){u=v=0;return;}
    if(val[o]<=k)split(son[u=o][1],son[o][1],v,k);
    else split(son[v=o][0],u,son[o][0],k);
    update(o);
}

int kth(int u,int k){
    while(1){
        if(siz[son[u][0]]>=k)u=son[u][0];
        else if(siz[son[u][0]]+1>=k)return num[u];
        else k-=siz[son[u][0]]+1,u=son[u][1];
    }
}

void ins(int &rt,int id){
    int rx,ry;
    split(rt,rx,ry,val[id]);
    // printf("%d %d\n",rt,siz[rt]);
    rt=merge(merge(rx,id),ry);
    // printf("%d %d\n",rt,siz[rt]);
}

void dfs(int u,int x){
    if(son[u][0])dfs(son[u][0],x);
    if(son[u][1])dfs(son[u][1],x);
    son[u][0]=son[u][1]=0;
    siz[u]=1;
    ins(root[x],u);
}

int main()
{
    srand(19260817);
    read(n),read(m);
    Rep(i,1,n){
        read(val[i]);
        siz[i]=1;
        son[i][0]=son[i][1]=0;
        treap[i]=rand();
        fa[i]=root[i]=num[i]=i;
    }
    Rep(i,1,m){
        int x,y;
        read(x),read(y);
        x=find(x),y=find(y);
        if(x==y)continue;
        if(siz[root[x]]<siz[root[y]])swap(x,y);
        dfs(root[y],x);
        fa[y]=x,root[y]=root[x];
    }
    read(q);
    Rep(i,1,q){
        char opt[10];
        int x,y;
        scanf("%s",opt);
        read(x),read(y);
        if(opt[0]=='Q'){
            x=find(x);
            if(siz[root[x]]<y)puts("-1");
            else printf("%d\n",kth(root[x],y));
        }
        else{
            x=find(x),y=find(y);
            if(x==y)continue;
            if(siz[root[x]]<siz[root[y]])swap(x,y);
            dfs(root[y],x);
            fa[y]=x,root[y]=root[x];
        }
    }
    return 0;
}

T4

P3991 [BJOI2017]喷式水战改

很有意思的题
直接粘在luogu上的题解了

这题其实想清楚还是挺好写的,代码连100行都不到

首先考虑如果没有插入操作,就给定一个序列怎么做,那就是一个非常简单的一维(二维?) d p dp dp,我们用 f i , j f_{i,j} fi,j表示第 i i i个点当做第 j j j个区间来使用(为了方便,我们把四个区间标记成 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3),其中 0 0 0 3 3 3的价格是完全相同的。那么转移就是

f i , j = max ⁡ { f i − 1 , k + v a l } , k ≤ j f_{i,j}=\max\{f_{i-1,k}+val\},k\leq j fi,j=max{fi1,k+val},kj

然后我们会到原题里面,有插入操作,显然想到平衡树

那么我们一个类似这样的 d p dp dp搬到平衡树上就好了

我们用$f[u] _{i,j} 表 示 平 衡 树 上 编 号 为 表示平衡树上编号为 u 的 节 点 当 他 代 表 的 是 的节点当他代表的是 [i,j]$这两个工作模式的时候的最大方法(两段可以不选)

那么我们就只需要改一下 u p d a t e update update的写法就可以,转移就是

f [ u ] i , k = max ⁡ { f [ l c ] i , j + v a l [ u ] j + f [ r c ] j , k } f[u]_{i,k}=\max\{f[lc]_{i,j}+val[u]_j+f[rc]_{j,k}\} f[u]i,k=max{f[lc]i,j+val[u]j+f[rc]j,k}

貌似没有ClCN姐姐的那么麻烦?(

但是这题还有一个恶心的地方就是有 n n n次操作,每次插入 x x x个数,最差的时候会插入 1 0 14 10^{14} 1014个数,炸飞了

怎么呢?我们可以用ODT的思想把连续一段相同的合并到一个点上

每次插入的时候判断一下,如果插到了一个点的中间,就需要把这个点拆成两个

那么可以发现每次插入的时候最多多三个点,如果开空间回收只用开 3 3 3倍空间就可以,不开 4 4 4倍也够了,当然我为了保险开了 5 5 5

那么我们最后的复杂度就是 O ( 64 n log ⁡ n ) O(64n\log n) O(64nlogn),时限三秒可以通过(因为 64 64 64还比较大所以单独写出来了)

当然因为需要拆点还有一点点小细节

比如说我们怎么确定我们要插入的这个点位于哪个序列里面呢?我们split按什么split呢?

可以按照平衡树上的节点数siz进行split,也可以按照实际上的燃料数sum进行split,当然可以两次分别split一下,但是其实是没有必要的

我们应该选择按照第一种方法,平衡树上的节点数进行split

为什么呢?比如说我们把 p p p所在的点按照sum拆出来了,那么从哪里把这个点劈成两半呢?我们不知道

但是如果按照siz拆出来,我们是可以利用siz表示出sum的,所以我们应该按照siz进行split

那么我们可以根据sum上的排名(已知)去找siz上的排名,然后根据排名split

然后特判插入的位置在末尾的情况,再还原就可以了

当然,开了longlong之后,记得输出%lld

本来写了个没啥用的空间回收,后来为了把代码卡进100行给删了

为啥删掉空间回收还变慢了500ms呢

最后卡到95行的代码:

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=5e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n;
int son[N][2],treap[N],val[N][4],len[N],siz[N];
ll sum[N],f[N][4][4];
ll ans;
int rt,tot;
int bin[N],top;

void update(int x){
    memset(f[x],0,sizeof(f[x]));
    Rep(i,0,3)
        Rep(j,i,3)
            Rep(k,j,3)
                f[x][i][k]=max(f[x][i][k],f[son[x][0]][i][j]+1ll*val[x][j]*len[x]+f[son[x][1]][j][k]);
    sum[x]=sum[son[x][0]]+sum[son[x][1]]+len[x];
    siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;
}

void split(int o,int &u,int &v,int k){
    if(!o){u=v=0;return;}
    int rank=siz[son[o][0]]+1;
    if(rank<=k)split(son[u=o][1],son[o][1],v,k-rank);
    else split(son[v=o][0],u,son[o][0],k);
    update(o);
}   

int merge(int u,int v){
    if(!u||!v)return u|v;
    int rt;
    if(treap[u]<treap[v])son[rt=u][1]=merge(son[u][1],v);
    else son[rt=v][0]=merge(u,son[v][0]);
    return update(rt),rt;
}

int rnk(ll k){
    int u=rt,res=0;
    while(u){
        if(sum[son[u][0]]>=k)u=son[u][0];
        else if(sum[son[u][0]]+len[u]>=k)return res+siz[son[u][0]]+1;
        else k-=sum[son[u][0]]+len[u],res+=siz[son[u][0]]+1,u=son[u][1];
    }
    return res;
}

int main()
{
    srand(19260817);
    read(n);
    Rep(i,1,n){
        ll p,x;
        int u=++tot;
        read(p),read(val[u][0]),read(val[u][1]),read(val[u][2]),read(x),val[u][3]=val[u][0];
        int rank=rnk(p);
        int lef,mid,rht;
        split(rt,lef,rht,rank);
        split(lef,lef,mid,rank-1);
        siz[u]=1,sum[u]=len[u]=x;
        son[u][0]=son[u][1]=0;
        treap[u]=rand();
        update(u);
        if(sum[lef]+len[mid]==p)rt=merge(merge(lef,mid),merge(u,rht));
        else{
            int l=++tot,r=++tot;
            Rep(i,0,3)val[l][i]=val[r][i]=val[mid][i];
            siz[l]=siz[r]=mid;
            son[l][0]=son[r][0]=son[l][1]=son[r][1]=0;
            sum[l]=len[l]=p-sum[lef];
            sum[r]=len[r]=sum[lef]+sum[mid]-p;
            treap[l]=rand(),treap[r]=rand();
            update(l),update(r);
            rt=merge(merge(lef,merge(l,u)),merge(r,rht));
        }
        printf("%lld\n",f[rt][0][3]-ans);
        ans=f[rt][0][3];
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值