【HDU 5749】Colmerauer(单调栈+组合数学)

【HDU 5749】Colmerauer(单调栈+组合数学)
Colmerauer

Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 161 Accepted Submission(s): 67

Problem Description
Peter has an n×m matrix M. Let S(a,b) be the sum of the weight all a×b submatrices of M. The weight of matrix is the sum of the value of all the saddle points in the matrix. A saddle point of a matrix is an element which is both the only largest element in its column and the only smallest element in its row. Help Peter find out all the value of S(a,b).

Note: the definition of saddle point in this problem may be different with the definition you knew before.

Input
There are multiple test cases. The first line of input contains an integer T, indicating the number of test cases. For each test case:

The first contains two integers n and m (1≤n,m≤1000) – the dimensions of the matrix.

The next n lines each contain m non-negative integers separated by spaces describing rows of matrix M (each element of M is no greater than 106).

Output
For each test case, output an integer W=(∑a=1n∑b=1ma⋅b⋅S(a,b)) mod 232.

Sample Input
3
2 2
1 1
1 1
3 3
1 2 3
4 5 6
7 8 9
3 3
1 2 1
2 3 1
4 5 2

Sample Output
4
600
215

Source
BestCoder Round #84

Recommend
wange2014

题目大意:给出一个 nm 的矩阵,定义 Sij 为大小为a*b的子矩阵的鞍点的加和
求一个整数 W=1in1jmijSij(mod232)

鞍点的定义:矩阵中满足行中唯一最小 列中唯一最大的元素 为该矩阵的鞍点。
类似的
1132
对于这个矩阵,左上角的1不是鞍点,因为虽然满足行中最小,列中不是唯一最大。
但左下角的2是鞍点,因为满足了列中唯一最大 行中唯一最小

如果直接枚举矩阵大小计算TLE无疑。
可以转换一下,没法枚举矩阵计算总贡献,可以枚举 nm 个元素,计算每个元素对最终答案的贡献。
对于元素 matij 可以找到以它为中心上下左右延伸出的最大的矩阵。
这一步可以用单调栈预处理出来。
以找左边界为例:
因为要找保证 matij 是行中唯一最小。对于第i行,可以从左到右维护一个递减的单调栈 这样,遍历到 matij 时,二分出单调栈中小于等于 matij 的最后一个元素,栈中存放pair < int,int > (val,position),找到得位置就是左边界(不包含)。同样方法预处理出来 nm 个元素的四个边界即可。

这样其实 W=1in1jmSij(mod232) 就很计算出来了。
但是题目中还有个 ij 赛时我卡在这了……
赛后QAQ巨给我秒秒钟解答了。。。Orz想来也感觉自己好菜。。

假设上下左右四个边界分别为a b c d,其实存边界没有卵用,直接存上下左右可扩展出的大小更直观。

1132
例如,该矩阵左上角的1四个区域范围为 1 1 1 1
右下角的2 上下左右四个区域范围为 2 1 2 1

这样对于每个元素。对答案的贡献就是
1Ua1Db1Lc1Rd(U+D1)(L+R1)matij
化简一下
1Ua1Db(U+D1)×1Lc1Rd(L+R1)×matij
=(U=1aUb+D=1bDaab)×(L=1cLd+R=1dRccd)×matij
=(a(a+1)2×b+b(b+1)2×aab)×(c(c+1)2×d+d(d+1)2×ccd)×matij
=abcd(a+b)(c+d)4×matij

解题完毕

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <stack>
#include <map>
#include <algorithm>
#define pr pair<int,int>
#define vi vector<int>
#define ll long long

using namespace std;
const int inf = 0x3f3f3f3f;
const ll mod = 1ll<<32;
const double eps = 1e-8;
const int msz = 1000000;

ll num[1111][1111];
//每个元素的四个区间
int rg[1111][1111][4];
pr tmp[1111];
int tp;

//二分找四个区间
int search(int opt,int x)
{
    int l,r;
    int ans = -1;
    l = 0;
    r = tp-1;

    if(opt == 0)
    {
        while(l <= r)
        {
            int mid = (l+r)>>1;
            if(tmp[mid].first <= x)
            {
                ans = mid;
                l = mid+1;
            }
            else r = mid-1;
        }
    }
    else
    {
        while(l <= r)
        {
            int mid = (l+r)>>1;
            if(tmp[mid].first >= x)
            {
                ans = mid;
                l = mid+1;
            }
            else r = mid-1;;
        }
    }
    return ans == -1? -1: tmp[ans].second;
}

//求和公式
ll solve(ll a,ll b,ll c,ll d)
{
   // printf("%lld %lld %lld %lld\n",a,b,c,d);
    return a*b*c*d*(a+b)*(c+d)/4;
    //printf("%lld\n",ans);
}

int main()
{

    int t,n,m;

    scanf("%d",&t);

    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i = 0; i < n; ++i)
            for(int j = 0; j < m; ++j)
                scanf("%lld",&num[i][j]);

        ll ans = 0;
        int l,r;
        int a,b,x,y;

        for(int i = 0; i < n; ++i)
        {
            tp = 0;
            for(int j = 0; j < m; ++j)
            {
                rg[i][j][0] = search(0,num[i][j]);
                rg[i][j][0] = j-rg[i][j][0];
                while(tp > 0 && num[i][j] <= tmp[tp-1].first) tp--;
                tmp[tp].first = num[i][j];
                tmp[tp++].second = j;
            }
        }

        for(int i = 0; i < n; ++i)
        {
            tp = 0;
            for(int j = m-1; j >= 0; --j)
            {
                rg[i][j][1] = search(0,num[i][j]);
                if(rg[i][j][1] == -1) rg[i][j][1] = m;
                rg[i][j][1] = rg[i][j][1]-j;
                while(tp > 0 && num[i][j] <= tmp[tp-1].first) tp--;
                tmp[tp].first = num[i][j];
                tmp[tp++].second = j;
            }
        }

        for(int j = 0; j < m; ++j)
        {
            tp = 0;
            for(int i = 0; i < n; ++i)
            {
                rg[i][j][2] = search(1,num[i][j]);
                rg[i][j][2] = i-rg[i][j][2];
                while(tp > 0 && num[i][j] >= tmp[tp-1].first) tp--;
                tmp[tp].first = num[i][j];
                tmp[tp++].second = i;
            }
        }

        for(int j = 0; j < m; ++j)
        {
            tp = 0;
            for(int i = n-1; i >= 0; --i)
            {
                rg[i][j][3] = search(1,num[i][j]);
                if(rg[i][j][3] == -1) rg[i][j][3] = n;
                rg[i][j][3] = rg[i][j][3]-i;
                while(tp > 0 && num[i][j] >= tmp[tp-1].first) tp--;
                tmp[tp].first = num[i][j];
                tmp[tp++].second = i;
            }
        }

        for(int i = 0; i < n; ++i)
            for(int j = 0; j < m; ++j)
            {
               // printf("%d %d %d %d\n",rg[i][j][0],rg[i][j][1],rg[i][j][2],rg[i][j][3]);
                ans = (ans+((num[i][j]*solve(rg[i][j][0],rg[i][j][1],rg[i][j][2],rg[i][j][3]))%mod))%mod;
            }

        printf("%lld\n",ans);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值