[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_n\) 即为第一小问的答案,现在考虑如何分组使得各人数尽量少。
容易发现一个单调性,当我们限制各组的人数时,组数会变少,这是由于 dp 方程变为了
其中 \(lim\) 是限制的每组最大人数。于是可以二分答案,使其能达到第一问的答案即可。需要用线段树维护转移,复杂度 \(O(n\log^2n)\)。
经过进一步思考之后,会发现每组最大人数也是可以 dp 出来的。
令 \(g_i\) 表示,以第 \(i\) 个人结尾的所有划分成 \(f_i\) 段的方案中,所有段人数最大值的最小值是多少。
回顾 \(f\) 的转移方程,\(g_i\) 就只能从所有 \(f_j = f_i - 1\) 的那些点 \(j\) 转移。具体而言,
令 \(h_i\) 表示 \(f_i\) 的前缀最大值。那么枚举 \(j\),考虑对后面 \(g\) 值的影响。用平衡树维护所有 \(f\) 等于 \(h_j\) 的点的集合,按 \(j + g_j\) 作为键值排序。那么对于所有 \(i - a_i = j\) 的 \(i\),此时已经可以算出 \(g_i\)。平衡树需要维护子树 \(j\) 的最大值和 \(g_j\) 的最小值。查询的时候按 \(i\) 将其分为两段,有
复杂度 \(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];
}
}