[2020-09-11 CQBZ/HSZX多校联测 T3] 万猪拱塔(线段树+巧妙转化)

description

题目描述
小七养了很多头猪,它们分布在 n 行 m 列中,其中第 i 行第 j 列的猪圈养的是第 w i , j w_{i,j} wi,j 种猪。
小七有时会选择一个子矩形范围内的猪圈进行巡视,如果该子矩形包含 i 行 j 列 (1 ≤ i ≤ n; 1 ≤ j ≤ m),且子矩形内含有的猪种类编号最大值减去编号最小值恰好为 i × j - 1 ,则小七会获得 i × j 的愉悦值。
小七想知道,如果他将每个可能的子矩形都巡视一次,总共能获得多少愉悦值呢?你只需要输出答案对 998244353 取模的结果。

输入格式
输入文件 pig.in 包含 1 + n × m 行。
第一行输入两个正整数依次表示 n; m 。
接下来 n 行每行包含 m 个正整数,其中第 i 行的第 j 个正整数表示 wi;j 。

输出格式
输出文件 pig.out 包含一行,仅一个非负整数,表示答案对 998244353 取模
的结果。
样例输入

2 2
1 3
2 4

样例输出

12

样例解释
合法子矩形面积为 1 的有 4 个,面积为 2 的有 2 个,面积为 4 的有 1 个。
数据范围及约定

测试点编号n × m特殊性质
1 ∼ 2≤ 50
3 ∼ 6≤ 10^4
7 ∼ 12≤ 2 × 10^5n = 1
13 ∼ 20≤ 2 × 10^5

对于 100% 的数据, 1 ≤ wi;j ≤ n × m ≤ 2 × 105 , 保证 wi;j 互不相同。

solution

先吐槽一下题目的 包含 真的是误导新青年!!

我以为是原矩阵的 i i i j j j列,结果是选的矩阵重新编号后的 i i i j j j

实际上就是问多少矩阵内种类编号的极差等于矩阵行数乘列数再减一


应该不会有人只拿case 1~2的暴力吧

case 1~6 :枚举所选矩阵的左上角,求出以其右下方每一个点为矩阵右下角的种类最大值和最小值,可以通过扫一遍,然后行加一的时候继承上面一行信息,再操作

case 7~12n=1是一个一维问题,可以用线段树和单调栈解决

具体而言,枚举矩阵右端点 r r r,如果区间 [ l , r ] [l,r] [l,r]合法,则满足 Max l , r −  Min l , r = r − l + 1 \text{Max}_{l,r}-\text{ Min}_{l,r}=r-l+1 Maxl,r Minl,r=rl+1

对于每个 l l l的定义 f [ l ] = Max l , r −  Min l , r + l f[l]=\text{Max}_{l,r}-\text{ Min}_{l,r}+l f[l]=Maxl,r Minl,r+l,用线段树维护 f [ l ] f[l] f[l]

因为种类互不相同,所以 ∀ l f [ l ] ≥ r + 1 \forall_l f[l]\ge r+1 lf[l]r+1,只需要线段树维护 f [ l ] f[l] f[l]的最小值,最小值个数,以及所有 l l l之和,就可以统计了,即 ( r + 1 ) ∗ c n t − s u m (r+1)*cnt-sum (r+1)cntsum

右端点移动 1 1 1,用单调栈维护其影响的 Max l , r , Min l , r \text{Max}_{l,r},\text{Min}_{l,r} Maxl,r,Minl,r,在线段树上修改

Max l , r \text{Max}_{l,r} Maxl,r维护一个单调递减栈, Min l , r \text{Min}_{l,r} Minl,r维护一个单调递增栈

case 13~20 :很不幸,一维的做法并不能处理二维情况

考虑另外一种nb的转化

考虑判定 权值 [ l , r ] [l,r] [l,r]区间的点是否构成了一个矩阵,记这些格子的颜色为黑色,其余均为白色

n × m n\times m n×m的猪圈算上周围一圈的空白边界,看成 ( n + 1 ) × ( m + 1 ) (n+1)\times (m+1) (n+1)×(m+1)的猪圈

产生 ( n + 1 ) × ( m + 1 ) (n+1)\times (m+1) (n+1)×(m+1) 2 × 2 2\times 2 2×2的小正方形猪圈(不能超出边界),标记在 2 × 2 2\times 2 2×2这个矩阵的左上角

如果所有黑色格子能围成一个矩阵,当且仅当恰好有4个小猪圈只有1个黑点,没有任何一个小猪圈有3个黑点

不要急,仔细想想这句很妙的话,小猪圈是由其左上角表示的,只有围成的矩阵的四个顶点所在的小猪圈,只含这个顶点一个黑点

从小到大枚举权值 r r r,定义 F [ l ] F[l] F[l]为染黑权值为 [ l , r ] [l,r] [l,r]的格子,其余格子为白色,小猪圈内有 1 / 3 1/3 1/3个合格子的小猪圈数量

显然 F [ r ] = 4 , F [ l ] ≥ 4 F[r]=4,F[l]\ge 4 F[r]=4,F[l]4

这个时候可以利用一维的做法,线段树维护 F [ l ] F[l] F[l]的最小值,最小值个数以及这些 l l l的和,贡献计算也是一样的

r r r增加 1 1 1的时候,只会影响 4 4 4 2 × 2 2\times 2 2×2的矩阵(分别是 r + 1 r+1 r+1做左上角,右上角,左下角,右下角时的不同的 4 4 4个小猪圈)

暴力修改即可——详见代码

最后就是这道题的天坑——卡常

必须开Ofast不然分竟然跟暴力差不多,快读快输,还有这种 n × m ≤ 2 e 5 n\times m\le 2e5 n×m2e5的必须卡着矩阵长相开数组的

这是什么jian题 好题,真的是好题!!

code

#pragma GCC optimize( "Ofast" )
#include <cstdio>
#define mod 998244353
#define maxn 200005
int Min[maxn << 2], cnt[maxn << 2], tag[maxn << 2];
long long sum[maxn << 2];
int ID[maxn];
int n, m, **w;
 
#define Make( arr ) do { \
    arr = new int*[n + 3]; \
    for( int i = 0;i <= n + 2;i ++ ) arr[i] = new int[m + 3](); \
} while( false )
 
inline void read( int &x ) {
    x = 0; char s = getchar();
    while( s < '0' or s > '9' ) s = getchar();
    while( '0' <= s and s <= '9' ) {
        x = ( x << 1 ) + ( x << 3 ) + ( s ^ 48 );
        s = getchar();
    }
}
 
inline void write( int x ) {
    if( x >= 10 ) write( x / 10 );
    putchar( x % 10 ^ 48 );
}
 
#define lson num << 1
#define rson num << 1 | 1
#define inf 0x3f3f3f3f
 
int chkmin( int x, int y ) { return x < y ? x : y; }
 
inline void pushup( int num ) {
    Min[num] = chkmin( Min[lson], Min[rson] );
    cnt[num] = sum[num] = 0;
    if( Min[num] == Min[lson] ) cnt[num] += cnt[lson], sum[num] += sum[lson];
    if( Min[num] == Min[rson] ) cnt[num] += cnt[rson], sum[num] += sum[rson];
}
 
inline void build( int num, int l, int r ) {
    if( l == r ) { Min[num] = inf, cnt[num] = 1, sum[num] = l; return; }
    int mid = ( l + r ) >> 1;
    build( lson, l, mid );
    build( rson, mid + 1, r );
    pushup( num );
}
 
inline void pushdown( int num ) {
    if( ! tag[num] ) return;
    Min[lson] += tag[num];
    tag[lson] += tag[num];
    Min[rson] += tag[num];
    tag[rson] += tag[num];
    tag[num] = 0;
}
 
inline void modify( int num, int l, int r, int L, int R, int val ) {
    if( r < L or R < l ) return;
    if( L <= l and r <= R ) { Min[num] += val, tag[num] += val; return; }
    pushdown( num );
    int mid = ( l + r ) >> 1;
    modify( lson, l, mid, L, R, val );
    modify( rson, mid + 1, r, L, R, val );
    pushup( num );
}
 
inline void modify( int r, int c, int Max, int opt ) {
    static int arr[4];
    if( ! ~opt ) {
        arr[0] = w[r][c], arr[1] = w[r][c + 1], arr[2] = w[r + 1][c], arr[3] = w[r + 1][c + 1];
        for( int i = 0;i < 3;i ++ )
            for( int j = 0;j < 3;j ++ )
                if( arr[j] > arr[j + 1] )    
                    arr[j] ^= arr[j + 1] ^= arr[j] ^= arr[j + 1];
    }
    int ip = 0;
    for(;ip < 4 and arr[ip] <= Max;ip ++ );
    if( ip == 1 or ( ip > 1 and arr[ip - 1] ^ arr[ip - 2] ) )
        modify( 1, 1, n * m, ip == 1 ? 1 : arr[ip - 2] + 1, arr[ip - 1], opt );
    if( ip == 3 or ( ip > 3 and arr[ip - 3] ^ arr[ip - 4] ) )
        modify( 1, 1, n * m, ip == 3 ? 1 : arr[ip - 4] + 1, arr[ip - 3], opt );
}
 
inline int mul( int x, int y ) { return 1ll * x * y % mod; }
inline int sub( int x, int y ) { return x - y < 0 ? x - y + mod : x - y; }
inline int add( int x, int y ) { return 1ll * x + y >= mod ? 1ll * x + y - mod : x + y; }
 
int main() {
    freopen( "pig.in", "r", stdin );
    freopen( "pig.out", "w", stdout );
    scanf( "%d %d", &n, &m ); Make( w );
    for( int i = 0;i <= n + 1;i ++ ) w[i][0] = w[i][m + 1] = n * m + 1;
    for( int i = 1;i <= m;i ++ ) w[0][i] = w[n + 1][0] = n * m + 1;
    for( int i = 1;i <= n;i ++ )
        for( int j = 1;j <= m;j ++ )
            read( w[i][j] ), ID[w[i][j]] = ( i - 1 ) * m + j;
    build( 1, 1, n * m );
    int ans = 0;
    for( int i = 1;i <= n * m;i ++ ) {
        int r = ( ID[i] - 1 ) / m + 1, c = ID[i] - ( r - 1 ) * m;
        modify( 1, 1, n * m, i, i, -inf );
        for( int j = -1;j <= 0;j ++ )
            for( int k = -1;k <= 0;k ++ ) {
                modify( r + j, c + k, i - 1, -1 );
                modify( r + j, c + k, i, 1 );
            }
        if( Min[1] == 4 ) ans = add( ans, sub( mul( i + 1, cnt[1] ), sum[1] % mod ) );
    }
    write( ans );
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值