SP685 SEQPAR - Partition the sequence Solution
目录更好的阅读体验戳此进入
(建议您从上方链接进入我的个人网站查看此 Blog,在 Luogu 中图片会被墙掉,部分 Markdown 也会失效)
题面
给定 $ n $ 个数的数列 $ a_n $,给定 $ k $,将数列分成 $ k $ 段,满足每一段的元素和不大于 $ m $,求最小的 $ m \(。\) T $ 组数据。
$ 1 \le k \le n \le 1.5 \times 10^4, -3 \times 10^4 \le a_i \le 3 \times 10^4, 1 \le T \le 10 $。
Solution
首先二分答案很显然,二分 $ m $,并验证 $ m $ 是否对于 $ k $ 成立。
然后对于验证 $ m $ 是否成立,我们显然可以想到,对于这样的一个数列和 $ m $,有解的情况下,一定存在一个最小的和最大的块数的划分方案,并且解是存在连续性的,对于最小 $ fmn $ 和最大 $ fmx $ 一定有 $ \forall k \in \left[ fmn, fmx \right] $ 都成立,这个的严谨证明我不会很显然,就不证明了。
The proof is left to the reader.
然后对于求 $ fmn(i) $ 和 $ fmx(i) $,令其为考虑前 $ i $ 个数的合法方案的最小和最大的 $ k $,求个前缀和 $ sum(i) $,显然有如下的 DP 方程:
\[\begin{aligned} fmn(i) = \min(fmn(j) + 1) \quad j \lt i \land sum(j) \le sum(i) - m \\ fmx(i) = \max(fmx(j) + 1) \quad j \lt i \land sum(j) \le sum(i) - m \end{aligned} \]对于二分的范围,不难想到我们令数列中负数的和为 $ \xi $,正数的和为 $ \epsilon $,那么范围即为 $ \left[ \xi, \epsilon \right] $,我们令这个区间长度为 $ v $。
发现这个东西的复杂度是 $ O(n^2) $ 的,最终复杂度 $ O(T n^2 \log v) $,显然会寄,于是考虑优化 DP。
显然我们发现这个转移时可以优化的,可以考虑写个平衡树然后区间取最大最小值等,但是我不会不好写,于是我们考虑线段树。
普通线段树显然开不下,所以我们要写一个动态开点的权值线段树,其中的叶子节点下标表示 $ sum(i) $ 的值,存的是 $ fmn(i) $ 和 $ fmx(i) $ 的值,然后支持单点修改和区间求 $ \min $ 和 $ \max $。考虑在每次验证的时候新开一个线段树,然后每次查询 $ \left[ sum(i) - m, \inf \right] $ 区间的 $ \min $ 和 $ \max $ 然后 $ +1 $ 作为新的 $ fmn(i) $ 和 $ fmx(i) $,然后再把这两个值插到线段树中对应的 $ sum(i) $ 中,具体实现可以看一下代码,这道题还是挺巧妙的。
对于权值线段树的范围,显然与二分限制相同。
那么最终复杂度为 $ O(T n \log n \log v) $。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define INF 0x3f3f3f3f
template<typename T = int>
inline T read(void);
struct Node{
Node *ls, *rs;
int fmn, fmx;
OPNEW;
}nd[30000000];
void* Node::operator new(size_t){static Node* P = nd; return P++;}
int limMin, limMax;
int N, K;
int a[18000];
int sum[18000];
int fmn[18000], fmx[18000];
Node* rt;
class SegTree{
private:
#define MID ((gl + gr) >> 1)
#define SIZ (gr - gl + 1)
public:
void Pushup(Node* p){
if(!p)return;
p->fmn = min(p->ls ? p->ls->fmn : INF, p->rs ? p->rs->fmn : INF),
p->fmx = max(p->ls ? p->ls->fmx : -INF, p->rs ? p->rs->fmx : -INF);
}
void Modify(int idx, pair < int, int > val, Node* &p = rt, int gl = limMin, int gr = limMax){
if(!p)p = new Node{npt, npt, INF, 0};
if(gl == gr)return tie(p->fmn, p->fmx) = val, void();
if(idx <= MID) Modify(idx, val, p->ls, gl, MID);
else Modify(idx, val, p->rs, MID + 1, gr);
Pushup(p);
}
pair < int, int > Query(int l, int r, Node* p = rt, int gl = limMin, int gr = limMax){
// printf("Querying l = %d, r = %d, gl = %d, gr = %d, val = %d,%d\n", l, r, gl, gr, p ? p->fmn : -114514, p ? p->fmx : 114514);
if(!p)return {INF, -INF};
if(l <= gl && gr <= r)return {p->fmn, p->fmx};
auto L = MID >= l ? Query(l, r, p->ls, gl, MID) : make_pair(INF, -INF);
auto R = MID + 1 <= r ? Query(l, r, p->rs, MID + 1, gr) : make_pair(INF, -INF);
return {min(L.first, R.first), max(L.second, R.second)};
}
}st;
bool Check(int M){
rt = new Node{npt, npt, INF, -INF};
fmx[0] = fmn[0] = 0;
st.Modify(sum[0], {fmn[0], fmx[0]});
for(int i = 1; i <= N; ++i){
tie(fmn[i], fmx[i]) = st.Query(sum[i] - M, INF);
++fmn[i], ++fmx[i];
st.Modify(sum[i], {fmn[i], fmx[i]});
}
if(fmn[N] <= K && K <= fmx[N])return true;
return false;
}
int main(){
int T = read();
while(T--){
limMin = limMax = 0;
N = read(), K = read();
for(int i = 1; i <= N; ++i)
a[i] = read(),
a[i] >= 0 ? limMax += a[i] : limMin += a[i],
sum[i] = sum[i - 1] + a[i];
int l = limMin, r = limMax, ans(-1);
while(l <= r){
int mid = (l + r) >> 1;
if(Check(mid))ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n", ans);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
UPD
update-2022_10_13 初稿
标签:SEQPAR,return,fmx,int,题解,sum,Partition,fmn,INF From: https://www.cnblogs.com/tsawke/p/16820315.html