传送门


被暴力包菜了,然而还不会卡……

有一个很暴力的DP:设\(f_i\)表示给\(1\)到\(i\)分好组最多可以分多少组,转移枚举最后一个组。接下来考虑优化这个暴力。

考虑:对于每一个位置\(i\),设\(pre_i\)表示在仅考虑\(d\)的条件下右端点为\(i\)的所有满足条件的区间中最左的左端点的前一个位置。显然\(pre_i\)随\(i\)的增大是不降的,而右端点为\(i\)的合法区间的左端点范围恰好为\([pre_i + 1 , i]\)。

这里我们消除了\(d\)的条件的限制,那么只需要考虑\(c\),而一个区间的\(c\)的限制只取决于其最大值,所以考虑最大值分治。

设正在解决区间\([l,r]\),我们找到\([l+1,r]\)的最大值\(p\)(注意是\([l+1,r]\),因为\(f_l\)贡献到\(f_r\)时只考虑\(c_{[l+1,r]}\)的值),那么\(forall i \in [l,p) , j \in [p,r] , \max\limits_{k \in [i+1,j]} \{c_k\} = c_p\)。先递归\([l,p-1]\),然后将\([l,p-1]\)对\([p,r]\)的贡献处理,然后解决\([p,r]\)。

对于\([l,p-1]\)对\([p,r]\)的贡献,注意到\(pre_i\)不降,所以对于每一个\(i \in [p,r]\),将转移分为三种情况:

①\(pre_i \leq l , i - c_p < p - 1\),在这种情况下\(i\)位置会从一段前缀转移过来,而且\(i\)增加\(1\),前缀长度增加\(1\)。在第一次的时候用线段树查一下最前面一段的最大值,之后每一次\(O(1)\)地更新这个前缀的值。

②\(pre_i \leq l , i - c_p \geq p - 1\),这种情况转移一定会是整个左区间。二分出满足\(pre_j \leq l\)的最大的\(j\)然后在线段树上区间修改。

③\(pre_i > l\),这种情况暴力在线段树上查对应区间。

④\(pre_i \geq p\),直接退出。

考虑复杂度:①中每一次只有一个\(log\)的查询和\(\min(p - l , r - p + 1)\)地查询,复杂度和最大值分治一致;②每一次只会有一个\(log\)的修改;③因为对于任意一个\(i\),\(pre_i\)在当前分治区域的左区间、\(i\)在当前分治区域的右区间的分治区域数量至多为\(1\),所以总复杂度是\(O(nlogn)\)。所以总共复杂度为\(O(nlogn)\)。

一些细节:

1、卡空间……求\(c\)的最大值用线段树而不是ST表;

2、线段树区间打标记并不需要动态维护当前区间的最大\(c\)值和方案数,只需要在分治区域大小为\(1\)的时候在线段树上求一下这个位置的实际答案。

#include<bits/stdc++.h>
//This code is written by Itst
using namespace std; inline int read(){
int a = 0;
char c = getchar();
while(!isdigit(c))
c = getchar();
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return a;
} const int MAXN = 1e6 + 1 , MOD = 1e9 + 7;
int add(int a , int b){return a + b >= MOD ? a + b - MOD : a + b;}
struct PII{
int st , nd;
PII(int _st = 0 , int _nd = 0) : st(_st) , nd(_nd){}
}dp[MAXN];
int C[MAXN] , D[MAXN] , pre[MAXN] , N;
//int ST[20][MAXN] , logg2[MAXN]; PII merge(PII a , PII b){
if(a.st == b.st) return PII(a.st , add(a.nd , b.nd));
return a.st > b.st ? a : b;
} int cmp(int a , int b){return C[a] > C[b] ? a : b;}
namespace segTree2{
int Max[MAXN << 2]; #define mid ((l + r) >> 1)
#define lch (x << 1)
#define rch (x << 1 | 1) void init(int x , int l , int r){
if(l == r) Max[x] = l;
else{
init(lch , l , mid); init(rch , mid + 1 , r);
Max[x] = cmp(Max[lch] , Max[rch]);
}
} int query(int x , int l , int r , int L , int R){
if(l >= L && r <= R) return Max[x];
int cur = 0;
if(mid >= L) cur = query(lch , l , mid , L , R);
if(mid < R) cur = cmp(cur , query(rch , mid + 1 , r , L , R));
return cur;
}
} namespace segTree{
struct node{
PII maxN , mrk;
node(){maxN = mrk = PII(-1e9 , 0);}
}Tree[MAXN * 2 + 100000]; #define mid ((l + r) >> 1)
#define lch (x << 1)
#define rch (x << 1 | 1) void pushup(int x){Tree[x].maxN = merge(Tree[lch].maxN , Tree[rch].maxN);}
void mark(int x , PII mrk){Tree[x].mrk = merge(Tree[x].mrk , mrk);}
void pushdown(int x){
mark(lch , Tree[x].mrk); mark(rch , Tree[x].mrk);
Tree[x].mrk = PII(-1e9 , 0);
} void modify(int x , int l , int r , int L , int R , PII mrk){
if(l >= L && r <= R) return mark(x , mrk);
pushdown(x);
if(mid >= L) modify(lch , l , mid , L , R , mrk);
if(mid < R) modify(rch , mid + 1 , r , L , R , mrk);
} void update(int tar){
int x = 1 , l = 0 , r = N;
while(l != r){
pushdown(x);
if(mid >= tar){x = lch; r = mid;}
else{x = rch; l = mid + 1;}
}
dp[tar] = Tree[x].maxN = merge(dp[tar] , Tree[x].mrk);
while(x >>= 1) pushup(x);
} PII query(int x , int l , int r , int L , int R){
if(L > R) return PII(-1e9 , 0);
if(l >= L && r <= R) return Tree[x].maxN;
pushdown(x);
PII sum(-1e9 , 0);
if(mid >= L) sum = merge(sum , query(lch , l , mid , L , R));
if(mid < R) sum = merge(sum , query(rch , mid + 1 , r , L , R));
return sum;
}
} priority_queue < int , vector < int > , greater < int > > q1 , q2;
void maintain(){while(!q2.empty() && q1.top() == q2.top()){q1.pop(); q2.pop();}}
void init(){
/*for(int i = 2 ; i <= N ; ++i)
logg2[i] = logg2[i >> 1] + 1;
for(int i = 1 ; i <= N ; ++i)
ST[0][i] = i;
for(int i = 1 ; 1 << i <= N ; ++i)
for(int j = 1 ; j + (1 << i) - 1 <= N ; ++j)
ST[i][j] = cmp(ST[i - 1][j] , ST[i - 1][j + (1 << (i - 1))]);*/
segTree2::init(1 , 1 , N);
for(int i = 1 ; i <= N ; ++i){
pre[i] = pre[i - 1];
q1.push(D[i]); maintain();
while(q1.top() < i - pre[i]){
q2.push(D[++pre[i]]); maintain();
}
}
} int qST(int x , int y){
//int t = logg2[y - x + 1];
//return cmp(ST[t][x] , ST[t][y - (1 << t) + 1]);
return segTree2::query(1 , 1 , N , x , y);
} void solve(int l , int r){
if(l == r)
return segTree::update(l);
int p = qST(l + 1 , r); solve(l , p - 1);
int pos = max(p , l + C[p]);
PII cur = segTree::query(1 , 0 , N , l , pos - C[p] - 1);
while(pos <= r && pre[pos] <= l && pos - C[p] < p){
cur = merge(cur , dp[pos - C[p]]);
dp[pos] = merge(dp[pos] , PII(cur.st + 1 , cur.nd));
++pos;
}
if(pos <= r && pre[pos] <= l){
int L = pos , R = r;
while(L < R){
int Mid = (L + R + 1) >> 1;
pre[Mid] <= l ? L = Mid : R = Mid - 1;
}
segTree::modify(1 , 0 , N , pos , L , PII(cur.st + 1 , cur.nd));
pos = L + 1;
}
while(pos <= r && pre[pos] < p){
cur = segTree::query(1 , 0 , N , pre[pos] , min(pos - C[p] , p - 1));
dp[pos] = merge(dp[pos] , PII(cur.st + 1 , cur.nd));
++pos;
}
solve(p , r);
} int main(){
#ifndef ONLINE_JUDGE
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
#endif
N = read();
fill(dp + 1 , dp + N + 1 , PII(-1e9 , 0)); dp[0].nd = 1;
for(int i = 1 ; i <= N ; ++i){C[i] = read(); D[i] = read();}
init(); solve(0 , N);
if(dp[N].st <= 0)
puts("NIE");
else
printf("%d %d\n" , dp[N].st , dp[N].nd);
return 0;
}

BZOJ3711 Druzyny 最大值分治、线段树的更多相关文章

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

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

  2. UVALive 7148 LRIP【树分治+线段树】

    题意就是要求一棵树上的最长不下降序列,同时不下降序列的最小值与最大值不超过D. 做法是树分治+线段树,假设树根是x,y是其当前需要处理的子树,对于子树y,需要处理出两个数组MN,MX,MN[i]表示以 ...

  3. 【loj6145】「2017 山东三轮集训 Day7」Easy 动态点分治+线段树

    题目描述 给你一棵 $n$ 个点的树,边有边权.$m$ 次询问,每次给出 $l$ .$r$ .$x$ ,求 $\text{Min}_{i=l}^r\text{dis}(i,x)$ . $n,m\le ...

  4. 2019ICPC上海网络赛A 边分治+线段树

    题目: 给定一棵树, 带边权. 现在有2种操作: 1.修改第i条边的权值. 2.询问u到其他一个任意点的最大距离是多少. 解法:边分治+线段树 首先我们将所有的点修改和边修改都存在对应的边里面. 然后 ...

  5. 牛客多校第三场 G Removing Stones(分治+线段树)

    牛客多校第三场 G Removing Stones(分治+线段树) 题意: 给你n个数,问你有多少个长度不小于2的连续子序列,使得其中最大元素不大于所有元素和的一半 题解: 分治+线段树 线段树维护最 ...

  6. 【BZOJ4372】烁烁的游戏 动态树分治+线段树

    [BZOJ4372]烁烁的游戏 Description 背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠.题意:给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠.烁烁他每次会跳到一个节点u,把周围与他距 ...

  7. 【bzoj4372】烁烁的游戏 动态点分治+线段树

    题目描述 给一颗n个节点的树,边权均为1,初始点权均为0,m次操作:Q x:询问x的点权.M x d w:将树上与节点x距离不超过d的节点的点权均加上w. 输入 第一行两个正整数:n,m接下来的n-1 ...

  8. 【bzoj3730】震波 动态点分治+线段树

    题目描述 在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i].不幸的是,这片土地常常发生地震,并且随着时代的发展,城市 ...

  9. 洛谷T44252 线索_分治线段树_思维题

    分治线段树,其实就是将标记永久化,到最后再统一下传所有标记. 至于先后顺序,可以给每个节点开一个时间戳. 一般地,分治线段树用于离线,只查询一次答案的题目. 本题中,标记要被下传 222 次. Cod ...

随机推荐

  1. eclipse 创建maven 项目 动态web工程完整示例

    需求表均同springmvc案例 此处只是使用maven 注意,以下所有需要建立在你的eclipse等已经集成配置好了maven了,说白了就是新建项目的时候已经可以找到maven了 没有的话需要安装m ...

  2. 痞子衡嵌入式:飞思卡尔Kinetis系列MCU启动那些事(3)- KBOOT配置(FOPT/BOOT Pin/BCA)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔Kinetis系列MCU的KBOOT配置. KBOOT是支持配置功能的,配置功能可分为两方面:一.芯片系统的启动配置:二.KBO ...

  3. 痞子衡嵌入式:第一本Git命令教程(6)- 日志(log/reflog/gitk)

    今天是Git系列课程第六课,上一课我们学会了Git本地提交,今天痞子衡要讲的是如何查看Git本地历史提交. 当我们在仓库里做了很多次提交之后,免不了需要回看提交记录,看看自己之前的改动.有三种Git命 ...

  4. Java开发笔记(六十五)集合:HashSet和TreeSet

    对于相同类型的一组数据,虽然Java已经提供了数组加以表达,但是数组的结构实在太简单了,第一它无法直接添加新元素,第二它只能按照线性排列,故而数组用于基本的操作倒还凑合,若要用于复杂的处理就无法胜任了 ...

  5. SpringBoot 整合 apollo

    简介 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管理场景 ...

  6. FrameSet定义页面宽度并且居中

    frameset定义一个1000px的页面并且居中 <frame src = "about:blank"></frame> : 这个语句的功能是在一个框架里 ...

  7. Javascript继承3:将优点为我所有----组合式继承

    //声明父类 function ParentClass(name){ //值类型公有属性 this.name = name //引用类型公有属性 this.books = ['Html'] } //父 ...

  8. 自定义控制台程序导出角色对实体的权限为Excel文件

    本人微信公众号:微软动态CRM专家罗勇 ,回复282或者20181116可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . 先上 ...

  9. 前端js面向对象编程以及封装组件的思想

    demo-richbase 用来演示怎么使用richbase来制作组件的例子 作为一名前端工程师,写组件的能力至关重要.虽然javascript经常被人嘲笑是个小玩具,但是在一代代大牛的前仆后继的努力 ...

  10. ASP.NET中弹出消息框的几种常见方法

    在ASP.NET网站开发中,经常需要使用到alert消息框,尤其是在提交网页的时候,往往需要在服务器端对数据进行检验,并给出提示或警告. 这里,仅介绍几种不同的实现方法. 1.众所周知的方法是采用如下 ...