题解 P2418 【yyy loves OI IV】
Created_equal1 · · 题解
设F[i]表示将前i个人处理完毕所要用的最少寝室个数
考虑暴力的O(n^2)的转移,我们会发现,时间大多数用在了寻找最小且满足条件的情况
可以考虑分情况讨论,对于都是膜拜一个人的情况用数组记录,对于膜拜人数的绝对值不大于M的情况,化简得到一个不等式,然后用线段树(单点修改,区间最值)记录。
具体分析看我的代码和代码开头的注释。
//j~i可以放在一间宿舍(j<=i) c01[i]表示前i个人中膜拜c01的人数,yyy[i]同理
//情况1:c01[j - 1] = c01[i]
//情况2:yyy[j - 1] = c01[i]
//情况3:设a = yyy[i] - yyy[j - 1] b = c01[i] - c01[j - 1]
// |a - b| <= M
//需满足:①a - b <= M
// =>(yyy[i] - yyy[j - 1]) - (c01[i] - c01[j - 1]) <=M
// =>yyy[i] - yyy[j - 1] - c01[i] + c01[j - 1] <= M
// =>c01[j - 1] - yyy[j - 1] <= M + c01[i] - yyy[i]
// ②b - a <= M
// =>yyy[j - 1] - c01[j - 1] <= M + yyy[i] - c01[i]
// =>c01[j - 1] - yyy[j - 1] >= c01[i] - yyy[i] - M
//整理可得 情况3等价于:
// c01[i] - yyy[i] - M <= c01[j - 1] - yyy[j - 1] <= M + c01[i] - yyy[i]
//情况1和情况2用数组记录,情况3用线段树处理 时间复杂度O(nlogn)
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const size_t Max_N(500050);
const int Add(500000);
const int INF(0X3F3F3F3F);
void Get_Val(int &Ret)
{
Ret = 0;
char ch;
while ((ch = getchar()), (ch > '9' || ch < '0'))
;
do
{
(Ret *= 10) += ch - '0';
}
while ((ch = getchar()), (ch >= '0' && ch <= '9'));
}
struct node
{
int l, r;
int Min;
};
struct Segment_Tree
{
node segt[Max_N << 3];
void build_tree(const int&, const int&, const int&);
void change(const int&, const int&, const int &);
int rmq_min(const int&, const int&, const int&);
inline
void pushup(const int &cur)
{
segt[cur].Min = min(segt[cur << 1].Min, segt[(cur << 1) | 1].Min);
}
};
Segment_Tree Space;
void Segment_Tree::build_tree(const int &cur, const int &l, const int &r)
{
segt[cur].l = l, segt[cur].r = r, segt[cur].Min = INF;
if (l == r)
return;
int mid = l + ((r - l) >> 1);
build_tree(cur << 1, l, mid);
build_tree((cur << 1) | 1, mid + 1, r);
}
void Segment_Tree::change(const int &cur, const int &i, const int &x)
{
if (segt[cur].l == i && segt[cur].r == i)
{
segt[cur].Min = x;
return;
}
int mid = segt[cur].l + ((segt[cur].r - segt[cur].l) >> 1);
if (i <= mid)
change(cur << 1, i, x);
else
change((cur << 1) | 1, i, x);
pushup(cur);
}
int Segment_Tree::rmq_min(const int &cur, const int &l, const int &r)
{
if (segt[cur].l == l && segt[cur].r == r)
return segt[cur].Min;
int mid = segt[cur].l + ((segt[cur].r - segt[cur].l) >> 1);
if (r <= mid)
return rmq_min(cur << 1, l, r);
else
if (l > mid)
return rmq_min((cur << 1) | 1, l, r);
else
return min(rmq_min(cur << 1, l, mid), rmq_min((cur << 1) | 1, mid + 1, r));
}
int N, M;
int c01[Max_N], yyy[Max_N];
int F[Max_N];
int c01_dp[Max_N], yyy_dp[Max_N];
int Minus[Max_N << 1];
void init()
{
int v;
Get_Val(N), Get_Val(M);
for (int i = 1;i <= N;++i)
{
Get_Val(v);
yyy[i] = yyy[i - 1] + (v == 1);
c01[i] = c01[i - 1] + (v == 2);
}
}
void dp()
{
memset(c01_dp, 0X3F, sizeof(c01_dp));
memset(yyy_dp, 0X3F, sizeof(yyy_dp));
memset(Minus, 0X3F, sizeof(Minus));
Minus[0 + Add] = c01_dp[0] = yyy_dp[0] = F[0] = 0;
Space.build_tree(1, -500000, 500000);
Space.change(1, 0, 0);
int a, b, c;
for (int i = 1;i <= N;++i)
{
a = c01_dp[c01[i]];
b = yyy_dp[yyy[i]];
c = Space.rmq_min(1, c01[i] - yyy[i] - M, M + c01[i] - yyy[i]);
F[i] = min(min(a + 1, b + 1), min(c + 1, F[i - 1] + 1));
c01_dp[c01[i]] = min(c01_dp[c01[i]], F[i]);
yyy_dp[yyy[i]] = min(yyy_dp[yyy[i]], F[i]);
if (F[i] < Minus[c01[i] - yyy[i] + Add])
{
Minus[c01[i] - yyy[i] + Add] = F[i];
Space.change(1, c01[i] - yyy[i], F[i]);
}
}
printf("%d", F[N]);
}
int main()
{
init();
dp();
return 0;
}