CH暑假欢乐赛 SRM 07 天才麻将少女KPM(DP+treap)

  首先LIS有个$O(n^2)$的DP方法

  $f(i,j)$表示前i个数,最后一个数<=j的LIS

  如果$a_i!=0$则有

  如果$a_i=0$则有

  注意因为$f(i-1,j)\leq f(i-1,j-1)+1$,所以上面第二个转移是成立的。

  用treap维护这个有两种写法。

  ①支持区间max=,区间+1。

  ②只要是只要求单点查询或者最后输出答案的区间操作都是可以用差分的。

  这题显然两个条件都满足233

  每次对一个区间+1的时候,实际上就是区间后移一位,直接插入一个点即可,然后l所在位置+1,(r+1)所在位置-1,但是这题我们维护的是前缀max,所以每个点的信息其实是前面任意一个地方的差分值里最大的,所以我们不能-1,而是如果区间右端点右边还有+1的话,就删掉最近的一个。

  $a_i=0$时,区间为$[l,r]$,$a_i!=0$,区间看成是$a_i$这个点。

  好像写丑了跑得很慢

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define lt tree[x].ls
#define rt tree[x].rs
using namespace std;
const int maxn=500010;
struct poi{int ls, rs, sum, rnd, size, w;}tree[maxn];
int n, l, r, tott, root, tmp, mx, now, cnt;
int a[maxn];
inline void read(int &k)
{
    int f=1; k=0; char c=getchar();
    while(c<'0' || c>'9') c=='-' && (f=-1), c=getchar();
    while(c<='9' && c>='0') k=k*10+c-'0', c=getchar();
    k*=f;
}
inline void build(int &x, int delta)
{
    tree[x=++tott].w=tree[x].sum=delta;
    tree[x].rnd=rand()<<15|rand();
    tree[x].size=1;
}
inline void up(int x)
{
    tree[x].size=tree[lt].size+tree[rt].size+1;
    tree[x].sum=tree[lt].sum+tree[rt].sum+tree[x].w;
}
void split(int x, int &l, int &r, int k)
{
    if(!k) l=0, r=x;
    else if(tree[x].size==k) l=x, r=0;
    else if(tree[lt].size>=k) r=x, split(lt, l, lt, k), up(x);
    else l=x, split(rt, rt, r, k-tree[lt].size-1), up(x);
}
void merge(int &x, int l, int r)
{
    if(!l || !r) x=l+r;
    else if(tree[l].rnd>tree[r].rnd) x=l, merge(rt, rt, r), up(x);
    else x=r, merge(lt, l, lt), up(x);
}
void find(int x)
{
    if(tree[lt].sum) return find(lt);
    now+=tree[lt].size+1;
    if(tree[x].w) return;
    if(tree[rt].sum) return find(rt);
}
void del(int &root, int rk)
{
    int x, y, z;
    split(root, x, y, rk); split(x, z, x, rk-1);
    merge(root, z, y);
}
inline void solve(int l, int r)
{
    int x, y, z;
    build(tmp, 1); split(root, x, y, l-1);
    merge(x, x, tmp); merge(root, x, y);
    split(root, x, y, r); 
    if(tree[y].sum) {now=0; find(y); del(y, now);}
    merge(root, x, y);
    split(root, x, y, mx); merge(root, x, y);
}
int main()
{
    read(n); read(l); read(r); tree[0].rnd=(1ll<<31)-1; mx=r;
    for(int i=1;i<=n;i++) read(a[i]), mx=max(mx, a[i]);
    for(int i=1;i<=mx;i++) build(tmp, 0), merge(root, root, tmp);
    for(int i=1;i<=n;i++)
    if(a[i]) solve(a[i], a[i]); else solve(l, r);
    int x, y; split(root, x, y, mx);
    printf("%d\n", tree[x].sum);
}
View Code

转载于:https://www.cnblogs.com/Sakits/p/8110744.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值