「JOISC 2019 Day2」两道料理(差分+线段树)
设\(sa\)表示a的前缀和,sb表示b的前缀和。
设\(f[i][j]\)表示n个中的前i个、m个中的前j个完成了,最大的分数和。
如果把第一维去掉,\(f[i]\)是由\(f[i-1]\)加上一些修改得到。
那么就是先:
1.\(f[j]+=a[i](sa[i]+sb[j]<=s[i])\)
满足条件的j显然是一个前缀。
2.\(f[j]=max(f[j],f[i]-v[i]+v[j])(i<j)\)
其中\(v\)是满足\(sb[i]+sa[j]<=t[i]\)的\(q[i]\)的前缀和。
对于第二个转移,考虑设\(g[j]=f[j]-v[j]\),不难发现,第二个转移在\(g\)上就是一个前缀max。
那么变成在\(g\)上操作:
1.\(g[j]+=a[i](sa[i]+sb[j]<=s[i])\)
还是给\(g\)的前缀区间加上一个数。
2.由于\(a[i]\)的加入,会有一些新的\(q[j]\)从v中剔除,v是前缀和,这个对v相当于后缀减,由于这个操作不会改变\(f\),所以相当于g的后缀加。
3.对g取前缀max。
注意1、2的操作都是区间加。
考虑如何维护?先把1、2的区间加离散成若干个极长小区间,显然不管怎么操作,每一个小区间的相对大小不变,也就是依然满足非递减关系,只需要考虑小区间之间的前缀max。
所以,左往右这样更新过去:
比如有相邻的小区间\([lx,rx]\)和\(ly,ry\),若\([lx,rx]\)的最大值大于\([ly,ry]\)的开头,那么找到\([ly,ry]\)开头相同的连续一段数,对这个进行区间加,以此类推。
势能分析后复杂度是对的。
Code
#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i < _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;
const int N = 1e6 + 5;
int n, m;
ll a[N], lima[N], p[N], sa[N];
ll b[N], limb[N], q[N], sb[N], he[N];
void Init() {
scanf("%d %d", &n, &m);
fo(i, 1, n) scanf("%lld %lld %lld", &a[i], &lima[i], &p[i]), sa[i] = sa[i - 1] + a[i];
fo(i, 1, m) scanf("%lld %lld %lld", &b[i], &limb[i], &q[i]), sb[i] = sb[i - 1] + b[i];
}
#define i0 i + i
#define i1 i + i + 1
ll t[N * 4], lz[N * 4]; int g[N * 4];
ll pl, pr, px;
void jia(int i, ll x) {
t[i] += x, lz[i] += x;
}
void down(int i) {
if(lz[i]) {
jia(i0, lz[i]);
jia(i1, lz[i]);
lz[i] = 0;
}
}
void add(int i, int x, int y) {
if(y < pl || x > pr) return;
if(x >= pl && y <= pr) { jia(i, px); return;}
int m = x + y >> 1; down(i);
add(i0, x, m); add(i1, m + 1, y);
t[i] = max(t[i0], t[i1]);
g[i] = (g[i0] & g[i1] & (t[i0] == t[i1]));
}
ll getv(int i, int x, int y, int l) {
if(g[i]) return t[i];
int m = x + y >> 1; down(i);
return l <= m ? getv(i0, x, m, l) : getv(i1, m + 1, y, l);
}
int py, pz; ll pk;
void ft(int i, int x, int y) {
if(y < pl || x > pr || pz) return;
if(x >= pl && y <= pr && g[i]) {
if(t[i] != pk) { pz = 1; return;}
if(g[i]) { py = y; return;}
}
int m = x + y >> 1; down(i);
ft(i0, x, m);
ft(i1, m + 1, y);
}
int d[N]; ll h[N];
int cmpd(int x, int y) {
return h[x] < h[y];
}
int w[N * 8], w0;
void cha(int x, int y, ll z) {
if(x > y) return;
pl = x, pr = y, px = z;
add(1, 0, m);
w[++ w0] = y; if(x) w[++ w0] = x - 1;
}
void dg(ll v, int x, int y) {
while(x <= y) {
ll v2 = getv(1, 0, m, x);
if(v <= v2) return;
pl = x, pr = y; py = 0, pz = 0; pk = v2;
ft(1, 0, m);
pl = x, pr = py; px = v - v2;
add(1, 0, m);
x = py + 1;
}
}
void premax() {
sort(w + 1, w + w0 + 1);
w0 = unique(w + 1, w + w0 + 1) - (w + 1);
fo(i, 1, w0) dg(getv(1, 0, m, w[i]), w[i] + 1, m);
}
void work() {
fo(i, 1, m * 4) g[i] = 1;
fo(i, 1, m) {
h[i] = limb[i] - sb[i];
d[i] = i;
}
sort(d + 1, d + m + 1, cmpd);
int l = 1;
while(l <= m && h[d[l]] < 0) l ++;
fo(i, 1, n) {
w0 = 0;
int as = -1;
for(int l = 0, r = m; l <= r; ) {
int mi = l + r >> 1;
if(sb[mi] <= lima[i] - sa[i]) as = mi, l = mi + 1; else r = mi - 1;
}
cha(0, as, p[i]);
while(l <= m && h[d[l]] < sa[i]) {
cha(d[l], m, q[d[l]]);
l ++;
}
premax();
}
ll ans = getv(1, 0, m, m);
while(l <= m) ans += q[d[l ++]];
pp("%lld\n", ans);
}
int main() {
Init();
work();
}
转载注意标注出处:
转自Cold_Chair的博客+原博客地址