题意:
有一串数字 主人公可以从这串数字中找出一个连续的子集扔掉其中最小的数字并得到与子集中元素个数相等数量的香肠 现在给出这串数字最后的状态 问 主人公将原串扔成目标串的样子的同时最多拿到多少香肠
思路:
为了拿到尽量多的香肠 每次选出的子集元素要尽量多 也就是要删除的元素要尽量小 这就提供了一个思路 从小往大删除元素 这个思路是有指导性的第一步
然后问题就变成 每次删除元素时 要找到该元素所在的尽量长的一串数字且保证要删除的数字是这里最小的 这可以抽象为 选择适当长度的线段 的问题 想到线段当然就联想到线段树或树状数组 这是第二步确定数据结构
最后就是实现 由于每个元素都不同 所以可以记录每个元素在数组中的位置 当要删除它的时候 可以直接找到它的位置 并尽量向左右拉长线段 灵活运用树状数组求和功能可以判断所检查的线段中是否含有曾经删除的元素或者比要删除元素小的元素 但是一个数字一个数字的检查实在复杂度太大 所以这里还需要二分查找的算法
代码:
#include<cstdio>
using namespace std;
#define M 1000005
#define lowbit(x) ( x&(-x) )
int n,m;
int a[M],vis[M],pos[M];
__int64 c[M];
void add(int x,__int64 key)
{
while(x<=n)
{
c[x]+=key;
x+=lowbit(x);
}
}
__int64 sum(int x)
{
__int64 tmp=0;
while(x)
{
tmp+=c[x];
x-=lowbit(x);
}
return tmp;
}
int main()
{
int i,j,k,L,R,ansk;
__int64 ans=0,tmp;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
add(i,1LL);
pos[a[i]]=i;
}
for(i=1;i<=m;i++)
{
scanf("%d",&j);
vis[j]=1;
}
for(i=1;i<=n;i++)
{
if(vis[i]) add(pos[i],-10LL*M);
else
{
L=1;
R=n;
ansk=j=pos[i];
tmp=sum(j);
while(L<=j)
{
k=(L+j)>>1;
if(sum(k)>tmp) L=k+1;
else
{
ansk=k;
j=k-1;
}
}
if(sum(ansk)-sum(ansk-1)>=0) ansk--;
L=ansk;
ansk=j=pos[i];
while(j<=R)
{
k=(R+j)>>1;
if(sum(k)<tmp) R=k-1;
else
{
ansk=k;
j=k+1;
}
}
R=ansk;
ans+=sum(R)-sum(L);
add(pos[i],-1);
}
}
printf("%I64d\n",ans);
return 0;
}