Anxdada的询问(two stack)

很多人会很自然的想到用two pointer来维护区间乘积, 但是从区间 [l,r] [ l , r ] 转移到 [l+1,r] [ l + 1 , r ] 的时候, 如果 a[l] a [ l ] 在模 p p 意义下的逆元不存在, 那么就无法转移

two stack:

用2个栈来维护区间[l,r]中数的乘积
其中一个栈 S1 S 1 维护的是区间 [l,k](lk) [ l , k ] ( l ≤ k ) 的倒序乘积, 即栈底元素是 a[k] a [ k ] , 上一层是 a[k]a[k1] a [ k ] ⋅ a [ k − 1 ] , ……,栈顶元素是: a[k]a[k1]a[l+1]a[l] a [ k ] ⋅ a [ k − 1 ] ⋯ a [ l + 1 ] ⋅ a [ l ] , 当要删除 a[l] a [ l ] 这个数时, 直接pop掉栈顶元素
另外一个栈 S2 S 2 维护的是区间 [k+1,r](k<r) [ k + 1 , r ] ( k < r ) 的顺序乘积, 即栈底是 a[k+1] a [ k + 1 ] , 上一层是 a[k+1]a[k+2] a [ k + 1 ] ⋅ a [ k + 2 ] , ……,栈顶元素是 a[k+1]a[k+2]a[r1]a[r] a [ k + 1 ] ⋅ a [ k + 2 ] ⋯ a [ r − 1 ] ⋅ a [ r ] , 当要向右扩展 a[r+1] a [ r + 1 ] 这个数时, 直接push进 a[r+1] a [ r + 1 ] 与栈顶元素的乘积
这样会产生一个问题, 如果栈 S1 S 1 为空, 但是要删除 a[l] a [ l ] 这个数, 这个时候把栈 S2 S 2 所表示的区间的数倒序推进栈 S1 S 1 并维护好乘积, 同时清空栈 S2 S 2 , 并修改 S1 S 1 S2 S 2 所表示的区间, 再按照上面做就行了

举例:

区间 [1,3] [ 1 , 3 ] 中有3个数字: a,b,c a , b , c
第一次查询 [1,2] [ 1 , 2 ] , 第二次查询区间 [2,3] [ 2 , 3 ]
第一次查询时, S1 S 1 为空, S2 S 2 先右扩充2个数字, 其自底到顶的元素为: a,a×b a , a × b
所以第一次询问答案就是 S1 S 1 S2 S 2 栈顶的乘积: a×b a × b
这时要进行一次删除: 即删除区间 [2,2] [ 2 , 2 ]
S2 S 2 所表示区间倒序更新到 S1 S 1 中, 并维护好乘积, 此时栈 S1 S 1 自底到顶是: b,a×b b , a × b , 同时清空 S2 S 2 , 根据询问要求将 [2,2] [ 2 , 2 ] 删除, 此时 S1 S 1 仅剩下一个元素 b b
S2向右扩充一个位置 [3,3] [ 3 , 3 ] 并维护好区间乘积, 此时栈 S2 S 2 中也只有一个元素 c c
所以第二次询问答案就是S1 S2 S 2 栈顶的乘积: b×c b × c

时间复杂度 O(n) O ( n ) 【与 q q <script type="math/tex" id="MathJax-Element-55">q</script>无关】
标程(未加读写外挂【会超时】):
#include<bits/stdc++.h>
#define fi first
#define sf scanf
#define se second
#define pf printf
#define pb push_back
#define mp make_pair
#define imax INT_MAX
#define imin INT_MIN
#define llmax LONG_LONG_MAX
#define llmin LONG_LONG_MIN
#define sz(x) ((int)(x).size())
#define mem(x,y) memset((x),(y),sizeof(x))
#define fup(i,x,y) for(int i=(x);i<=(y);i++)
#define fdn(i,x,y) for(int i=(x);i>=(y);i--)
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
using namespace std;

const int __=1e6+5;
int a[__],p;

struct node
{
    ll val;int id;
    node(ll x,int y):
        val(x),id(y) {}
};

stack<node>S[3];

int main()
{
    int n;sf("%d%d",&n,&p);
    fup(i,1,n)sf("%d",&a[i]);
    int q;sf("%d",&q);
    S[1].push(node(1,0));
    S[2].push(node(1,0));
    int l=1,r=0;
    while(q--)
    {
        int ql,qr;
        sf("%d%d",&ql,&qr);
        while(r<qr)
        {
            r++;
            S[2].push(node(S[2].top().val*a[r]%p,r));
        }
        while(l<ql && sz(S[1])>1)
            S[1].pop(),l++;
        if(sz(S[1])==1 && l!=ql)
            for(;sz(S[2])>1;S[2].pop())
            {
                int x=S[2].top().id;
                S[1].push(node(S[1].top().val*a[x]%p,x));
            }
        while(l<ql && sz(S[1])>1)
            S[1].pop(),l++;
        pf("%lld\n",S[1].top().val*S[2].top().val%p);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值