P7482 不条理狂诗曲 分治 DP
P7482 不条理狂诗曲 分治 DP
题意
有一个长度为\(n\)的非负整数序列\(a\),定义\(f(l,r)\)表示从序列\(a\)的区间\([l,r]\)选择若干不相邻的数的和的最大值
现要求
\[\sum_{l=1}^n\sum_{r=l}^nf(l,r)
\]
取模\(1e9 + 7\)
\[1 \leq n \leq 10^5\\
0 \leq a_i \leq 10^9
\]
分析
好久没做过这么单纯的分治了。。
先想想一个区间怎么做,那么显然跑一遍DP就行了
然后可以利用分治的思想去算每个区间
对于区间\([l,r]\),如果子区间在\([l,mid]\)或者\([mid + 1,r]\),直接递归下去就行了
重点在于横跨中间点
考虑到不能相邻,用\(fl_i\)表示\([i,mid]\)且选\(mid\)的最大和,\(fr_i,gl_i,gr_i\)同理,这些显然都可以跑一遍\(DP\)求出
那么对于区间\([L,R]\),答案应该是\(max(fl_L + gr_R,gl_L + fr_R,gl_L + gr_R)\)
将下标移到一边可以推得\(fr_R - gr_R > max(fl_L - gl_L,0)\)
于是各自维护几个变量,枚举\(r\),就可以二分算出一段的贡献
于是就可以把朴素的\(n^2\)优化到\(nlogn\)
总复杂度\(O(nlog^2n)\)
代码
#include<bits/stdc++.h>
#define fi first
#define se second
#define pii pair<ll,int>
#define re register
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
ll rd(){
ll x = 0;
char ch = getchar();
while(ch < '0' || ch > '9') {
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x;
}
const int maxn = 1e5 + 5;
const ll MOD = 1e9 + 7;
pii v[maxn];
int a[maxn];
ll ans;
ll fl[maxn],fr[maxn],gl[maxn],gr[maxn];
ll sum1[maxn],sum2[maxn];
//fl: choose mid
//gl: choose mid - 1
//fr: choose mid + 1
//gr: choose mid + 2
//for interval: [L,R]
//ans: max(fl_L + gr_R,gl_L + fr_R,gl_L + gr_R) = max(max(fl_L,gl_L) + gr_R,gl_L + fr_R)
// gl + fr > max(fl,gl) + gr
// fr_R - gr_R > max(fl_L - gl_L,0)
// g:max(fl_L - gl_L,0)
// for every j: find the first pos (fr_R - gr_R) >= (g) k,
// l - k - 1:
// ans += sum1 + fr_R * (k - l)
// k - r :
// ans += (mid - k + 1) * gr_R + sum3[k - r]
void solve(int l,int r){
if(l == r) {
ans += a[l];
ans %= MOD;
return;
}
int mid = l + r >> 1;
solve(l,mid);
solve(mid + 1,r);
for(re int i = mid;i >= l;i--){
if(i == mid) fl[i] = a[i],fl[i + 1] = gl[i] = 0;
else
fl[i] = max(fl[i + 1],fl[i + 2] + a[i]);
if(i == mid - 1)
gl[i] = a[i],gl[i + 1] = 0;
else if(i != mid )
gl[i] = max(gl[i + 1],gl[i + 2] + a[i]);
v[i] = make_pair(max(0ll,fl[i] - gl[i]),i);
}
sort(v + l,v + mid + 1);
sum1[l - 1] = sum2[l - 1] = 0;
for(re int i = l;i <= mid;i++){
sum1[i] = sum1[i - 1] + max(fl[v[i].se],gl[v[i].se]);
sum1[i] %= MOD;
sum2[i] = sum2[i - 1] + gl[v[i].se];
sum2[i] %= MOD;
}
for(re int i = mid + 1;i <= r;i++){
if(i == mid + 1) fr[i] = a[i],fr[i - 1] = gr[i] = 0;
else
fr[i] = max(fr[i - 1],fr[i - 2] + a[i]);
if(i == mid + 2) gr[i] = a[i];
else if(i != mid + 1) gr[i] = max(gr[i - 1],gr[i - 2] + a[i]);
int k = lower_bound(v + l,v + mid + 1,make_pair(fr[i] - gr[i],0)) - v;
ans += sum1[mid] - sum1[k - 1] + (mid - k + 1) * gr[i] % MOD;
ans %= MOD;
ans += sum2[k - 1] + (k - l) * fr[i] % MOD;
ans %= MOD;
}
}
int main(){
int n = rd();
for(int i = 1;i <= n;i++)
a[i] = rd();
solve(1,n);
cout << ans;
}