The 2020 ICPC Asia Macau Regional Contest J. Jewel Grab(数颜色+链表)

J. Jewel Grab

Tartarus
_Wallace_


转化询问:对于一个询问 [s,k],找到一个最长的区间 [s,t],满足区间中出现次数超过一次的元素,的出现次数减一,的和,不超过 k。

对于该区间[s,t] 区间数颜色:也就是每种颜色只算一个权值求最大和(显然每种颜色都取最大的权值)于是有下面转化:对于所有区间中出现次数超过一次的元素,找出其中权值最大的求和;对其他的元素(仅仅出现一次)直接求和。计算最终结果。


区间数颜色显然要维护一个pre[x]数组表示前一个出现c[x]的位置在哪?

首先我们考虑如何找到区间的端点t,也就是找到[s,t]这个区间。由于k非常小,考虑一次只忽略一个宝石,也就是每次找到第一个 pre[x]>s 的 x 即可,那么由于pre[x]和x都在所考虑区间内部,于是就需要多一次忽略。

显然c[x]就是出现次数超过一次的元素 于是只需要记录mc[c[x]]每次选择最大的。而对于[cur,x)这段区间都是其他的元素(仅仅出现一次)直接区间求和即可计算贡献。


考虑如何修改?

每当修改一个位置的颜色最多会有3个位置的pre[]发生改变:

  • 该位置pre一定改变
  • 原颜色后面位置的pre(可能)改变
  • 新颜色后面位置的pre(可能)改变

可以考虑用一个双链表pre[],nxt[]维护一下。详细参考代码,代码都是抄第一篇博客的。

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}
const int N=200010;

int n,m;
int pre[N],last[N],nxt[N];
int c[N];ll v[N],mc[N];
set<int> cp[N];
void prework()
{
    memset(last,-1,sizeof last);
    for(int i=n;i>=1;i--)
    {
        nxt[i]=last[c[i]];
        last[c[i]]=i;
    }
    memset(last,-1,sizeof last);
    for(int i=1;i<=n;i++) 
    {
        pre[i]=last[c[i]];
        last[c[i]]=i;
        cp[c[i]].insert(i);
    }
}
struct node
{
    int l,r;
    int v;ll s;
}t[N<<2];
void pushup(int u)
{
    t[u].v=max(t[u<<1].v,t[u<<1|1].v);
    t[u].s=t[u<<1].s+t[u<<1|1].s;
}
void build(int u,int l,int r)
{
    t[u]={l,r};
    if(l==r)
    {
        t[u].v=pre[l];
        t[u].s=v[l];
        return;
    }
    int mid=l+r>>1;
    build(u<<1,l,mid),build(u<<1|1,mid+1,r);
    pushup(u);
}
void modify(int u,int p)
{
    if(t[u].l==t[u].r) 
    {
        t[u].v=pre[p];
        t[u].s=v[p];
        return;
    }
    int mid=t[u].l+t[u].r>>1;
    if(p<=mid) modify(u<<1,p);
    else modify(u<<1|1,p);
    pushup(u);
}
ll query(int u,int l,int r)
{
    if(l<=t[u].l&&t[u].r<=r) return t[u].s;
    int mid=t[u].l+t[u].r>>1;
    ll v=0;
    if(l<=mid) v+=query(u<<1,l,r);
    if(r>mid) v+=query(u<<1|1,l,r);
    return v;
}
int find(int u,int s,int p)
{
    if(t[u].l==t[u].r) return t[u].l;
    int mid=t[u].l+t[u].r>>1;
    int pos=-1;
    if(t[u<<1].v>=s&&p<=mid)    
        pos=find(u<<1,s,p);
    if(pos!=-1) return pos;
    if(t[u<<1|1].v>=s)
        pos=find(u<<1|1,s,p);
    return pos;
}
int main()
{
    n=rd(),m=rd();
    for(int i=1;i<=n;i++) c[i]=rd(),v[i]=rd();
    prework();
    build(1,1,n);
    while(m--)
    {
        int op=rd();
        if(op==1)
        {
            int x=rd(),c0=rd(),v0=rd();
            cp[c[x]].erase(x);
            // 最后一次出现的c[x]的位置
            if(last[c[x]]==x) last[c[x]]=pre[x];
            
            v[x]=v0;
            c[x]=c0;
            
            // 删除的修改
            // 链表的一些删除操作
            if(nxt[x]!=-1) 
            {
                if(pre[x]!=-1) 
                {
                    nxt[pre[x]]=nxt[x];
                    pre[nxt[x]]=pre[x];
                    modify(1,nxt[x]);// nxt[x]的pre[]发生改变需要维护
                }
                else
                {
                    pre[nxt[x]]=-1;
                    modify(1,nxt[x]);
                }
            }
            else
            {
                if(pre[x]!=-1) nxt[pre[x]]=-1;
            }
            
            // 插入的修改
            int pos;
            set<int>::iterator it;
            it=cp[c0].lower_bound(x);
            // 插入在末尾
            if(it==cp[c0].end())
            {
                nxt[x]=-1;
                pre[x]=last[c0];
                if(pre[x]!=-1) nxt[pre[x]]=x;
                modify(1,x);
            }
            else
            {
                pos=*it;
                if(pre[pos]!=-1) //插入不在开头
                {
                    nxt[pre[pos]]=x;
                    pre[x]=pre[pos];
                    modify(1,x);
                }// 插入在开头
                else
                {
                    pre[x]=-1;
                    modify(1,x);
                }
                nxt[x]=pos;
                pre[pos]=x;
                modify(1,pos);
            }
            cp[c0].insert(x);
            last[c0]=max(last[c0],x);
        }
        else
        {
            int s=rd(),k=rd();
            vector<int> col;
            int cur=s;
            ll ans=0;
            for(int j=0;j<=k&&cur<=n;j++)
            {
                int pos=find(1,s,cur);
                if(pos==-1) {ans+=query(1,cur,n);break;}
                
                col.push_back(c[pos]);
                if(j<k)
                {
                    // 出现一次直接选
                    if(mc[c[pos]]==0) mc[c[pos]]=v[pre[pos]];
                    // 出现第二次考虑与第一次出现的比较
                    if(v[pos]>mc[c[pos]])
                    {
                        ans-=mc[c[pos]];
                        ans+=v[pos];
                        mc[c[pos]]=v[pos];
                    }
                }
                ans+=query(1,cur,pos-1);// [cur,pos-1]都是第一次出现
                cur=pos+1;
            }
            printf("%lld\n",ans);
            for(int u:col) mc[u]=0;
        }
    }
    return 0;
}

太难了吧。。
要加油哦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值