@description@

体育课上,n个小朋友排成一行(从1到n编号),老师想把他们分成若干组,每一组都包含编号连续的一段小朋友,每个小朋友属于且仅属于一个组。

第i个小朋友希望它所在的组的人数不多于d[i],不少于c[i],否则他就会不满意。

在所有小朋友都满意的前提下,求可以分成的组的数目的最大值,以及有多少种分组方案能达到最大值。

原题传送门。

@solution@

记 \(dp_i\) 表示前 i 个人进行分组的最优值及方案数。我们可以从满足 \(\max\{c_{j+1\dots i}\} \leq i - j \leq \min\{d_{j+1\dots i}\}\) 的 j 转移到 i。

朴素 dp 是 \(O(n^2)\) 的,需要优化。

d 有单调性的:当 j 离 i 越远,\(\min\{d_{j+1\dots i}\}\) 越小同时 \(i - j\) 越大。

我们可以记 \(left_i\) 表示只考虑 d 的限制,能够转移到 i 的最小 \(j\)。

不过,c 没有单调性。

注意到对于区间 [l, r],如果 c 的最大值在 x,则从 [l, x-1] 转移到 [x, r] 的限制全部都是 c[x]。

因此,我们可以考虑根据区间最大值(笛卡尔树)进行分治。

不妨记 k = c[x]。此时如果 [l, x-1] 中的某个 j 可以转移到 [x, r] 中的某个 i,则应有 \(left_i \leq j \leq i - k\)。这是一个和 i 有关的区间,可以使用线段树维护。

但是由于区间并不是等分两份然后递归,此时复杂度 \(T(a + b) = T(a) + T(b) + b\log n\) 还是可能会炸。

对于这种非等分的分治,我们可以考虑怎么把复杂度变为 \(T(a + b) = T(a) + T(b) + \min\{a, b\}\log n\),这样总时间复杂度就会降为 \(O(n\log^2 n)\)(可以考虑成启发式合并的时间复杂度)。

对于某个 j,它所能转移到的 i 也是个和 j 有关的区间,只需要根据 \(left\) 的单调性进行二分。这个线段树区间修改即可。

左右区间哪个小,就在那个区间进行扫描 + 线段树查询/区间修改。这样就可以做到 \(O(n\log^2 n)\),但是还不足以通过本题。

\(left\) 是一个很棘手的条件,我们考虑将 [x, r] 的 i 分成 3 种类型:\(left_i < l\);\(l \leq left_i < x\);\(x \leq left_i\),每一种都是连续的区间。

第 3 种直接不管。

第 2 种的 i 满足 \(left_i < x \leq i\),也就是区间 \([left_i, i]\) 横跨了分治中心。对于每个 i 只会有一次横跨,因此直接暴力 \(O(\log n)\) 线段树查询总时间复杂度依然是 \(O(n\log n)\)。

此时第 1 种就没有 \(left\) 的限制了,只需要满足 \(j \leq i - k\)。

我们先对 x 求出满足 \(j \leq x - k\) 的所有 j 的贡献(如果左区间小就暴力扫;如果右区间小就线段树查)。

此时如果 i 变为 i + 1,则最多只会贡献一个新的 j;同理如果 j 变为 j + 1,则转移范围最多少 1。因此直接扫描一遍,就不需要线段树。

最后如果左区间小,则还需要把那些取值范围覆盖整个左区间的点 i 线段树区间修改。

第 1 种的复杂度为 \(T(a + b) = T(a) + T(b) + \min\{a, b\} + \log n\),前面依然通过启发式合并的想法得到复杂度 \(O(n\log n)\);而后面的 \(\log n\) 的贡献是决策树内的点数,由于决策树大小为 O(n) 所以总复杂度为 \(O(n\log n)\)。

因此总时间复杂度 \(O(n\log n)\)。

@accepted code@

#include <cstdio>
#include <algorithm>
using namespace std; const int MOD = 1000000007;
const int MAXN = 1000000;
const int INF = (1 << 30); struct type{
int mx, cnt; type() : mx(-INF) {}
type(int _m, int _c) : mx(_m), cnt(_c) {}
friend type operator + (const type &a, const type &b) {
if( a.mx < 0 ) return b;
else if( b.mx < 0 ) return a;
else {
if( a.mx == b.mx )
return type(a.mx, a.cnt + b.cnt >= MOD ? a.cnt + b.cnt - MOD : a.cnt + b.cnt);
else return (a.mx > b.mx ? a : b);
}
}
};
type func(type x) {return x.mx >= 0 ? type(x.mx + 1, x.cnt) : type();} namespace segtree{
type tag[2*MAXN + 5], mx[2*MAXN + 5]; inline int id(int l, int r) {return (l + r) | (l != r);}
void maintain(int x, type t) {tag[x] = tag[x] + t, mx[x] = mx[x] + t;}
void pushup(int l, int r) {
int m = (l + r) >> 1;
int x = id(l, r), lch = id(l, m), rch = id(m + 1, r);
mx[x] = mx[lch] + mx[rch];
}
void pushdown(int l, int r) {
int m = (l + r) >> 1;
int x = id(l, r), lch = id(l, m), rch = id(m + 1, r);
if( tag[x].mx >= 0 ) {
maintain(lch, tag[x]);
maintain(rch, tag[x]);
tag[x] = type();
}
}
void update(int l, int r, int ql, int qr, type k) {
int x = id(l, r);
if( ql > r || qr < l ) return ;
if( ql <= l && r <= qr ) {
maintain(x, k);
return ;
}
pushdown(l, r);
int m = (l + r) >> 1;
update(l, m, ql, qr, k);
update(m + 1, r, ql, qr, k);
pushup(l, r);
}
type query(int l, int r, int ql, int qr) {
int x = id(l, r);
if( ql > r || qr < l ) return type();
if( ql <= l && r <= qr ) return mx[x];
pushdown(l, r); int m = (l + r) >> 1;
return query(l, m, ql, qr) + query(m + 1, r, ql, qr);
}
}; int read() {
int x = 0; char ch = getchar();
while( '0' > ch || ch > '9' ) ch = getchar();
while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
return x;
} int c[MAXN + 5], d[MAXN + 5], n; int ch[2][MAXN + 5], stk[MAXN + 5], tp, rt;
void get() {
for(int i=1;i<=n;i++) {
int nw = 0;
while( tp && c[stk[tp]] <= c[i] )
ch[1][stk[tp]] = nw, nw = stk[tp--];
stk[++tp] = i, ch[0][i] = nw;
}
while( tp ) {
int x = stk[tp--];
if( tp ) ch[1][stk[tp]] = x;
else rt = x;
}
} int le[MAXN + 5]; type dp[MAXN + 5];
void solve(int l, int r, int x) {
// printf("%d %d %d\n", l, r, x);
if( l == r ) {
segtree::update(0, n, l, r, dp[l]);
dp[l] = segtree::query(0, n, l, r);
return ;
}
else {
solve(l, x - 1, ch[0][x]); int k = c[x];
int p = lower_bound(le + x, le + r + 1, l) - le;
int q = lower_bound(le + x, le + r + 1, x) - le; if( x - l < r - x + 1 ) {
type nw = type();
for(int i=l;i<x;i++) {
nw = nw + func(dp[i]);
if( x <= i + k && i + k < p )
dp[i + k] = dp[i + k] + nw;
}
segtree::update(0, n, x + k, p - 1, nw);
}
else {
type nw = func(segtree::query(0, n, l, x - k - 1));
for(int i=x;i<p;i++) {
if( l <= i - k && i - k < x )
nw = nw + func(dp[i - k]);
dp[i] = dp[i] + nw;
}
} for(int i=p;i<q;i++)
dp[i] = dp[i] + func(segtree::query(0, n, le[i], min(i - k, x - 1))); solve(x, r, ch[1][x]);
}
}
int main() {
n = read();
for(int i=1;i<=n;i++)
c[i] = read(), d[i] = read(); int s, t; stk[s = t = 1] = 1, le[1] = 0;
for(int i=2;i<=n;i++) {
while( s <= t && d[i] <= d[stk[t]] )
t--;
stk[++t] = i; le[i] = le[i - 1];
while( i - le[i] > d[stk[s]] ) {
le[i]++;
while( stk[s] <= le[i] ) s++;
}
} dp[0] = type(0, 1), get(), solve(0, n, rt);
if( dp[n].mx >= 0 ) printf("%d %d\n", dp[n].mx, dp[n].cnt);
else puts("NIE");
}

@details@

本题卡空间,所以你需要一些卡空间的手段(比如线段树空间从 4n 降到 2n 的方法)。

@bzoj - 3711@ [PA2014]Druzyny的更多相关文章

  1. BZOJ 3721: PA2014 Final Bazarek

    3721: PA2014 Final Bazarek Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 645  Solved: 261[Submit][ ...

  2. BZOJ 3709: [PA2014]Bohater

    3709: [PA2014]Bohater Time Limit: 5 Sec  Memory Limit: 128 MBSec  Special JudgeSubmit: 1050  Solved: ...

  3. 【贪心】bzoj 3709:[PA2014]Bohater

    3709: [PA2014]Bohater Time Limit: 5 Sec  Memory Limit: 128 MBSec  Special JudgeSubmit: 653  Solved:  ...

  4. BZOJ 3713: [PA2014]Iloczyn( 枚举 )

    斐波那契数列<10^9的数很少很少...所以直接暴力枚举就行了... ------------------------------------------------------------- ...

  5. bzoj 3714 [PA2014]Kuglarz 最小生成树

    [PA2014]Kuglarz Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1335  Solved: 672[Submit][Status][Di ...

  6. bzoj 3722: PA2014 Final Budowa

    3722: PA2014 Final Budowa Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 303  Solved: 108[Submit][St ...

  7. BZOJ 3712: [PA2014]Fiolki 倍增+想法

    3712: [PA2014]Fiolki Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 437  Solved: 115[Submit][Status ...

  8. P5979 [PA2014]Druzyny dp 分治 线段树 分类讨论 启发式合并

    LINK:Druzyny 这题研究了一下午 终于搞懂了. \(n^2\)的dp很容易得到. 考虑优化.又有大于的限制又有小于的限制这个非常难处理. 不过可以得到在限制人数上界的情况下能转移到的最远端点 ...

  9. BZOJ3711 : [PA2014]Druzyny

    设f[i]为[1,i]分组的最优解,则 f[i]=max(f[j]+1),max(c[j+1],c[j+2],...,c[i-1],c[i])<=i-j<=min(d[j+1],d[j+2 ...

随机推荐

  1. Postgres基础操作

    显示数据库\l \l+ dw=# \l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ...

  2. 使用vue2.0创建的项目的步骤

    1.由于vue项目依赖 node.js npm 需要先安装.   若没有请先安装,请百度 //检查是否有node.js  npm vue win+r   输入cmd  输入node -v  回车 会出 ...

  3. python 生成随机字符串

    1.生成随机字符串 #数字+字母+符号 def getRandChar(n): l = [] #sample = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^ ...

  4. Tortoise svn 基础知识

    1 不跟踪文件.文件夹 1.1  文件.文件夹已经被svn跟踪 将本地文件.文件夹删除(windows删除文件的删除,快捷键是shift+delete),然后执行svn  update 将服务器同步到 ...

  5. Hive 集成 Hudi 实践(含代码)| 可能是全网最详细的数据湖系列

    公众号后台越来越多人问关于数据湖相关的内容,看来大家对新技术还是很感兴趣的.关于数据湖的资料网络上还是比较少的,特别是实践系列,对于新技术来说,基础的入门文档还是很有必要的,所以这一篇希望能够帮助到想 ...

  6. Android_适配器(adapter)之ArrayAdapter

    ArrayAdapter是一个很简单的适配器,是BaseAdapter的子类. ArrayAdapter绑定的数据是集合或数组,比较单一.视图是列表形式,ListView 或 Spinner. Arr ...

  7. @Spring Boot程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码

    前言 这篇其实是对一年前的一篇文章的补坑. @Java Web 程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码 当时,就是在spring mvc应用里定义一个 ...

  8. 【转】shell的反引号、单引号、双引号的作用

    Linux Shell中有三种引号,分别为双引号(" ").单引号(' ')以及反引号(` `). 其中双引号对字符串中出现的$.''.`和\进行替换:单引号不进行替换,将字符串中 ...

  9. ActiveMQ 笔记(一)概述与安装

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.消息中间件的产生背景 1.前言:考虑消息中间件的使用场景? 在何种场景下需要使用消息中间件 为什么要 ...

  10. Java 第十一届 蓝桥杯 省模拟赛 第十层的二叉树

    一棵10层的二叉树,最多包含多少个结点? 注意当一棵二叉树只有一个结点时为一层. 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可.本题的结果为一个整数,在提交答案时只填写这个整数,填写多余 ...