1304F2 - Animal Observation (hard version) 线段树or单调队列 +DP
题意
用摄像机观察动物,有两个摄像机,一个可以放在奇数天,一个可以放在偶数天。摄像机在同一天可以同时照到k个区域放下去可以持续两天。现在给出每一天每个区域的动物数量,问最多照到动物多少个。如果两个照相机同时照到一个动物只算一次。n<=50 k<=m<=2e4
思路
我们可以考虑只在一天的情况 那么就是一个简单的dp,状态为dp[i][j]第i天放置在区域j可以获得的最大数。那么dp[i][j]转移的时候只要取max(dp[i-1])再加上第i天放在j的收益即可。持续天数变成了两天有什么差异呢?那就是会产生覆盖的情况,如何解决这个情况呢,对于dp[i][j]来说 只有上一天的摄像机区间会和[j,j+k-1]相交的时候才要考虑这种情况,那么相交的时候,相当于上一层状态要减去和[j,j+k-1]的交集区间 例如对于dp[i-1][j+1]来说,就是要减去[j,j+k-1]和[j+1,j+k]的交集动物数也就是[j+1,j+k-1]的动物数,这里可以用滑动窗口来维护。对于长度为k的滑动窗口,当前窗口边界为j,如果j出去,表示所有[max(1,j-k+1),j]都应该加上a[i][j]那么新进来的j+k则表示所有[j+1,j+k]都应该减去a[i][j+k]因为这里采用的是区间加减的形式,所以可以用线段树进行维护,同时也可以用单调队列做法。
单调栈做法
对于dp[i-1][x]单调栈是把答案分成了三种情况讨论
1.x位于位于j-k+1的左边 即dp[i][j]由没有相交的情况转移过来直接求前缀最大值即可
2. x位于j+k-1的右边 同上 求后缀最大
3. 和[j,j+k-1]有相交的区间 这个时候就要用单调栈维护最大值了,我们可以知道的是我们维护一个k大的窗口,那么这个k大窗口里的东西都和[j,j+k-1]有相交,那么我们可以把每个入队的dp[i-1][j]减去sum[i][j..j+k-1]压入单调栈,那么对于我们求dp[i][j]而言,值qv[tail]+sum[i][q[tail]...j-1]则为减去了相交区间sum的相应dp值因为qv[tail]+sum[q[tail]..j-1]是单调递增的,即j移动的时候sum[q[tail]..j-1]要增加,就相当于对单调栈里面的每一个值都加上了了一个相同的数,单调栈里的元素的rank不会发生改变,当要状态转移的时候只需要还原值即可
这里有一个技巧对于[x1,y1] [x2,y2]如果交集是[y1,x2]即交叉的情况,如果我们要计算[x2,y2]减去[x2,y1]的值,那么我们对于区间[x1,y1]可以直接减去区间[1...y1]的值,到[x1,y1]的时候 直接加上区间[1,x2-1]则只剩下了[x2,y1]的值了,这样对于队列里的每个值都是加上区间[1,x2-1] 滑动窗口滑动的时候仅仅是x2变化即统一加的值变化了
线段树做法代码
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
const int mod=1e9+7;
int dp[55][maxn],a[55][maxn],sum[55][maxn];
int lazy[maxn<<2],tree[maxn<<2];
void build(int o,int l,int r){
lazy[o]=tree[o]=0;
if(l==r)return ;
int mid=l+r>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
}
void push_down(int o){
if(lazy[o]){
lazy[o<<1|1]+=lazy[o];
lazy[o<<1]+=lazy[o];
tree[o<<1|1]+=lazy[o];
tree[o<<1]+=lazy[o];
lazy[o]=0;
}
}
void push_up(int o){
tree[o]=max(tree[o<<1],tree[o<<1|1]);
}
void update(int o,int l,int r,int x,int y,int value){
if(l>=x&&r<=y){
tree[o]+=value;
lazy[o]+=value;
return ;
}
int mid=l+r>>1;
push_down(o);
if(mid>=x)update(o<<1,l,mid,x,y,value);
if(mid<y)update(o<<1|1,mid+1,r,x,y,value);
push_up(o);
}
int query(int o,int l,int r,int x,int y){
if(x<=l&&r<=y){
push_down(o);
return tree[o];
}
int mid=l+r>>1;
int ans=0;
if(mid>=x)ans=max(ans,query(o<<1,l,mid,x,y));
if(mid<y)ans=max(ans,query(o<<1|1,mid+1,r,x,y));
return ans;
}
int main(){
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
sum[i][j]=sum[i][j-1]+a[i][j];
}
}
int up=m-k+1;
for(int i=1;i<=up;i++){
dp[1][i]=sum[1][i+k-1]-sum[1][i-1]+sum[2][i+k-1]-sum[2][i-1];
}
for(int i=2;i<=n;i++){
build(1,1,up);
for(int j=1;j<=up;j++)update(1,1,up,j,j,dp[i-1][j]);
for(int j=1;j<=k;j++)update(1,1,up,1,j,-a[i][j]);
for(int j=1;j<=up;j++){
dp[i][j]=query(1,1,up,1,up)+sum[i][j+k-1]-sum[i][j-1]+sum[i+1][j+k-1]-sum[i+1][j-1];
if(j<up){
update(1,1,up,max(1,j-k+1),j,a[i][j]);
update(1,1,up,j+1,j+k,-a[i][j+k]);
}
}
}
printf("%d\n",*max_element(dp[n]+1,dp[n]+up+1));
return 0;
}
单调栈
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
const int mod=1e9+7;
int dp[55][maxn],a[55][maxn],sum[55][maxn],q[maxn],qv[maxn];
int n,m,k;
int query(int i,int l){
return sum[i][l+k-1]-sum[i][l-1];
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
sum[i][j]=sum[i][j-1]+a[i][j];
}
}
int up=m-k+1;
for(int i=1;i<=up;i++){
dp[1][i]=query(1,i)+query(2,i);
}
for(int i=2;i<=n;i++){
int maxnum=0;
for(int j=1;j<=up;j++){
if(j-k>=1)maxnum=max(maxnum,dp[i-1][j-k]);
dp[i][j]=max(dp[i][j],maxnum+query(i,j)+query(i+1,j));
}
maxnum=0;
for(int j=up;j>=1;j--){
if(j+k<=up)maxnum=max(maxnum,dp[i-1][j+k]);
dp[i][j]=max(dp[i][j],maxnum+query(i,j)+query(i+1,j));
}
int tail=0,head=1;
for(int j=1;j<=up;j++){
int v=dp[i-1][j]-sum[i][j+k-1];
while(head<=tail&&v>=qv[tail])tail--;
qv[++tail]=v;q[tail]=j;
dp[i][j]=max(dp[i][j],qv[head]+sum[i][j-1]+query(i,j)+query(i+1,j));
while(head<=tail&&q[head]<=j-k+1)head++;
}
tail=0,head=1;
for(int j=up;j>=1;j--){
int v=dp[i-1][j]+sum[i][j-1];
while(head<=tail&&v>=qv[tail])tail--;
qv[++tail]=v;q[tail]=j;
dp[i][j]=max(dp[i][j],qv[head]-sum[i][j+k-1]+query(i,j)+query(i+1,j));
while(head<=tail&&q[head]>=j+k-1)head++;
}
}
printf("%d\n",*max_element(dp[n]+1,dp[n]+1+up));
return 0;
}