1. 程式人生 > >Codeforces 786C Till I Collapse[主席樹][二分]

Codeforces 786C Till I Collapse[主席樹][二分]

題意:給你n個數,問最少能把這n個數分成連續的幾段,且每段中不同的個數小於等於k個,輸出k從1到n的答案。

分析:我們知道i~(i,,,n)的不同數的個數肯定是遞增的,所以對於每個i,我們可以通過二分得出一個最大的j使[i,j]中不同的數個數<=k。那麼問題的關鍵在於,如何知道[i,j]這樣一個區間中,不同的數的個數。

我們可以利用主席樹,以root[i]為頂點的線段樹存的是,[i,n]中不同的數個數,對於每顆樹,將每個第一次出現的數的位置 置為1,其他重複出現的位置 置為0。

那麼,對於每個k,剛開始我們位於root[1],二分找出值為k+1的位置r,ans[k]++,並將當前位置置成root[r],這樣,我們面對的就是[r,n]這段數,直到r>n停止。

程式碼中:ed[i]表示最先出現i的位置,Next[i]表示在i位置的數下一次出現在哪個位置。

以下是程式碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<cstring>
#include<string>
#include<vector>
//#include<unordered_set>
//#include<unordered_map>
#include<cmath>
using namespace std;

#define ull unsigned long long
#define ll long long
#define lson l,mid,id<<1
#define rson mid+1,r,id<<1|1

typedef pair<int, int>pii;
typedef pair<ll, ll>pll;
typedef pair<double, double>pdd;

const double eps = 1e-6;
const int MAXN = 100005;
const int MAXM = 5005;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int INF = 0x3f3f3f3f;
const int MOD = 1000000007;
const double FINF = 1e18;

struct {
	int lc, rc, sum;
}st[MAXN * 100];
int n, ed[MAXN], Next[MAXN];
int rt[MAXN], a[MAXN],cnt, ans[MAXN];
void update(int l, int r, int &x, int y, int pos , int val) {
	st[++cnt] = st[y], st[cnt].sum+=val, x = cnt;
	if (l == r)return;
	int mid = (l + r) / 2;
	if (pos <= mid)update(l, mid, st[x].lc, st[y].lc, pos, val);
	else update(mid + 1, r, st[x].rc, st[y].rc, pos, val);
}
int find(int l, int r, int x, int pos)
{
	if (l == r)return l;
	int mid = (l + r) >> 1;
	if (st[st[x].lc].sum >= pos)return find(l, mid, st[x].lc, pos);
	else return find(mid + 1, r, st[x].rc, pos - st[st[x].lc].sum);
}
int main()
{
	memset(ans, 0, sizeof(ans));
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		ed[i] = n + 1;
	}
	for (int i = n; i >= 1; --i)
	{
		Next[i] = ed[a[i]];
		ed[a[i]] = i;
	}
	for (int i = 1; i <= n; ++i)update(1, n + 1, rt[1], rt[1], ed[i], 1);
	for (int i = 2; i <= n; ++i)
	{
		update(1, n + 1, rt[i], rt[i - 1], i - 1, -1);
		update(1, n + 1, rt[i], rt[i], Next[i - 1], 1);
	}
	int l, r, L, R, mid, tmp;
	for (int i = 1; i <= n; ++i)
	{
		int now = 1;
		while (now <= n)
		{
			now = find(1, n + 1, rt[now], i + 1);
			ans[i]++;
		}
	}
	for (int i = 1; i <= n; ++i)cout << ans[i] << " ";
}
/*
5
1 3 4 3 3
*/