[CEOI2011] Teams

Description

\(n\) 个小朋友要进行比赛,他们要被分为若干队伍。每一个小朋友都有一个要求,其中第 \(i\) 个小朋友要求他所在的队伍最少要有 \(a_i\) 个人(包括自己)。

现在请你求出一种划分方案,在满足所有小朋友的要求的情况下,最大化队伍的数量。同时在此基础上,请你最小化人数最多的队伍的人数。

\(n, a_i\leq 10^6\)

Solution

首先考虑 \(a_i\) 最大的那个人,他一定被分在某一组,且该组人数要大于等于 \(a_i\)

那么容易通过反证法证明,我们应该把其余 \(a_i\) 较大的那些人分到这组。使得剩下的人 \(a_i\) 较小,从而能分成更多组。于是变成一个子问题。

所以我们首先将所有人按 \(a\) 排序,定义 \(f_i\) 表示,以第 \(i\) 个人结尾,最多能分成多少段。

那么

\[f_i = \max_{j\leq i - a_i} \{f_j\} + 1 \]

\(f_n\) 即为第一小问的答案,现在考虑如何分组使得各人数尽量少。

容易发现一个单调性,当我们限制各组的人数时,组数会变少,这是由于 dp 方程变为了

\[f_i = \max_{i - lim \leq j \leq i - a_i} \{ f_j\} + 1 \]

其中 \(lim\) 是限制的每组最大人数。于是可以二分答案,使其能达到第一问的答案即可。需要用线段树维护转移,复杂度 \(O(n\log^2n)\)

经过进一步思考之后,会发现每组最大人数也是可以 dp 出来的。

\(g_i\) 表示,以第 \(i\) 个人结尾的所有划分成 \(f_i\) 段的方案中,所有段人数最大值的最小值是多少。

回顾 \(f\) 的转移方程,\(g_i\) 就只能从所有 \(f_j = f_i - 1\) 的那些点 \(j\) 转移。具体而言,

\[\begin{align*} g_i &= \min_{\substack{j \leq i - a_i \\ f_j + 1 = f_i}} \{ \max \{i - j, g_j\}\} \\ &=\min_{\substack{j \leq i - a_i \\ f_j + 1 = f_i}} \{ \max \{i, g_j + j\} - j\} \\ & = \min_{\substack{j \leq i - a_i \\ f_j + 1 = f_i}} \{[j + g_j \leq i](i - j) + [j + g_j > i]g_j\} \end{align*} \]

\(h_i\) 表示 \(f_i\) 的前缀最大值。那么枚举 \(j\),考虑对后面 \(g\) 值的影响。用平衡树维护所有 \(f\) 等于 \(h_j\) 的点的集合,按 \(j + g_j\) 作为键值排序。那么对于所有 \(i - a_i = j\)\(i\),此时已经可以算出 \(g_i\)。平衡树需要维护子树 \(j\) 的最大值和 \(g_j\) 的最小值。查询的时候按 \(i\) 将其分为两段,有

\[g_i = \min \{i - \max j, \min g_j\} \]

复杂度 \(O(n\log n)\)

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstdlib>
#include <cassert>

using namespace std;

typedef long long ll;
typedef double db;

const int N = 1e6 + 7;

int n, pre[N];

struct Node
{
    int val, id;
    bool operator <(const Node &X) const
    {
        if (val != X.val)
            return val < X.val;
        return id < X.id;
    }
}a[N];

struct DP
{
    int MinLen, dp;
}A[N];

vector<int> p[N];

struct FHQ_Node
{
    int j, len, val, key;
    int Maxj, Minlen;
    int MinlenPos;
    int ls, rs;
}t[N];

inline void updateMinLen(int x, int y)
{
    if (t[x].Minlen > t[y].Minlen)
    {
        t[x].Minlen = t[y].Minlen;
        t[x].MinlenPos = t[y].MinlenPos;
    }
}

void update(int x)
{
    t[x].Maxj = max(t[t[x].ls].Maxj, t[t[x].rs].Maxj);
    t[x].Maxj = max(t[x].Maxj, t[x].j);
    t[x].Minlen = t[x].len;
    t[x].MinlenPos = t[x].j;
    if (t[x].ls)
        updateMinLen(x, t[x].ls);
    if (t[x].rs)
        updateMinLen(x, t[x].rs);
}

int merge(int x, int y)
{
    if (!x || !y)
        return x + y;
    if (t[x].key < t[y].key)
    {
        t[x].rs = merge(t[x].rs, y);
        update(x);
        return x;
    }
    else
    {
        t[y].ls = merge(x, t[y].ls);
        update(y);
        return y;
    }
}

void split(int id, int k, int &x, int &y)
{
    if (!id)
        x = y = 0;
    else
    {
        if (t[id].val <= k)
            x = id, split(t[id].rs, k, t[id].rs, y);
        else
            y = id, split(t[id].ls, k, x, t[id].ls);
        update(id);
    }
}

int NewNode(int j, int len)
{
    static int nd_cnt = 0;
    t[++nd_cnt] = (FHQ_Node){j, len, j + len, rand(), j, len, j, 0, 0};
    // cout << "nd_cnt = " << nd_cnt << "\n";
    return nd_cnt;
}

int main()
{
    srand(114514);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i].val;
        a[i].id = i;
    }
    sort(a + 1, a + 1 + n);
    for (int i = 1; i <= n; i++)
        if (i >= a[i].val)
            p[i - a[i].val].push_back(i);
    int rt = 0, x, y, z;
    for (int i = 0, now = 0; i < n; i++)
    {
        if (!i || A[i].MinLen > 0)
        {
            z = NewNode(i, A[i].MinLen);
            if (now < A[i].dp)
            {
                rt = z;
                now = A[i].dp;
            }
            else if (now == A[i].dp)
            {
                split(rt, t[z].val, x, y);
                rt = merge(merge(x, z), y);
            }
        }
        for (int & k : p[i])
        {
            A[k].dp = now + 1;
            split(rt, k, x, y);
            A[k].MinLen = n;
            if (x && A[k].MinLen > k - t[x].Maxj)
            {
                A[k].MinLen = k - t[x].Maxj;
                pre[k] = t[x].Maxj;
            }
            if (y && A[k].MinLen > t[y].Minlen)
            {
                A[k].MinLen = t[y].Minlen;
                pre[k] = t[y].MinlenPos;
            }
            rt = merge(x, y);
        }
    }
    cout << A[n].dp << "\n";
    int now = n;
    while (now)
    {
        cout << now - pre[now] << " ";
        assert(now > pre[now]);
        for (int i = pre[now] + 1; i <= now; i++)
            cout << a[i].id << " \n"[i == now];
        now = pre[now];
    }
}
posted @ 2024-11-18 22:38  Kreap  阅读(73)  评论(0编辑  收藏  举报