zoukankan      html  css  js  c++  java
  • P5300 [GXOI/GZOI2019]与或和 [单调栈]

    与或和

    题目描述见链接 .


    color{red}{正解部分}

    1子任务1: 求有多少全11矩阵 .
    2子任务2: 求有多少存在11的矩阵 ightarrow 总矩阵数目 -00矩阵数目 .
    其中 全00矩阵数目 等于 i=1Nij=1nj=(N(N+1)2)2sum_{i=1}^Nisum_{j=1}^nj = (frac{N(N+1)}{2})^2 个.

    于是只需解决 1子任务1 即可, 怎么解决呢 ?? 我们使用 单调栈 .

    (i,j)(i, j) 及其上方连续 11 的个数是 Up[i,j]Up[i, j], 最后答案为 resres,

    枚举矩阵的下边界, O(N)O(N), 从左向右维护一个 Up[i,j]Up[i, j] 单调递增单调栈,
    设当前 单调栈高度不同左上端点 数量为 tmptmp, 那么在新加入一个元素 Up[i,j]Up[i,j] 时, 分 22 类情况讨论,

    • 满足单调性, 则 tmptmp不同高度左上端点 可以获得 相应高度的 右端点,
      而这 Up[i,j]Up[i, j]11 又可以作为 Up[i,j]Up[i,j]不同高度左上端点, 且 可以 以自己为 右端点 .
      于是 tmp+=Up[i,j], res+=tmptmp += Up[i, j], res += tmp .
    • 不满足单调性, 此时tmptmp 个 … 存在不能以这 Up[i,j]Up[i,j]11右端点左端点,
      且这些 左端点 对应的矩形都已经在前面计算过了, 已无用, 需要丢掉,
      Up[i,stk[k]]Up[i,j]Up[i,stk[k]] le Up[i,j], 不断弹出栈顶直到stk[top]=stk[k]stk[top] = stk[k] 即可,
      在弹出一个栈顶时, 有 t=(stk[top]stk[top1])(Up[i,stk[top]Up[i,j])t=(stk[top] - stk[top-1]) * (Up[i, stk[top] - Up[i, j])左端点 消失了,
      因此每次弹出时, tmp=ttmp-=t, 弹完后, 左端点 就全部可以正常使用了,
      同上 tmp+=Up[i,j], res+=tmptmp += Up[i, j], res += tmp .

    最后得到 resres .


    color{red}{实现部分}

    上面已经说得很清楚了 .

    #include<bits/stdc++.h>
    #define reg register
    typedef long long ll;
    
    int read(){
            char c;
            int s = 0, flag = 1;
            while((c=getchar()) && !isdigit(c))
                    if(c == '-'){ flag = -1, c = getchar(); break ; }
            while(isdigit(c)) s = s*10 + c-'0', c = getchar();
            return s * flag;
    }
    
    const int maxn = 1002;
    const int mod = 998244353;
    
    int N;
    int top;
    int Ans;
    int Ans_1;
    int Max_v;
    int Ld[maxn];
    int Rd[maxn];
    int stk[maxn];
    int A[maxn][maxn];
    int Up[maxn][maxn];
    
    int sum_1[maxn][maxn][2];
    //int sum_2[maxn][maxn][2];
    
    ll pw[maxn];
    
    ll Calc(int p){ 
            ll res = 0;
            for(reg int j = 1; j <= N; j ++)
                    for(reg int i = 1; i <= N; i ++){ 
                            Up[i][j] = 0;
                            if(A[i][j] & pw[p]) Up[i][j] = Up[i-1][j] + 1;
                    }
            for(reg int i = 1; i <= N; i ++){
                    top = 1; ll tmp = 0;
                    for(reg int j = 1; j <= N; j ++){
                            while(top != 1 && Up[i][stk[top]] > Up[i][j])
                                    tmp -= (stk[top]-stk[top-1]) * (Up[i][stk[top]] - Up[i][j]), top --;
                            tmp += Up[i][j], stk[++ top] = j;
                            res = (res + tmp) % mod;
                    }
            }
            return res % mod;
    }
    
    int main(){
            freopen("mob.in", "r", stdin);
            freopen("mob.out", "w", stdout);
            N = read();
            for(reg int i = 1; i <= N; i ++)
                    for(reg int j = 1; j <= N; j ++) A[i][j] = read(), Max_v = std::max(Max_v, A[i][j]);
            pw[0] = 1;
            for(reg int i = 1; i <= 100; i ++) pw[i] = pw[i-1]<<1;
            ll tot = N*(N+1)/2 % mod; tot = tot*tot % mod;
            for(reg int p = 0; pw[p] <= Max_v; p ++){
                    Ans_1 = (Ans_1 + (1ll*pw[p]*Calc(p))%mod) % mod;
                    for(reg int i = 1; i <= N; i ++) 
                            for(reg int j = 1; j <= N; j ++) A[i][j] ^= pw[p];
                    int num = (tot - Calc(p) + mod) % mod;
                    Ans = (Ans + (1ll*pw[p]*num)%mod) % mod;
            }
            printf("%d
    ", Ans_1);
            printf("%d
    ", Ans);
            return 0;
    }
    
  • 相关阅读:
    驱动下的异常处理
    头文件 .h 与源文件 .ccp 的区别
    驱动程序进阶篇
    驱动中链表的使用
    内存操作相关内核 API 的使用
    链表的概念、建立、删除与插入
    编写简单的 NT 式驱动程序的加载与卸载工具
    驱动程序入门篇
    c++ 指针的简单用法
    CTL_CODE 宏 详解
  • 原文地址:https://www.cnblogs.com/zbr162/p/11822486.html
Copyright © 2011-2022 走看看