专栏文章

P9990

P9990题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mio2yh70
此快照首次捕获于
2025/12/02 12:30
3 个月前
此快照最后确认于
2025/12/02 12:30
3 个月前
查看原文

思路

考对于所有操作离线,按照右边边界的大小排序。
对于每一个位置 ii,记录 fjf_j 表示如果第 jj 个位置到第 ii 个位置的序列是否满足条件(11 表示满足;00 表示不满足。规定当 j>ij>ifj=0f_j=0)。
显然,对于 [l,r][l,r] 中的合法序列个数位 flf_lfrf_r 中所有与的元素在 ii 走到 rr 时的历史总和。
然后就是历史和线段树的板子了。
可以使用矩阵乘法。
构造 [balen]\begin{bmatrix}b&a&len\end{bmatrix} 分别当前节点表示历史和、但前和、总长度。
当要将 aa 加到 bb 上时将矩阵乘上:
[100110001]\begin{bmatrix} 1&0&0\\ 1&1&0\\ 0&0&1 \end{bmatrix}
当要将 aa 中异或时:
[100010011]\begin{bmatrix} 1&0&0\\ 0&-1&0\\ 0&1&1 \end{bmatrix}
然后用线段树维护即可。
但是这样写只能拿 50 分。因为常数巨大。
考虑卡常。
  • 对于第一行的第二个、第一行的三个、第二行的第三个元素一定为 00,可以再计算时不考虑它们。
  • 对于第一行的第一个、第三行的第三个数一定为 11,可以省略乘法。
  • 用手写链表存查询。
  • 多交几次。
AC 记录注意第十六号点的时间

代码

CPP
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000005;
struct M//矩阵
{
    ll a[3][3];
    inline void mem()
    {
        a[0][0]=0;
        a[1][0]=0;a[1][1]=0;
        a[2][0]=0;a[2][1]=0;a[2][2]=0;
    }
    inline void init()
    {
        a[0][0]=1;
        a[1][0]=0;a[1][1]=1;
        a[2][0]=0;a[2][1]=0;a[2][2]=1;
    }
    inline M operator*(M b)
    {
        M ans;
        ans.a[0][0]=1;
        ans.a[1][0]=a[1][0]+a[1][1]*b.a[1][0];
        ans.a[1][1]=a[1][1]*b.a[1][1];
        ans.a[2][0]=a[2][0]+a[2][1]*b.a[1][0]+b.a[2][0];
        ans.a[2][1]=a[2][1]*b.a[1][1]+b.a[2][1];
        ans.a[2][2]=1;
        return ans;
    }
}A;
struct seg//线段树
{
    ll b,a,len;
    M t;//懒标记
}w[8*N];
struct E
{
    int ne,to;
}e[N];
int he[N],idx,a[N],b[N],l[N],r[N];
ll ans[N];
inline void pushup(int u)//跟新节点信息
{
    w[u].b=w[u<<1].b+w[u<<1|1].b;
    w[u].a=w[u<<1].a+w[u<<1|1].a;
    w[u].len=w[u<<1].len+w[u<<1|1].len;
}
inline void pushdown(int u)//懒标记下传
{
    M t=w[u].t;w[u].t.init();
    w[u<<1].t=w[u<<1].t*t;
    w[u<<1|1].t=w[u<<1|1].t*t;
    ll b,a;
    b=w[u<<1].b+t.a[1][0]*w[u<<1].a+t.a[2][0]*w[u<<1].len;
    a=t.a[1][1]*w[u<<1].a+t.a[2][1]*w[u<<1].len;
    w[u<<1].b=b;w[u<<1].a=a;
    b=w[u<<1|1].b+t.a[1][0]*w[u<<1|1].a+t.a[2][0]*w[u<<1|1].len;
    a=t.a[1][1]*w[u<<1|1].a+t.a[2][1]*w[u<<1|1].len;
    w[u<<1|1].b=b;w[u<<1|1].a=a;
}
void build(int u,int l,int r)
{
    w[u].t.init();
    if(l==r)
    {
        w[u].len=1;
        return;
    }
    int mid=(l+r)>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
    pushup(u);
}
void mul(int u,int l,int r,int x,int y)//对于每一个节点乘上矩阵A
{
    pushdown(u);
    if(l>=x&&r<=y)
    {
        w[u].t=w[u].t*A;
        w[u].b=w[u].b;
        w[u].a=-w[u].a+w[u].len;
        return;
    }
    int mid=(l+r)>>1;
    if(mid>=x)mul(u<<1,l,mid,x,y);
    if(mid<y)mul(u<<1|1,mid+1,r,x,y);
    pushup(u);
}
ll get(int u,int l,int r,int x,int y)//得到区间历史和
{
    pushdown(u);
    if(l>=x&&r<=y)return w[u].b;
    int mid=(l+r)>>1;ll sum=0;
    if(mid>=x)sum+=get(u<<1,l,mid,x,y);
    if(mid<y)sum+=get(u<<1|1,mid+1,r,x,y);
    return sum;
}
void add(int x,int y)
{
    e[++idx].ne=he[x];
    e[idx].to=y;
    he[x]=idx;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n,m;
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>l[i]>>r[i];
        add(r[i],i);
    }
    build(1,1,n);
    A.init();
    for(int i=1;i<=n;i++)
    {
        A.a[1][1]=-1;A.a[2][1]=1;
        mul(1,1,n,b[a[i]]+1,i);//异或
        A.a[1][1]=1;A.a[2][1]=0;
        A.a[1][0]=1;
        pushdown(1);
        w[1].t=w[1].t*A;
        w[1].b=w[1].b+w[1].a;
        w[1].a=w[1].a;//可以将全局操作放进主函数
        A.a[1][0]=0;
        b[a[i]]=i;//跟新last
        for(int j=he[i];j;j=e[j].ne)
            ans[e[j].to]=get(1,1,n,l[e[j].to],i);//处理询问
    }
    for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
    return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...