ABC218H Red and Blue Lamps
题意
对(N)个位置染色,如果(A_i)和(A_{i+1})不同色,则获得(a_i) ,要求涂(r)个红色,(n-r)个蓝色
[N leq 2e5
]
分析
显然会选择贪心地涂间隔(r)个颜色(r leq n /2)
问题转化为从(n)个物品中选取(r)个不相邻物品使得总价值最大
直接朴素的令(dp[i][j])表示前(i)个物品选择(j)个的做法复杂度很难下降
WQS二分可以把这样的问题转化为任意选择物品个数下的最大价值
令(g(x))表示选择(x)个物品的最大价值,显然这是一个凸函数 ,凸函数的性质就是切线斜率具有单调性。
1.二分斜率
对这个凸包的斜率进行二分,如果能通过二分得到点(x),就可以比较(x)和已知的限制(r)来偏移二分的区间
2.计算截距
对于一个固定的斜率,想要寻找和它切的点,只需要找到和(y)轴截距最大的点(画个图可以明白)
亦即(f(x)= g(x) - kx) 只需要求出(f_{max})即可,考察这个函数的意义:就是每选择一个物品,总价值减(k) 下的最大价值,这个东西就是个简单DP
这样就能记录取到(f_{max})时的(x)
3. 把二分得到的值再把斜率加回去
然后就做完了,注意实数
代码
#include<bits/stdc++.h>
#define pii pair<long long,long long>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> VI;
inline ll rd(){
ll x;
scanf("%lld",&x);
return x;
}
/*
inline int rd(){
int 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 MOD = 23333333;
inline int mul(int a,int b){
int res = (ll)a * b % MOD;
if(res < 0) res += MOD;
return res;
}
inline void add(int &a,int b){
a += b;
if(a >= MOD) a -= MOD;
}
inline void sub(int &a,int b){
a -= b;
if(a < 0) a += MOD;
}
ll gcd(ll a,ll b){
return !b ? a : gcd(b,a % b);
}
const int maxn = 2e5 + 5;
int n,r;
int a[maxn];
pair<long double,int> dp[maxn][2];
pair<long double,int> check(long double v){
dp[n + 1][0] = dp[n + 1][1] = make_pair(0,0);
for(int i = n;i >= 1;i--){
for(int j = 0;j < 2;j++){
auto r0 = dp[i + 1][0],r1 = dp[i + 1][1];
if(!j) r1.fi += a[i];
else r0.fi += a[i];
dp[i][j] = max(r0,r1);
if(!j)
dp[i][j].se++,dp[i][j].fi -= v;
}
}
return max(dp[1][0],dp[1][1]);
}
int main(){
n = rd();
r = rd();
for(int i = 1;i <= n - 1;i++)
a[i] = rd();
long double L = -1e15,R = 1e15;
for(int i = 0;i < 100;i++){
long double mid = (L + R) / 2.0;
auto it = check(mid);
if(it.se < r) R = mid;
else L = mid;
}
auto it = check(L);
ll ans = (ll)(it.fi + r * L);
printf("%lld",ans);
}