J. Jewel Grab
转化询问:对于一个询问 [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;
}
太难了吧。。
要加油哦~