这题...整理了4 hours 有所收获 来分享一下
题目大意:
求 一个最小的子矩阵,子矩阵的和 要大于等于S
题目思路:
首先矩阵问题——压缩成一维函数问题。
所以 题目要求变为:求一段最短的区间[L,R]使得和大于等于S
真的被这题晃了一手,没有看到数据范围直接跑尺取。
后来发现,有负数不可尺取(无单调性) —— 有负数的情况下,前缀和不是单调函数
之后考虑以下问题,没了负数之后 只可进行二分:
(这里扩充一个还没试的想法:二分滑动窗口的长度,判断是否大于等于S,好像不满足单调性有待证明测试)
当然由于前缀和不是单调函数,二分也会受到限制。
下面分享一下这个题目的优化过程 —— 希望有所收获
Part 1:
首先考虑,sum[r]-sum[l] >= S
那么 sum[l]<=sum[r]-S
这说明 前缀和 在 {0,S-sum[r]} 范围内 会对答案产生贡献,由于我们需要一个对于当前节点来说,最近的一个点,所以考虑用线段树(树状数组)降维:以权值为下标,位置为贡献建立线段树。每次对于新进节点i,查找{0,S-sum[i]}最大下标即可。
具体实现需要离散化 ,附一波代码:
复杂度:On*lgn*C —— 这个题把常数卡了,线段树是无法过的,只是分享思路历程
}//行列压缩
for(int j=1;j<=n;j++){
sum+=r[j][k]-r[j][i-1];
dp[j]=sum;
g.push_back(sum);
}
sort(g.begin(),g.end());
g.erase(unique(g.begin(),g.end()),g.end());
nn=g.size();
build(1,1,nn);
update(1,getid(0),0);
for(int j=1;j<=n;j++){
int id=upper_bound(g.begin(),g.end(),dp[j]-p)-g.begin()+1;
ll temp=query(1,1,id-1);
// debug(temp);
if(temp!=-1)
minl=min((j-temp)*(k-i+1)*1ll,minl);
update(1,getid(dp[j]),j);
}
}
Part 2:
考虑优化线段树的同时,会发现一些无用的东西——只需要得到最后一次满足要求的位置即可。
所以过程中出现了很多重复
接下来就是 标题了
利用决策性,去优化这个题目
首先考虑 s(r)-s(l) 要大于等于S,所以考虑 如果存在 i >j ,并且s(i) < s(j) ,那么根据区间和的公式,ans = s(r) - s(l) ,那么说明s(j)不可能在s(i)起到任何贡献,换句话说起到的贡献也是无用贡献,所以可以直接扔掉。
所以利用决策性的话,很容易看出这是个单调队列,所以答案即为维护一个单调队列即可。
单调队列内二分 ,一定会保证单调性。
Code:
/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pp;
const ll INF=1e17;
const int maxn=1e6+6;
const int mod=20020421;
const double eps=1e-3;
inline bool read(ll &num)
{char in;bool IsN=false;
in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
ll **a,**r;
ll b[maxn];
int q[maxn];
int main()
{
read(n);read(m);read(p);
ll minl=INF;
if(n > m){
swap(n, m);
a = new ll*[n + 1];
for(int i=0;i<=n;i++) a[i]=new ll[m + 1];
for(int j=1;j<=m;j++){
for(int i = 1;i<= n;i++){
scanf("%lld", &a[i][j]);
}
}
}else{
a = new ll*[n + 1];
for(int i=0;i<=n;i++) a[i]=new ll[m + 1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%lld",&a[i][j]);
}
}
}
r=new ll*[n+12];
for(int i=0;i<=n;i++) r[i]=new ll[m+12];
for(int i=1;i<=n;i++){
for(int k=1;k<=m;k++)
r[i][k]=r[i-1][k]+a[i][k];
}
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
ll k=i+len-1;
b[0]=0;
int s=0;
for(int j=1;j<=m;j++) b[j]=b[j-1]+r[k][j]-r[i-1][j];
// for(int j=1;j<=m;j++) printf("%lld ",b[j]);
for(int j=0;j<=m;j++){
int l=1,r=s;
int ans=-1;
if(s){
while(l<=r){
int mid=(l+r)/2;
if(b[j]-b[q[mid]]>=p){
l=mid+1;
ans=mid;
}
else r=mid-1;
}
}
if(ans!=-1) minl=min(minl,1ll*len*(j-q[ans]));
while(s&&b[j]<=b[q[s]]) s--;
q[++s]=j;
}
}
}
printf("%lld\n",minl==INF?-1:minl);
return 0;
}
Part 3:
考虑单调队列内既然可以二分,那么可二分必然可以尺取——具有单调性
所以可以 按照 在单调队列里嵌套一个尺取 更新答案
假设当前尺取的最后指针在pos位置,说明对于当前位置i,pos是满足要求的最小
接下来单调队列更新
但是此时单调队列可能会更新到比pos小的位置,所以pos位置也需要更新一下:
这样就不需要二分了
Code:
/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pp;
const ll INF=1e17;
const int maxn=1e6+6;
const int mod=20020421;
const double eps=1e-3;
inline bool read(ll &num)
{char in;bool IsN=false;
in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
ll **a,**r;
ll b[maxn];
int q[maxn];
int main()
{
read(n);read(m);read(p);
ll minl=INF;
if(n > m){
swap(n, m);
a = new ll*[n + 1];
for(int i=0;i<=n;i++) a[i]=new ll[m + 1];
for(int j=1;j<=m;j++){
for(int i = 1;i<= n;i++){
scanf("%lld", &a[i][j]);
}
}
}else{
a = new ll*[n + 1];
for(int i=0;i<=n;i++) a[i]=new ll[m + 1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%lld",&a[i][j]);
}
}
}
r=new ll*[n+12];
for(int i=0;i<=n;i++) r[i]=new ll[m+12];
for(int i=1;i<=n;i++){
for(int k=1;k<=m;k++)
r[i][k]=r[i-1][k]+a[i][k];
}
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
ll k=i+len-1;
b[0]=0;
int s=0;
for(int j=1;j<=m;j++) b[j]=b[j-1]+r[k][j]-r[i-1][j];
int x=0;
for(int j=0;j<=m;j++){
if(j){
while(x+1<=s&&b[j]-b[q[x+1]]>=p) x++;
if(b[j]-b[q[x]]>=p) minl=min(minl,(j-q[x])*len*1ll);
}
while(s&&b[j]<=b[q[s]]) s--;
x=min(s,x);
q[++s]=j;
}
}
}
printf("%lld\n",minl==INF?-1:minl);
return 0;
}