Codeforces 786C Till I Collapse

题意:
给出一个长度为n的序列,每个数值在1-n之间且为整数,现在要把这个序列划分为若干段,使得每一段的颜色种数不超过k,求最少的区间数目.对于从1到n的n种k的取值,分别输出这时的最少区间数目.
分析:
首先这个题很像HH的项链,而HH的项链的在线做法需要写可持久化线段树,我们自然想到这个题也需要可持久化线段树
考虑对于每个k分别求解.暴力的贪心做法是这样:从左端开始,每次找出一段尽量长的颜色种数不超过k的区间划分出来.如果颜色种数要求不超过k,那么划分的区间段数最多为n/k上取整.(k个元素一段即可).对于k的n种取值,一共最多要找nln(n)段区间.如果能在较低的复杂度(例如logn)内找出每一段区间,这个题就做出来了.
然后我的想法是类似bzoj4504 K个串,写了一发区间修改的可持久化线段树,然后每次在线段树上二分.Codeforces的官方题解大概相当于这个做法差分一下,不需要区间修改,只需要单点修改的可持久化线段树.
具体是:对于每个左端点L,我们求出一个数组f[L][],f[L][i]存储从L到i出现的不同颜色的种数(如果i<L那么f[L][i]=0).那么从F[L][]到F[L-1][],只有a[L-1]影响的一段区间的数值会+1,那么可以用可持久化线段树来维护这个序列.查询时如果二分答案,每次在线段树上查询是两个log,直接在线段树上二分(也就是在每个节点判断向左侧还是右侧递归求解)就是一个log的.我写了一发标记永久化.细节参见代码.

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100005;
struct node{
  int Max,mk;
  node* ch[2];
  node(){}
  node(int x,int y){
    ch[0]=ch[1]=0;Max=x;mk=y;
  }
}t[maxn*60];int tsz=0;
node* root[maxn];
node* newnode(int x,int y){
  t[++tsz]=node(x,y);return t+tsz;
}
int a[maxn];
int last[maxn];
void Insert(node* rt0,node* &rt,int l,int r,int ql,int qr){
  if(ql<=l&&r<=qr){
    rt=newnode(rt0->Max+1,rt0->mk+1);
    rt->ch[0]=rt0->ch[0];rt->ch[1]=rt0->ch[1];
  }else{
    rt=newnode(0,rt0->mk);
    rt->ch[0]=rt0->ch[0];rt->ch[1]=rt0->ch[1];
    int mid=(l+r)>>1;
    if(ql<=mid)Insert(rt0->ch[0],rt->ch[0],l,mid,ql,qr);
    if(qr>mid)Insert(rt0->ch[1],rt->ch[1],mid+1,r,ql,qr);
    rt->Max=max(rt->ch[0]->Max,rt->ch[1]->Max)+rt0->mk;
  }
}
int n;
int query(node* rt,int l,int r,int lim,int pre){
  if(l==r){//printf("%d\n",pre);
    if(pre+rt->Max>lim)return -1;
    else return l;
  }
  int lmax=rt->ch[0]->Max+pre+rt->mk;
  int mid=(l+r)>>1;
  if(lmax>lim)return query(rt->ch[0],l,mid,lim,pre+rt->mk);
  else if(lmax<lim)return query(rt->ch[1],mid+1,r,lim,pre+rt->mk);
  int t=query(rt->ch[1],mid+1,r,lim,pre+rt->mk);
  if(t==-1)return mid;
  else return t;
}
int right(int s,int lim){
  return query(root[s],1,n,lim,0);
}
int work(int lim){
  int pt=1;
  int ans=0;
  while(pt<=n){
    ans++;
    pt=right(pt,lim)+1;//if(lim==1)printf("%d\n",pt);
  }
  return ans;
}
int main(){
  scanf("%d",&n);
  for(int i=1;i<=n;++i)scanf("%d",a+i);
  root[n+1]=newnode(0,0);
  root[n+1]->ch[0]=root[n+1]->ch[1]=root[n+1];
  for(int i=1;i<=n;++i)last[i]=n+1;
  for(int i=n;i>=1;--i){
    root[i]=root[i+1];
    Insert(root[i],root[i],1,n,i,last[a[i]]-1);
    last[a[i]]=i;
  }
  //  printf("%d\n",query(root[1],1,n,1,0));return 0;
  for(int i=1;i<=n;++i){
    printf("%d ",work(i));
  }
  return 0;
}

转载于:https://www.cnblogs.com/liu-runda/p/6952508.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值