• All-one Matrices (01矩阵)

第八场一道题,求01矩阵中不被其他矩阵完全包含的矩阵个数。
求出一个点的左右边界后,可能的重复情况就只有上下包含关系。
那么逆序枚举每行,记录对于每一对(l,r)(矩阵的左右边界),下一个矩阵左右边界与其重合时,矩阵底边可能存在的位置。
第二场有个类似的题,求次大矩阵,其实就是求出来不重复的最大矩阵和次大矩阵,
那么可重复的次大矩阵就是次大矩阵或最大矩阵长/宽减一。
#include<cstdio>
#include<string>
#include<sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e6+10;
#define INF  0x3f3f3f3f
#define endll printf("\n")
#define s1(a) scanf("%d",&a)
#define s2(a,b) scanf("%d %d",&a,&b)
#define mem(a,b) memset(a,b,sizeof(a))
int h[3333][3333];
int vis[3333][3333];
int l[3333],r[3333];
//vis负责记录
//逆序枚举i时
//下次底边在(l,r)范围内的矩形可能出现的范围
//00111
//01111
//01101
//00101
//i=3,j=2时,vis[2][3]=2
//下次矩形底边所在行必须小于2才有可能成为新矩形
int main()
{
    int n,m;s2(n,m);mem(h,0);mem(vis,INF);
    for(int i=1;i<=n;i++)
    {
        char s[3333];scanf("%s",s+1);
        for(int j=1;j<=m;j++)
        {
            if(s[j]=='0') h[i][j]=0;
            else h[i][j]=h[i-1][j]+1;
        }
    }
    int ans=0;
    for(int i=n;i>=1;i--)
    {
        for(int j=1;j<=m;j++)
        {
            l[j]=j;
            while(l[j]>1&&h[i][l[j]-1]>=h[i][j])
                l[j]=l[l[j]-1];
        }
        for(int j=m;j>=1;j--)
        {
            r[j]=j;
            while(r[j]<m&&h[i][r[j]+1]>=h[i][j])
                r[j]=r[r[j]+1];
        }
        for(int j=1;j<=m;j++)
        {
            int ll=l[j],rr=r[j];
            if(h[i][j]&&i<vis[ll][rr])
            {
                ans++;
                vis[ll][rr]=i-h[i][j]+1;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}
  • CDMA (构造题)

第一次接触构造题呀,,当时比赛的时候一直在找规律但是构造不出。。
如何构造:每一个大矩阵都是由小矩阵
A A
A -A
构造出的。
#include<cstdio>
#include<string>
#include<sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=1e6+10;
#define endll printf("\n")
#define s1(a) scanf("%d",&a)
int n,a[1111][1111];
int main()
{
    a[1][1]=a[1][2]=a[2][1]=1;a[2][2]=-1;
    s1(n);
    for(int k=4;k<=n;k*=2)
    {
        int kk=k>>1;
        //右上角
        for(int i=1;i<=kk;i++)
            for(int j=kk+1;j<=kk*2;j++)
                a[i][j]=a[i][j-kk];
        //左下角
        for(int i=kk+1;i<=kk*2;i++)
            for(int j=1;j<=kk;j++)
                a[i][j]=a[i-kk][j];
        //右下角
        for(int i=kk+1;i<=kk*2;i++)
            for(int j=kk+1;j<=kk*2;j++)
                a[i][j]=-a[i-kk][j-kk];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            printf("%d ",a[i][j]);
        endll;
    }
    return 0;
}
  • Move(假二分,枚举暴力)

比赛的时候和队友一起二分双双WA掉了。。。结果这题压根不满足二分性,,,
比如:
15 5
39 39 39 39 39 60 60 60 60 60 100 100 100 100 100
199 为一个合法的答案,但 200 不是,201 也不是。
就直接从下界暴力枚举。
大概学了一下multiset,内部元素自动有序而且可重复,感觉是个很实用的STL了
#include<set>
#include<cstdio>
#include<vector>
#include<string>
#include<sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=1e5+10;
#define endll printf("\n")
#define s1(a) scanf("%d",&a)
#define s2(a,b) scanf("%d %d",&a,&b)
int n,k;
int a[1111];
multiset<int>ms;
multiset<int>::iterator it;
int check(int x)//枚举容积
{
    ms.clear();
    for(int i=0;i<n;i++)
        ms.insert(a[i]);
    for(int i=1;i<=k;i++)//枚举每个盒子
    {
        int r=x;
        while(!ms.empty())
        {
            it=ms.upper_bound(r);
            if(it!=ms.begin())
            {
                it--;
                r-=*it;
                ms.erase(it);
            }
            else break;
        }
    }
    if(ms.size()==0) return 1;
    return 0;
}
int main()
{
    int t;s1(t);int tt=1;
    while(t--)
    {
        s2(n,k);
        int sum=0,l=0;
        for(int i=0;i<n;i++)
        {
            s1(a[i]);
            sum+=a[i];l=max(l,a[i]);
        }
        sum/=k;
        l=max(l,sum);
        while(!check(l)) l++;
        printf("Case #%d: %d\n",tt++,l);
    }
    return 0;
}
  • Upgrading Technology (思维,后缀和优化)

牛客链接:https://ac.nowcoder.com/acm/contest/886/J
第六场J题
题意:你有n个技能每个技能有m级,从j-1 级升级到 j 级需要支付 c元(可能小于零,此时获得收益),当所有技能都升级到 j 级时,获得额外奖励 d 元 (可能小于零,此时表示损失)询问最大的利润,利润可以0(即升级0个技能)。
思路:
对每个收益求前缀和,
那么最大收益是在全部技能升到某级别后的收益,和部分(非全部)技能继续升级获得收益,两部分相加得到的
暴力也能过,但是有点险,可以维护一个后缀最大值优化。
还有一点,数据范围在ll的时候INF记得开大点。
#include<cstdio>
#include<string>
#include<sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e6+10;
#define INF  1e18
#define s1(a) scanf("%d",&a)
#define s2(a,b) scanf("%d %d",&a,&b)
#define mem(a,b) memset(a,b,sizeof(a))

int n,m;//种类数和等级数
ll s[1111];//全部技能升到j等级的净收益
ll d[1111];//全部技能升到j等级的总收益
ll c[1111][1111];//i技能升到j等级获得的收益
ll x[1111][1111];//i技能升级到j等级及以后能获得的最大收益
int main()
{
    int t;s1(t);int tt=1;
    while(t--)
    {
        s2(n,m);
        mem(c,0);mem(s,0);mem(d,0);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                ll z;scanf("%lld",&z);
                c[i][j]=c[i][j-1]-z;
                x[i][j]=c[i][j];
            }
            for(int j=m-1;j>=0;j--)//记得0的情况,因为枚举等级基础时可能为0
                x[i][j]=max(c[i][j],x[i][j+1]);
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%lld",&d[i]);
            d[i]+=d[i-1];s[i]=d[i];
            for(int j=1;j<=n;j++)
                s[i]+=c[j][i];
        }
        ll ans=0;
        for(int k=0;k<=m;k++)//枚举以加到哪一等级为基础
        {
            ll minsh=INF,tmp=0,cnt=0;
            for(int i=1;i<=n;i++)
            {
                ll maxsh=x[i][k]-c[i][k];
                if(maxsh>0)
                {
                    cnt++;
                    minsh=min(minsh,maxsh);
                    tmp+=maxsh;
                }
            }
            if(cnt==n) tmp-=minsh;
            ans=max(ans,tmp+s[k]);
        }
        printf("Case #%d: %lld\n",tt++,ans);
    }
    return 0;
}
  • Subsequence(单调队列,思维)

VJ链接:https://vjudge.net/problem/HDU-3530
补题补到第三场有个题,是这题的二维版本,就先拿这个题练一下。
题意:求数列中满足最大值减去最小值的范围在m,k之间的最长子串

思路:利用两个单调队列,一个维护当前最大值,一个维护当前最小值。
如更新到某值时候,最大值减去最小值大于k,说明该值更新了最大值或者最小值,
为了让该值结尾的字串依然满足题意,需要更新另外一个最值队列,
用last记录最后满足题意的左端点位置。
然后判断此序列是否满足最值之差大于等于m。
因为如果最大值减去最小值仍然小于m,此区间不可能存在字串满足题意。
如m=2,k=2,a[]=234356。
#include<cstdio>
#include<string>
#include<sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=1e5+10;
#define s1(a) scanf("%d",&a)
#define s2(a,b) scanf("%d %d",&a,&b)
#define mem(a,b) memset(a,b,sizeof(a))
#define s3(a,b,c) scanf("%d %d %d",&a,&b,&c)
int n,m,k;
int a[MAXN];
//单调队列
int mx[MAXN];//单调递减
int mi[MAXN];//单调递增
int main()
{
    while(~s3(n,m,k))
    {
        int l1=0,l2=0,r1=0,r2=0,last=1,ans=0;
        for(int i=1;i<=n;i++)
        {
            s1(a[i]);
            while(r1>l1&&a[mx[r1-1]]<a[i]) r1--;
            while(r2>l2&&a[mi[r2-1]]>a[i]) r2--;
            mx[r1++]=i;mi[r2++]=i;
            while(r1>l1&&r2>l2&&a[mx[l1]]-a[mi[l2]]>k)
            {
                if(mx[l1]<mi[l2])
                    last=mx[l1++]+1;
                else
                    last=mi[l2++]+1;
            }
            if(r1>l1&&r2>l2&&a[mx[l1]]-a[mi[l2]]>=m)
                ans=max(ans,i-last+1);
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • Planting Trees (单调队列)

链接:https://ac.nowcoder.com/acm/contest/883/F?&headNav=acm
题意:求矩阵中满足元素之差小于等于m的最大矩阵。
思路:没思路。。一开始想拿求01矩阵的思路写,但是发现很难维护,因为需要维护当前串的最大元素,最小元素。
进一步想到使用单调队列(我还以为单调队列就只能拿来滑窗玩呢ε=ε=ε=┏(゜ロ゜;)┛)
题目描述中提到了n^3,并且保证不会超时,应该想到设计一个O(n^3)的算法。
那么枚举矩阵的上边界,下边界,右边界,利用这些求出满足要求的最小左边界。
为了将二维矩阵压缩成一维,枚举上边界下边界时顺序枚举,并且只在枚举上边界时初始化最值数组。
这样数组中存的即为上界下界该列中的最值。
#include<bits/stdc++.h>
#define INF  0x3f3f3f3f
using namespace std;
//单调队列
//枚举上下边界和右边界
//二维压缩成一维
int n,m,a[505][505];
int mx[505],mn[505];
int q1[505],q2[505];
int l1,l2,r1,r2;
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
                scanf("%d",&a[i][j]);
        int ans=0;
        for(int l=1; l<=n; l++)
        {
            //初始化最大最小值
            for(int p=0;p<=n+1;p++)
                mx[p]=0,mn[p]=INF;
            for(int r=l; r<=n; r++)
            {
                l1=l2=r1=r2=0;
                for(int k=1,p=0; k<=n; k++) //枚举右边界
                {
                    //更新最大最小值
                    mx[k]=max(mx[k],a[r][k]);
                    mn[k]=min(mn[k],a[r][k]);
                    while(l1<r1&&mx[q1[r1-1]]<mx[k]) r1--;
                    while(l2<r2&&mn[q2[r2-1]]>mn[k]) r2--;
                    q1[r1++]=k,q2[r2++]=k;
                    while(l1<r1&&l2<r2&&mx[q1[l1]]-mn[q2[l2]]>m)//更新最小左边界
                    {
                        if(q1[l1]<q2[l2])
                            p=q1[l1++];
                        else
                            p=q2[l2++];
                    }
                    ans=max(ans,(r-l+1)*(k-p));
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • sequence (单调栈,线段树,前缀和)

牛客链接:https://ac.nowcoder.com/acm/contest/884/C?&headNav=acm
题意:

给你2个长度为n的区间 a区间和b区间

区间的值为b区间之和乘以a区间的最小值,要你求出值最大的区间

思路:么得思路,只能想到求出a数组每个数字最远贡献,想怎么求有贡献的这段区间里 b数组的最大和绞尽脑汁,,,
一直在想怎么让求出的最值区间还能包括ai,忘了分成两段求前缀和。。。
因为舍友借走了电脑这个题断断续续写了三天,也是今年牛客多校补题的最后一道题了。
一个教训就是线段树的端点要记得啊,左开右闭写顺了,,右区间忘记y>=mid+1,(′д` )…彡…彡
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=3e6+10;
#define pi  acos(-1.0)
#define INF  0x3f3f3f3f3f3f3f3f
#define mod   1000000009
#define lson rt<<1,l,mid
#define endll printf("\n")
#define pii pair<int, int>
#define rson rt*2+1,mid+1,r
#define s1(a) scanf("%d",&a)
int n;
ll a[MAXN],b[MAXN];
int l[MAXN],r[MAXN];//a数组中某点向左向右最远贡献
struct IN
{
    ll mi,ma;
}m[MAXN<<2];
//枚举每个点,利用a数组找到该数字影响范围(l,r)
//对前缀和利用线段树维护最大值与最小值
//当a[i]>0时对左小区间求最小值,右小区间求前缀和最大值,相减即为最大值
ll tmpmi,tmpmx;
void build(int rt,int l,int r)
{
    if(l==r)
    {
        m[rt].mi=m[rt].ma=b[l];
        return;
    }
    int mid=(l+r)>>1;
    build(lson);build(rson);
    m[rt].mi=min(m[rt<<1].mi,m[rt*2+1].mi);
    m[rt].ma=max(m[rt<<1].ma,m[rt*2+1].ma);
}
void init()
{
    for(int i=1;i<=n;i++)
    {
        l[i]=i;
        while(l[i]>1&&a[l[i]-1]>=a[i])
            l[i]=l[l[i]-1];
    }
    for(int i=n;i>=1;i--)
    {
        r[i]=i;
        while(r[i]<n&&a[r[i]+1]>=a[i])
            r[i]=r[r[i]+1];
    }
    build(1,1,n);
}
ll askmi(int x,int y,int rt,int l,int r)
{
    if(l>=x&&r<=y) return m[rt].mi;
    int mid=(l+r)>>1;
    ll ans=INF;
    if(x<=mid) ans=min(ans,askmi(x,y,lson));
    if(y>mid) ans=min(ans,askmi(x,y,rson));//又是血的教训。。错误示范:y>=mid,摔
    return ans;
}
ll askmx(int x,int y,int rt,int l,int r)
{
    if(l>=x&&r<=y) return m[rt].ma;
    int mid=(l+r)>>1;
    ll ans=-INF;
    if(x<=mid) ans=max(ans,askmx(x,y,lson));
    if(y>mid) ans=max(ans,askmx(x,y,rson));//
    return ans;
}
int main()
{
    s1(n);b[0]=0;
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++) scanf("%lld",&b[i]),b[i]+=b[i-1];
    if(n==1)//特判加速
    {
        printf("%lld\n",a[1]*b[1]);
        return 0;
    }
    init();
    ll ans=-INF;
    for(int i=1;i<=n;i++)
    {
        int ll=l[i],rr=r[i];
        if(a[i]==0) ans=ans>0?ans:0;//特判加速
        else if(a[i]>0)
        {
            tmpmi=askmi(max(ll-1,1),max(i-1,1),1,1,n);
            tmpmx=askmx(i,rr,1,1,n);
            ans=max(ans,a[i]*(tmpmx-tmpmi));
        }
        else
        {
            tmpmx=askmx(max(ll-1,1),max(i-1,1),1,1,n);
            tmpmi=askmi(i,rr,1,1,n);
            ans=max(ans,a[i]*(tmpmi-tmpmx));
        }
    }
    printf("%lld\n",ans);
}