2018 ICPC南京站网络赛 The writing on the wall(单调栈求全1子矩阵数量)

Feeling hungry, a cute hamster decides to order some take-away food (like fried chicken for only 3030 Yuan).

However, his owner CXY thinks that take-away food is unhealthy and expensive. So she demands her hamster to fulfill a mission before ordering the take-away food. Then she brings the hamster to a wall.

The wall is covered by square ceramic tiles, which can be regarded as a n * mn∗m grid. CXY wants her hamster to calculate the number of rectangles composed of these tiles.

For example, the following 3 * 33∗3 wall contains 3636 rectangles:

Such problem is quite easy for little hamster to solve, and he quickly manages to get the answer.

Seeing this, the evil girl CXY picks up a brush and paint some tiles into black, claiming that only those rectangles which don't contain any black tiles are valid and the poor hamster should only calculate the number of the valid rectangles. Now the hamster feels the problem is too difficult for him to solve, so he decides to turn to your help. Please help this little hamster solve the problem so that he can enjoy his favorite fried chicken.

Input

There are multiple test cases in the input data.

The first line contains a integer TT : number of test cases. T \le 5T≤5.

For each test case, the first line contains 33 integers n , m , kn,m,k , denoting that the wall is a n \times mn×m grid, and the number of the black tiles is kk.

For the next kk lines, each line contains 22 integers: x\ yx y ,denoting a black tile is on the xx-th row and yy-th column. It's guaranteed that all the positions of the black tiles are distinct.

For all the test cases,

1 \le n \le 10^5,1\le m \le 1001≤n≤105,1≤m≤100,

0 \le k \le 10^5 , 1 \le x \le n, 1 \le y \le m0≤k≤105,1≤x≤n,1≤y≤m.

It's guaranteed that at most 22 test cases satisfy that n \ge 20000n≥20000.

Output

For each test case, print "Case #xx: ansans" (without quotes) in a single line, where xx is the test case number and ansans is the answer for this test case.

Hint

The second test case looks as follows:

样例输入复制

2
3 3 0
3 3 1
2 2

样例输出复制

Case #1: 36
Case #2: 20

思路:把红色看成1,黑色看成0,。题意就是求求1子矩阵的数量。

首先 n^3 的算法思路:

矩阵:

1 1 1 1

0  1 1 1

0  0  1 1

0  0  0  1

考虑每个点对答案的贡献,先看(1,1)位置,值为1,此时它只能形成1个矩阵,对答案的贡献是1,接着看(1,2),它可以和单独贡献1个子矩阵,同时也可以和前面(1,1),合力贡献1,所以它的总贡献先定为2(之所以这样说,是因为现在我们只考虑行的影响,对于列的影响,一会会分析)。接着看(1,3),它可以贡献3个矩阵(自己,和2,和1,2).同理分析(1,4).

先总结一下,对于一个值为1 的点,它所在行对答案的贡献是,从它开始向左可以找到的,连续1的个数,我们设定为f[i][j]。

f[1][1]=1,f[1][2]=2,f[1][3]=3,f[1][4]=4,f[2][2]=1........

下面考虑每个点列的方向对答案的贡献,当我们走到(1,2)点,其实它对列的影响只能到第2列(因为第3列为0,第二列的第一个为0,不在连续),列方向的贡献只能是1(f[2][2]),是否只是刚好在这种情况下列方向的贡献刚好等于f[2][2]?,其实分析可知,向下的最大贡献只能是这一列的最小f[i][j],因为这样才能保证所计算的方格中不含有0.

同理点向上方的贡献,也是这列上的最小f[i][j],所以,我们可以得到计算方法:

先预处理出f[i][j],然后按行扫描,对每个点,先计算行方向的贡献,在向上,向下找最小的f[i][j],即可。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+9;
const int mod=1e9+7;
#define inf 0x3f3f3f3f
int n,m;
ll s[maxn][101],num[maxn][101];
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
    int T;
    cin>>T;
    int Case=0;
    while(T--){
    int k;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            s[i][j]=1;
        }
    }
    while(k--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        s[x][y]=0;
    }
    memset(num,0,sizeof(num));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(s[i][j])
            {
                num[i][j]++;
                num[i][j]+=num[i][j-1];
            }
            else
            {
                num[i][j]=0;
            }
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(num[i][j])
            {
                int tmp=num[i][j],h1,h2;
                h1=i-1,h2=i+1;
                while(h1>=1&&num[h1][j]>tmp) h1--;
                while(h2<=m&&num[h2][j]>=tmp) h2++;
                ans+=1ll*(i-h1)*(h2-i)*tmp;
            }
        }
    }
    printf("Case #%d: ",++Case);
    printf("%lld\n",ans);
    }
    return 0;
}

单调栈优化:

上面的算法其实已经很精妙了,但是复杂度还是太高,其实我们可以优化一下找列的贡献值,因为当我们已经查出(i,j)的向下或向上贡献后,可以推出(i+1,j)的贡献。

具体做法就是设up[]和down[]数组,分别记录当前位置向下或向上能够产生的贡献。

具体细节看代码就明了了。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+9;
const int mod=1e9+7;
#define inf 0x3f3f3f3f
int n,m;
ll s[maxn][102],num[maxn][102];
ll sta[maxn],tt;
ll up[maxn],down[maxn];
void singleupStack(int i,int j)
{
    while(tt && num[i][j] < num[sta[tt]][j]) down[i]+=down[sta[tt]],--tt;
}
void singledownStack(int i,int j)
{
    while(tt && num[i][j] < num[sta[tt]][j]) down[i]+=down[sta[tt]],--tt;
}
int main()
{
     #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
    int T;
    cin>>T;
    int k;
    int Case=0;
    while(T--)
    {
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                s[i][j]=1;
            }
        }
        for(int i=0,x,y;i<k;i++)
        {
            scanf("%d%d",&x,&y);
            s[x][y]=0;
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(s[i][j]==1)
                {
                    num[i][j]=num[i][j-1]+1;
                }
                else
                {
                    num[i][j]=0;
                }
            }
        }
        ll ans=0;
        for(int j=1;j<=m;j++)
        {
            tt=0;
            for(int i=1;i<=n;i++)
            {
                if(num[i][j]!=0)
                {
                    up[i]=1;
                    while(tt && num[i][j] <= num[sta[tt]][j]) up[i]+=up[sta[tt]],--tt;
                    sta[++tt]=i;
                }
                else
                {
                    tt=0,up[i]=0;
                }
            }
            tt=0;
            for(int i=n;i>=1;i--)
            {
                if(num[i][j]!=0)
                {
                    down[i]=1;
                    while(tt && num[i][j] < num[sta[tt]][j]) down[i]+=down[sta[tt]],--tt;
                    sta[++tt]=i;
                }
                else 
                {
                    tt=0,down[i]=0;
                }
                ans += up[i]*down[i]*num[i][j];
            }
        }
        printf("Case #%d: ",++Case);
        printf("%lld\n",ans);
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值