2019 UCF Practice F Sub Matrix Sum —— 利用决策性优化问题

这题...整理了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;
}
发布了157 篇原创文章 · 获赞 146 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43857314/article/details/105475362