2022 百度之星程序设计大赛复赛 D.子序列2(动态dp/线段树维护矩阵)

题目

序列a1,a2,...ap是好序列,

当且仅当p>=1且任意i∈[1,p),有a_{i}<k \leq a_{i+1}a_{i+1}<k \leq a_{i}

给定长为n(n<=1e5)的序列a1,...,an(1<=ai<=n),q(q<=2e5)次询问

每次询问给定k,l,r(1<=k<=n,1<=l<=r<=n),

询问区间[l,r]内的子序列中有多少个是好序列

答案对1e9+7取模

思路来源

比赛几分钟后才做出来,这种题做过无数次了,次次被卡

题解

一开始想着边转移,但是不行,需要有一个末态的概念

所以,需要按点转移,>=k记为状态1,<k记为状态0,

子序列矩阵可以0转移到0,1转移到1,0转移到1,1转移到0,

其中,前两种表示本次不取,后两种只能同时有一个存在,表示之前取的是0/1,本次取的是1/0

将k离线,建立线段树矩阵,在ai<k时对ai单点反转c[0][1]和c[1][0]

注意到,c[0][0]和c[1][1]均有为空的一种方案,所以答案需要恒减2

最后几分钟就是这里没想清楚

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=2e5+10,mod=1e9+7;
const int INF=0x3f3f3f3f;
int n,q,l,r,v,a[N],ans[M];
vector<int>add[N];
struct mat{
	const static int MAXN = 2;
    int c[MAXN][MAXN];
    int m, n;
    mat(){
    	memset(c,0,sizeof (c));
    }
    mat(int a, int b) : m(a), n(b) {
        memset(c, 0, sizeof (c));
    }
    void reset(int a,int b){
    	m=a; n=b;
    }
    void clear(){
		memset(c, 0, sizeof(c)); 
    }
    mat operator + (const mat& temp) {
        mat ans(m, temp.n);
        for (int i = 0; i < m; i ++)
            for (int j = 0; j < temp.n; j ++) {
                for (int k = 0; k < n; k ++){
                	ans.c[i][j] = (ans.c[i][j] + 1ll * c[i][k] * temp.c[k][j] %mod)%mod;
                }
            }
        return ans;
    }
}dat[N*4],y;
struct node{
	int id,l,r;
	node(){
	}
	node(int a,int b,int c):id(a),l(b),r(c){
	}
};
vector<node>f[N];
void pushup(int p){
	dat[p]=dat[p<<1]+dat[p<<1|1];
}
void init(int p,int l,int r){
	dat[p].c[0][1]=1;
	dat[p].c[1][1]=1;
	dat[p].c[0][0]=1;
	dat[p].c[1][0]=0;
}
void build(int p,int l,int r){
	dat[p].reset(2,2);
	if(l==r){
		init(p,l,r);
		return;
	}
	int mid=(l+r)/2;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
}
void chg(int p,int l,int r,int pos){
    if(l==r){
        dat[p].c[1][0]=1;
        dat[p].c[0][1]=0;
        return;
    }    
	int mid=(l+r)/2;
    if(pos<=mid)chg(p<<1,l,mid,pos);
    else chg(p<<1|1,mid+1,r,pos);
    pushup(p);
}
mat ask(int p,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr)return dat[p];
	int mid=(l+r)/2;
	if(ql>mid)return ask(p<<1|1,mid+1,r,ql,qr);
	if(qr<=mid)return ask(p<<1,l,mid,ql,qr);
	return ask(p<<1,l,mid,ql,mid)+ask(p<<1|1,mid+1,r,mid+1,qr);
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		add[a[i]].push_back(i);
	}
	build(1,1,n);
	for(int i=1;i<=q;++i){
		int k,l,r;
		scanf("%d%d%d",&k,&l,&r);
		f[k].push_back(node(i,l,r));
	}
	for(int i=1;i<=n;++i){
		for(auto &x:f[i]){
			int id=x.id,l=x.l,r=x.r;
			y=ask(1,1,n,l,r);
			ans[id]=(1ll*y.c[0][0]+y.c[0][1]+y.c[1][0]+y.c[1][1])%mod;
			ans[id]=(ans[id]+mod-2)%mod;
		}
		for(auto &v:add[i]){
			chg(1,1,n,v);
		}
	}
	for(int i=1;i<=q;++i){
		printf("%d\n",ans[i]);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值