【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
题目大意:给出一个
n⋅m
的矩阵,定义
Sij
为大小为a*b的子矩阵的鞍点的加和
求一个整数
W=∑1≤i≤n1≤j≤mi⋅j⋅Sij(mod232)
鞍点的定义:矩阵中满足行中唯一最小 列中唯一最大的元素 为该矩阵的鞍点。
类似的
1132
对于这个矩阵,左上角的1不是鞍点,因为虽然满足行中最小,列中不是唯一最大。
但左下角的2是鞍点,因为满足了列中唯一最大 行中唯一最小
如果直接枚举矩阵大小计算TLE无疑。
可以转换一下,没法枚举矩阵计算总贡献,可以枚举
n⋅m
个元素,计算每个元素对最终答案的贡献。
对于元素
matij
可以找到以它为中心上下左右延伸出的最大的矩阵。
这一步可以用单调栈预处理出来。
以找左边界为例:
因为要找保证
matij
是行中唯一最小。对于第i行,可以从左到右维护一个递减的单调栈 这样,遍历到
matij
时,二分出单调栈中小于等于
matij
的最后一个元素,栈中存放pair < int,int > (val,position),找到得位置就是左边界(不包含)。同样方法预处理出来
n⋅m
个元素的四个边界即可。
这样其实
W=∑1≤i≤n1≤j≤mSij(mod232)
就很计算出来了。
但是题目中还有个
i⋅j
赛时我卡在这了……
赛后QAQ巨给我秒秒钟解答了。。。Orz想来也感觉自己好菜。。
假设上下左右四个边界分别为a b c d,其实存边界没有卵用,直接存上下左右可扩展出的大小更直观。
1132
例如,该矩阵左上角的1四个区域范围为 1 1 1 1
右下角的2 上下左右四个区域范围为 2 1 2 1
这样对于每个元素。对答案的贡献就是
∑1≤U≤a1≤D≤b1≤L≤c1≤R≤d(U+D−1)⋅(L+R−1)⋅matij
化简一下
∑1≤U≤a1≤D≤b(U+D−1)×∑1≤L≤c1≤R≤d(L+R−1)×matij
=(∑U=1aU⋅b+∑D=1bD⋅a−a⋅b)×(∑L=1cL⋅d+∑R=1dR⋅c−c⋅d)×matij
=(a⋅(a+1)2×b+b⋅(b+1)2×a−a⋅b)×(c⋅(c+1)2×d+d⋅(d+1)2×c−c⋅d)×matij
=a⋅b⋅c⋅d⋅(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;
}