传送门


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

有一个很暴力的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. Java开发知识之Java的继承多态跟接口*

    Java开发知识之Java的继承多态跟接口 一丶继承 1.继承的写法 在Java中继承的 关键字是 extends 代表一个类继承另一个类. 继承的含义以及作用: 继承就是基于某个父类的扩展.制定出来 ...

  2. [七]基础数据类型之Float详解

        Float 基本数据类型float  的包装类 Float 类型的对象包含一个 float 类型的字段    属性简介 用来以二进制补码形式表示 float 值的比特位数 public sta ...

  3. 图示Java类的初始化顺序

    Java类的初始化顺序   在开发中,知道Java类的初始化顺序才能让我们更加清楚地掌握程序的执行流程.先把结论贴出来,Java里,从图里的1~6,分别按顺序执行.   以下为代码验证阶段,一共三个类 ...

  4. SmoOne——开源免费的企业移动OA应用,基于.Net

    一.SmoOne是什么一个开源的移动OA应用 二.语言C# 三.开发环境Visual Studio 四.开发平台Smobiler Designer 五.功能该应用开源代码中包含注册.登录.用户信息等基 ...

  5. InnoSetup 使用

    目录 简介 示例脚本 相关参考 在进行 WPF 程序打包发布的时候如果对程序打包没有特别高的要求,InnoSetup 足以胜任普通的程序打包发布需求,它支持安装包加密,安装包升级安装,注册表操作等常规 ...

  6. 微软正式开源Blazor ,将.NET带回到浏览器

    微软 ASP.NET 团队近日正式开源了  Blazor ,这是一个 Web UI 框架,可通过 WebAssembly 在任意浏览器中运行 .Net . Blazor 旨在简化快速的单页面 .Net ...

  7. Win10系统给文件夹添加备注

    在Win10系统中,相信大多用户都没有看到过文件或者是文件夹上有备注信息.下面给大家分享下在Win10系统中给文件夹或文件添加备注的方法.在添加备注之前,首先我们要在需要显示备注的文件夹中显示&quo ...

  8. Vue-指令

    1. v-text:这个指令用于将vue实例中的data内的属性渲染到标签内.有两种写法: 1. `<div v-text="数据"></div>`:该写法 ...

  9. 基于GIS的视频管理指挥平台

    平台利用空间地理信息技术,以GIS地图为基础,将各类信息空间化.可视化,实现基于空间电子地图的可视化查询和分析,它能使情报.推理.分析与其他可用数据融为一体,提供依托于电子地图的清晰而精确的现场态势图 ...

  10. Python编写脚本(输出三星形状的‘*’符号)

    环境:python3.* 心得:个人认为脚本非我强项,以下效果可以有更简单解决方案,纯属练习逻辑. 方案一: s=1 while s<=10: #这是决定多少列,起始为1,大循环一圈即加一,就是 ...