A All-one Matrices

题意

求所有极大全一矩阵的个数。

分析

  • 预处理每个点向下延伸的高度值,同一行再用单调栈预处理每个高度作为最小值能延伸的左右边界。
  • 枚举每个1的点,先判断这个点的高度左右延伸覆盖的区间是否在前面已被覆盖过(即已统计过),如果否,再判断对应这段覆盖区间的上一行是否全1,如果是,那么就是上一行已统计过,否则就答案++。
  • 加上每一行用map判断是否重复覆盖,复杂度大概是\(O(nmlogm)\)

代码

//
// Created by keane on 2019/8/10.
//
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3050;
int a[N][N];
int n,m;
char s[N];
//每个1向下延伸
int dw[N][N];
int pre[N][N];
//每个dw作为最小值延伸
int le[N][N],ri[N][N];
stack<int> ss;
map<pair<int,int>,bool> vis;
int main(){
//    freopen("in.txt","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        for(int j=1;j<=m;j++){
            a[i][j]=s[j]-'0';
            pre[i][j]=pre[i][j-1]+a[i][j];
        }
    }
    for(int i=n;i>=1;i--){
        for(int j=1;j<=m;j++){
            if(a[i][j]==0){
                continue;
            }
            if(a[i+1][j]==1){
                dw[i][j]=dw[i+1][j]+1;
            }else{
                dw[i][j]=1;
            }
        }
        while(!ss.empty()){
            ss.pop();
        }
        for(int j=1;j<=m;j++){
            while(ss.size()>0 && dw[i][j]<=dw[i][ss.top()]){
                ss.pop();
            }
            if(ss.size()>0){
                le[i][j]=ss.top()+1;
            }else{
                le[i][j]=1;
            }
            ss.push(j);
        }
        while(!ss.empty()){
            ss.pop();
        }
        for(int j=m;j>=1;j--){
            while(ss.size()>0 && dw[i][j]<=dw[i][ss.top()]){
                ss.pop();
            }
            if(ss.size()>0){
                ri[i][j]=ss.top()-1;
            }else{
                ri[i][j]=m;
            }
            ss.push(j);
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        vis.clear();
        for(int j=1;j<=m;j++){
            if(a[i][j]==0){
                continue;
            }
            int l=le[i][j];
            int r=ri[i][j];
            if(vis[{l,r}]){
                continue;
            }
            vis[{l,r}]=true;
            if(pre[i-1][r]-pre[i-1][l-1]!=r-l+1){
                ans++;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

B Beauty Values

题意

给定一个序列,求所有区间的区间不同数个数之和。

分析

  • 显然是统计每个数贡献的问题,对于一个区间来说,每个数肯定是第一次出现才对答案有贡献,所以考虑将相同的数分开成多个区间,也就是记录每个数上一次出现的位置,然后对于当前这个数左右两边能选的数的个数相乘即可得到含有该数的区间个数。
  • 比如样例,\([1,2,1,3]\),那么每个数分别有贡献的区间为\({1:[1,12,121,1213],2:[12,121,1213,2,21,213],1:[21,213,1,13],3:[1213,213,13,3]}\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+50;
int n,a[N],p[N];
int main(void){
    // freopen("in.txt","r",stdin);
    scanf("%d",&n);
    ll ans=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        //每个数只在区间第一次出现才有贡献
        ans+=1ll*(i-p[a[i]])*(n-i+1);
        p[a[i]]=i;
    }
    printf("%lld\n",ans);
    return 0;
}

C CDMA

题意

给定一个\(n(n=2^k)\),构造一个只含1和-1的矩阵使得任意两行向量的点乘为0。

分析

  • n是2的次方,暗示需要类似分治的思想,从最小的矩阵(2*2)进行构造。
  • n=2时,矩阵是\((\begin{smallmatrix} 1&1\\1&-1 \end{smallmatrix})\),现在要构造4*4的矩阵,显然,如果我们将2*2的矩阵复制一遍到右边,得到的这两行显然是符合条件的。
  • 下半部分的构造,先把上半部分复制下来,然后右半部分取反,这样子对于上下部分相同的行,左右部分刚好一正一反,和为0,对于不同的行,如果是同在上部分或同在下部分,因为我们上半部分已经确定是符合要求的,而下半部分只是复制,所以也能满足要求,如果是不同部分的不同行,由于小矩阵(n/2 * n/2)已经是满足的,所以将两行调整到同一部分,其实分成左右两个小矩阵都是点乘为0的。
  • 其实就是xjb构造,循环,取反,翻转等等试一试,把8*8构造出来一般就对了。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e3+50;
int n,a[N][N];
int main(void){
//    freopen("in.txt","r",stdin);
    scanf("%d",&n);
    a[0][0]=1;
    a[0][1]=1;
    a[1][0]=1;
    a[1][1]=-1;
    for(int k=2;k<n;k<<=1){
        for(int i=0;i<k;i++){
            for(int j=0;j<k;j++){
//                a[i][k+j]=a[k-1-i][k-1-j];
                a[i][k+j]=a[i][j];
            }
        }
        for(int i=k;i<k+k;i++){
            for(int j=0;j<k;j++){
                a[i][j]=-1*a[i-k][j];
            }
            for(int j=k;j<k+k;j++){
                a[i][j]=a[i-k][j];
            }
        }
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            printf("%d%c",a[i][j],j==n-1?'\n':' ');
        }
    }
    return 0;
}

G Gemstones

题意

给一个字符串,每次可以选择三个连续相同字符消去,求最多消去次数。

分析

栈模拟。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
char s[N];
stack<char> st;
int main(void){
    scanf("%s",s);
    int n=strlen(s);
    int ans=0;
    for(int i=0;i<n;i++){
        st.push(s[i]);
        if(st.size()<3){
            continue;
        }
        int a=st.top();
        st.pop();
        int b=st.top();
        st.pop();
        int c=st.top();
        st.pop();
        if(a!=b || b!=c){
            st.push(c);
            st.push(b);
            st.push(a);
        }else{
            ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
}