这是一个很菜的 Oier 的博客|

Hanx16Msgr

园龄:2年8个月粉丝:12关注:3

2022-08-05 16:24阅读: 110评论: 0推荐: 0

P5251 [LnOI2019]第二代图灵机

[LnOI2019]第二代图灵机

Luogu P5251

题目背景

1989年,Abbi提出了一种抽象的计算模型 —— 第二代图灵机 (The 2nd Generation Turing Machine)。

所谓的第二代图灵机就是指一个抽象的机器,它有一条长度为n的纸带,纸带分成了n个小方格,每个方格有不同的颜色和不同的数字

avatar

题目描述

第二代图灵的基本思想是用机器来模拟鹿们用纸笔进行数学运算的过程,他把这样的过程看作下列两种简单的动作:

  1. 在纸带上的一格写数字.
  2. 在纸带上的一段区间着色.

为了测试第二代图灵机的性能,Abbi提出了一种用于判定机器是否具有智能的试验方法,即图灵试验。

  1. [l,r]中包含所有(一共c种)颜色,数字和最小的子区间的数字和。
  2. [l,r]中没有重复颜色,数字和最大的子区间的数字和。

你需要为第二代图灵机编写算法,使他能通过所有的图灵试验。为保证试验的正确性,所有数据都是随机生成的。

输入格式

第一行输入两个正整数n,m,c,分表表示纸带长度,操作次数和颜色总数。

第二行n个非负整数,表示每个格子上的初始数字ai

第三行n个非负整数,表示每个格子上的颜色编号bi

接下来m行,对应每一次操作。

操作一格式:1xy,表示将第x位的数字改为y,保证1y10000.

操作二格式:2lry,表示将区间[l,r]的颜色全部改为y,保证1yc.

操作三格式:3lr,表示询问区间[l,r]中包含所有(一共c种)颜色,数字和最小的子区间的数字和。

操作四格式:4lr,表示询问区间[l,r]中没有重复颜色,数字和最大的子区间的数字和。

输出格式

对于操作三和操作四,输出一个整数,表示最小或最大的数字和。

对于操作三,若不存在满足条件的子区间,请输出1

样例 #1

样例输入 #1

9 8 4
17 5 8 1 6 4 12 3 4
1 1 1 1 1 1 1 3 4
2 3 6 2
3 1 9
4 1 9
4 6 9
4 1 3
2 4 5 4
3 1 1
3 1 9

样例输出 #1

23
23
23
17
-1
23

提示

avatar

由于数据规模较大,建议用以下方法读入一个正整数。

void read(int &x){
	char ch;
	while(ch = getchar(), ch < '!'); x = ch - 48;
	while(ch = getchar(), ch > '!') x = (x << 3) + (x << 1) + ch - 48;
}

Solution

难得一见的数据随机+区间推平的题目,这不用珂朵莉树真的可惜了。

看题目,会发现原数列由数字和颜色两部分组成,并且两部分的数据相互不干扰,因此可以考虑采用不同的数据结构来进行处理。颜色这一类数据因为有区间推平的操作,所以采用珂朵莉树进行维护,而数字这一维看题目有单点修改和区间求和的操作,可以选择线段树或者树状数组进行维护,那么两类修改操作就分析完毕了。

现在需要关注的就变成了 3,4 这两类操作,我们分开来讨论:

  • 操作 3

    既然要询问区间 [l,r] ,那么我们就现在珂朵莉树上把这一个区间给 split 出来再说。因为要统计区间颜色种类,所以需要开一个桶来存储(记得初始化)。注意到 ai1 ,所以有一个结论:假设 [l,r] 是一个合法区间,并且 [l,k] 是一个比 [l,r] 大的合法区间,那么因为 a 的取值范围,可以得知区间 [l,r] 的数字和一定比区间 [l,k] 的数字和小(因为多出了 (r,k] 这一部分的数字),所以可以考虑采用尺取法来在珂朵莉树上 O(logn) 地求到答案。

    尺取的过程中需要记录一个 cnt 表示目前已有的颜色种类数,那么当 cnt=c 的时候就表示找到了答案,因为 c=1lr 处于同一个区间,所以需要特判一下,此时的答案就是区间 [l,r] 的最小值(因为只选一个最小的数字一定能保证数字和最小)。否则(即 c1 时),因为要数字和最小,并且区间的左右端点位于 lr 中,所以取 l 的最右侧作为左端点、取 r 的最左侧作为右端点一定是最佳答案,这一段的数字区间和即为答案。

    int query3(int l,int r)//操作3
    {
        mem(mp,0);//mp 用于记录颜色出现次数
        auto itr=split(r+1),itl=split(l);
        auto L=itl,R=itl;int cnt=0,ans=INT_MAX;
        for (;L!=itr;L++)
        {
            while (R!=itr && cnt!=c)//可以右移r
            {
                if (!mp[R->v]++) cnt++;//加入这点的颜色
                R++;
            }
            R--;//上面循环结束的时候是已经不符合条件了,所以R--来到达最后一个符合条件的区间
            if (cnt==c)
            {
                if (L==R) ans=min(ans,queryMin(1,1,n,L->l,L->r));//c=1的情况
                else ans=min(ans,querySum(1,1,n,L->r,R->l));
            }
            R++;
            if (!--mp[L->v]) cnt--;//去除l区间
        }
        return ans==INT_MAX?-1:ans;//如果一次答案都没有更新,就说明没有答案,返回-1
    }
    
  • 操作 4

    与操作 3 类似,也可以用尺取法解决。

    因为只取一个点也是满足题意的答案,所以答案初始化为区间 [l,r] 的最大值。

    因为要求不能有重复的颜色,所以尺取右移 r 的过程中加入的区间只能够是长度为 1 的区间,凡是大于 1 的区间都会存在有两个相同颜色的数。在右移 r 完成后,如果 r 右侧有长度大于 1 的区间,那么可以取这一个区间的左端点来作为答案区间的右端点(因为只取了一个点所以不会导致颜色重复)。更新完答案后将这一个点的影响清除,再右移 l 进行下一步。

    int query4(int l,int r)//操作4
    {
        mem(mp,0);//记录颜色
        auto itr=split(r+1),itl=split(l);
        auto L=itl,R=itl;
        int ans=queryMax(1,1,n,l,r);//初始化为区间最大值
        for (;L!=itr;L++)
        {
            if (L==R) mp[R->v]++,R++;//如果左右端点在同一区间,相当于只取一个点,已经考虑过了所以右移R
            while (R!=itr && !mp[R->v] && R->r-R->l+1==1) mp[R->v]++,R++;//不断加入长度为1的区间
            bool AddNew=0;//记录是否加入了一个长度大于1的区间的左端点
            if (R!=itr && !mp[R->v]) mp[R->v]++,AddNew=1,R++;//可以加入
            R--;
            if (L!=R) ans=max(ans,querySum(1,1,n,L->r,R->l));//更新答案
            mp[L->v]--;//清除l的影响
            if (AddNew) mp[R->v]--,R--;//清除右侧区间的影响
            R++;
        }
        return ans;
    }
    

分析完询问操作后,可以发现,我们需要在数字上维护的不仅是单点修改+区间求和,还有区间最值的操作,因为树状数组只能进行求和,不能求区间最值,所以使用线段树可以很好的节省码量。

既然这是道黑题,那来这里的人一定都会珂朵莉树和线段树了吧,所以我就不在这里讲解这两种数据结构了。

Code

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define LC (k<<1)
#define RC ((k<<1)|1)
using namespace std;
template<typename T> void read(T &k)
{
    k=0;T flag=1;char b=getchar();
    while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
    while (isdigit(b)) {k=k*10+b-48;b=getchar();}
    k*=flag;
}
const int _SIZE=1e5,_MAXN=1e4;
int n,m,c;
int sum[(_SIZE<<2)+5],minn[(_SIZE<<2)+5],maxn[(_SIZE<<2)+5];
int num[_SIZE+5],col[_SIZE+5];
int mp[_MAXN+5];
void pushup(int k) //线段树标准操作
{
    sum[k]=sum[LC]+sum[RC];
    minn[k]=min(minn[LC],minn[RC]);
    maxn[k]=max(maxn[LC],maxn[RC]);
}
void buildSeq(int k,int l,int r)
{
    if (l==r)
    {
        sum[k]=minn[k]=maxn[k]=num[l];
        return;
    }
    int mid=l+r>>1;
    buildSeq(LC,l,mid);
    buildSeq(RC,mid+1,r);
    pushup(k);
}
void update(int k,int l,int r,int pos,int v)
{
    if (l==r && l==pos)
    {
        sum[k]=minn[k]=maxn[k]=v;
        return;
    }
    if (l>pos || r<pos) return;
    int mid=l+r>>1;
    update(LC,l,mid,pos,v);
    update(RC,mid+1,r,pos,v);
    pushup(k);
}
int querySum(int k,int l,int r,int a,int b)
{
    if (l>b || r<a) return 0;
    if (l>=a && r<=b) return sum[k];
    int mid=l+r>>1;
    return querySum(LC,l,mid,a,b)+querySum(RC,mid+1,r,a,b);
}
int queryMin(int k,int l,int r,int a,int b)
{
    if (l>b || r<a) return INT_MAX;
    if (l>=a && r<=b) return minn[k];
    int mid=l+r>>1;
    return min(queryMin(LC,l,mid,a,b),queryMin(RC,mid+1,r,a,b));
}
int queryMax(int k,int l,int r,int a,int b)
{
    if (l>b || r<a) return -1;
    if (l>=a && r<=b) return maxn[k];
    int mid=l+r>>1;
    return max(queryMax(LC,l,mid,a,b),queryMax(RC,mid+1,r,a,b));
}
struct NODE{//珂朵莉树标准操作
    int l,r;
    mutable int v;
    NODE (int l,int r=0,int v=0) : l(l),r(r),v(v) {}
    bool operator< (const NODE &a) const {return l<a.l;}
};
set<NODE> ctlt;
auto split(int pos)
{
    auto it=ctlt.lower_bound(NODE(pos));
    if (it!=ctlt.end() && it->l==pos) return it;
    it--;
    if (it->r<pos) return ctlt.end();
    int l=it->l,r=it->r,v=it->v;
    ctlt.erase(it);
    ctlt.insert(NODE(l,pos-1,v));
    return ctlt.insert(NODE(pos,r,v)).first;
}
void assign(int l,int r,int v)
{
    auto itr=split(r+1),itl=split(l);
    ctlt.erase(itl,itr);
    ctlt.insert(NODE(l,r,v));
}
void buildCtlt() {for (int i=1;i<=n;i++) ctlt.insert(NODE(i,i,col[i]));}//直接把每个颜色看作区间[i,i]的颜色col[i]
int query3(int l,int r)//操作3
{
    mem(mp,0);//mp 用于记录颜色出现次数
    auto itr=split(r+1),itl=split(l);
    auto L=itl,R=itl;int cnt=0,ans=INT_MAX;
    for (;L!=itr;L++)
    {
        while (R!=itr && cnt!=c)//可以右移r
        {
            if (!mp[R->v]++) cnt++;//加入这点的颜色
            R++;
        }
        R--;//上面循环结束的时候是已经不符合条件了,所以R--来到达最后一个符合条件的区间
        if (cnt==c)
        {
            if (L==R) ans=min(ans,queryMin(1,1,n,L->l,L->r));//c=1的情况
            else ans=min(ans,querySum(1,1,n,L->r,R->l));
        }
        R++;
        if (!--mp[L->v]) cnt--;//去除l区间
    }
    return ans==INT_MAX?-1:ans;//如果一次答案都没有更新,就说明没有答案,返回-1
}
int query4(int l,int r)//操作4
{
    mem(mp,0);//记录颜色
    auto itr=split(r+1),itl=split(l);
    auto L=itl,R=itl;
    int ans=queryMax(1,1,n,l,r);//初始化为区间最大值
    for (;L!=itr;L++)
    {
        if (L==R) mp[R->v]++,R++;//如果左右端点在同一区间,相当于只取一个点,已经考虑过了所以右移R
        while (R!=itr && !mp[R->v] && R->r-R->l+1==1) mp[R->v]++,R++;//不断加入长度为1的区间
        bool AddNew=0;//记录是否加入了一个长度大于1的区间的左端点
        if (R!=itr && !mp[R->v]) mp[R->v]++,AddNew=1,R++;//可以加入
        R--;
        if (L!=R) ans=max(ans,querySum(1,1,n,L->r,R->l));//更新答案
        mp[L->v]--;//清除l的影响
        if (AddNew) mp[R->v]--,R--;//清除右侧区间的影响
        R++;
    }
    return ans;
}
int main()
{
    read(n),read(m),read(c);
    for (int i=1;i<=n;i++) read(num[i]);
    for (int i=1;i<=n;i++) read(col[i]);
    buildSeq(1,1,n);//建线段树
    buildCtlt();//建珂朵莉树
    for (int i=1;i<=m;i++)
    {
        int op,x,y,l,r;read(op);
        if (op==1)
        {
            read(x),read(y);
            update(1,1,n,x,y);
        }
        else if (op==2)
        {
            read(l),read(r),read(y);
            assign(l,r,y);
        }
        else if (op==3)
        {
            read(l),read(r);
            printf("%d\n",query3(l,r));
        }
        else 
        {
            read(l),read(r);
            printf("%d\n",query4(l,r));
        }
    }
    return 0;
}
posted @   Hanx16Msgr  阅读(110)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起