Codeforces Gym 100503F

题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=411106

题目大意:

给出一个长为n的非负数列,求删除最少的数,使任意相邻两个数差的绝对值不为1

1<=n<=10^5 ,1<=ai<=10^6

分析:

先吐槽一句:CF的Gym题居然要加文件……我一个上午就浪费在两行代码上……

首先这是一个经典问题的翻版:给出一个长为n的非负数列,求删除最少的数,使任意相邻两个数差的绝对值小于给定的d

如果用F[i]表示以i结尾的最长链,则有F[i]=max{F[j] | |a[i]-a[j]|<=d }+1

但是复杂度是O(n^2)的,难以承受。

于是我们琢磨一下这个式子:每次需要在范围[a[i]-d,a[i]+d]里找一个最大F值。

对呀这就是区间查询呀!我们建一棵以a为关键字,存储i的线段树,每次计算F[i]后用它去更新线段树(即a的值为a[i]的最大F[i]对应的i)

复杂度为O(nlogn)

在看原题,我们只要把DP方程改下,查询区间改成[0,a[i]-2]+[a[i],a[i]]+[a[i]+2,maxa]即可。因为a最大才10^6,复杂度可以接受。

核心思想:枚举->区间查询

好像有点抽象……具体看代码吧。

代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>

#define rep(i,x,y) for (int i=x;i<=y;i++)
#define dep(i,y,x) for (int i=y;i>=x;i--)

using namespace std;

const int maxn=100000+23;

int n,M,k,h,size,t[maxn*40],l[maxn],p[maxn],maxa,a[maxn],F[maxn],G[maxn],ans;

void init()
{
 for (M=1;M<=maxa;M*=2);
 memset(t,-1,sizeof(t));
}

int max2(int x,int y) //比较两个值对应DP值大小
{
 if (x==-1) return y; 
 if (y==-1) return x;
 if (F[x]>F[y]) return x; else return y;
}

void update(int x,int k)
{
 x+=M; t[x]=k; x>>=1;
 while (x>0)
 {
  t[x]=max2(t[x<<1],t[(x<<1)+1]);
  x>>=1;
 }
}

int query(int l,int r)
{
 int ans=-1; l+=M; r+=M;
 while (l<=r)
 {
  if (l==r) {ans=max2(ans,t[l]);break;}
  if (l&1) ans=max2(t[l++],ans);
  if (!(r&1)) ans=max2(t[r--],ans);
  l>>=1;r>>=1;
 }
 return ans;
}

int get_max(int l,int r,int k)
{
 if (l>r) return k;
 int h=query(l,r);
 return max2(h,k);
}

int main()
{
 freopen("input.txt","r",stdin);
 freopen("output.txt","w",stdout);
 
 scanf("%d",&n);
 
 maxa=0;
 rep(i,1,n) {scanf("%d",&a[i]);maxa=max(maxa,a[i]);} //计算a最大值,即线段树范围
 
 init(); //初始化

 rep(i,1,n)
 {
  p[i]=get_max(a[i],a[i],-1); //区间【a[i],a[i]】
  p[i]=get_max(0,a[i]-2,p[i]); //区间【0,a[i]-2】
  p[i]=get_max(a[i]+2,maxa,p[i]); //区间【a[i]+2,maxa】

  if (p[i]==-1) F[i]=1; else F[i]=F[p[i]]+1; //更新F[i]

  update(a[i],i); //用F[i]更新线段树
 }
 
 ans=0;size=1;
 rep(i,1,n) if (F[i]>ans) {ans=F[i];k=i;}

 printf("%d\n",n-ans);
 
 l[1]=a[k];

 while (p[k]>=0)
 {
  k=p[k];
  l[++size]=a[k];
 }
 
 dep(i,size,2) printf("%d ",l[i]);
 printf("%d\n",l[1]);
 
 return 0;
}

zkw线段树就是好啊……77ms,快到飞起……

posted @ 2016-07-06 16:41  Krew  阅读(191)  评论(0)    收藏  举报