• Till I Collapse CodeForces


    大意: 给定序列, 将序列划分为若干段, 使得每段不同数字不超过k, 分别求出k=1...n时的答案.

    考虑贪心, 对于某个k

    从1开始, 每次查询最后一个颜色数<=k的点作为一个划分, 直到全部划分完毕

    由于每个划分大小至少为k, 故最多需要查询$frac{n}{k}$次, 所以总共需要查询$O(nlogn)$次.

    查询操作考虑用主席树实现.

    对序列中每个点维护一棵线段树, 对于位置$x$的线段树, $[x,n]$的每个位置存它到点$x$的种类数, 非叶结点存儿子的最小值用来二分.

    从大到小更新, 这样就相当于每次对[x,nxt[a[x]]-1]位置进行区间加, 可以用标记永久化来优化. 

    #include <iostream>
    #include <cstdio>
    #define REP(i,a,n) for(int i=a;i<=n;++i)
    #define PER(i,a,n) for(int i=n;i>=a;--i)
    #define hr putchar(10)
    #define lc tr[o].l
    #define rc tr[o].r
    #define mid ((l+r)>>1)
    #define ls lc,l,mid
    #define rs rc,mid+1,r
    using namespace std;
    const int N = 1e5+10;
    int n, tot, a[N], nxt[N], T[N];
    struct {int l,r,v;} tr[N<<6];
    
    void add(int &o, int l, int r, int ql, int qr) {
    	tr[++tot] = tr[o], o = tot;
    	if (ql<=l&&r<=qr) return ++tr[o].v,void();
    	int tag = tr[o].v-min(tr[lc].v,tr[rc].v);
    	if (mid>=ql) add(ls,ql,qr);
    	if (mid<qr) add(rs,ql,qr);
    	tr[o].v = tag+min(tr[lc].v,tr[rc].v);
    }
    int find(int o, int l, int r, int x, int k) {
    	if (l==r) return l;
    	k -= tr[o].v-min(tr[lc].v,tr[rc].v);
    	if (mid<x) return find(rs,x,k);
    	return tr[rc].v>k?find(ls,x,k):find(rs,x,k);
    }
    int solve(int k) {
    	int ans = 0, now = 1;
    	while (now<=n) {
    		now = find(T[now],1,n,now,k)+1;
    		++ans;
    	}
    	return ans;
    }
    int main() {
    	scanf("%d", &n);
    	REP(i,1,n) scanf("%d", a+i);
    	PER(i,1,n) {
    		T[i] = T[i+1];
    		add(T[i],1,n,i,nxt[a[i]]?nxt[a[i]]-1:n);
    		nxt[a[i]] = i;
    	}
    	REP(i,1,n) printf("%d ", solve(i));hr;
    }
    
  • 相关阅读:
    Raphael入门
    EXT使用SASS修改主题样式问题
    Hough Transform Performance article List
    about utf8 ...
    computer vision resource
    Asp.net 中配置 CKEditor和CKFinder
    压缩格式 zip与tar.gz 区别
    一系列按钮的弹出提示框借助jq实现
    SQL Server2005 sa登录出错~
    正则表达式整理
  • 原文地址:https://www.cnblogs.com/uid001/p/10983869.html
Copyright © 2020-2023  润新知