[CodeChef]Binary Land

280 篇文章 1 订阅
220 篇文章 2 订阅

题目

传送门 to CodeChef

思路

可恶,拿到这道题,还真的被唬住了……

我一开始转化成了这样的模型:有 q q q 个带宽为 3 3 3 的带状矩阵,每次询问一个区间内的矩阵的乘积的某一位,且保证询问按照左端点排序后,右端点单调不降。

由于矩阵可能不满秩,不能使用前缀积。那么考虑类似 R M Q \rm{RMQ} RMQ 的做法,很快会口胡出来一个:每 m m m 个矩阵作为一个块,对于这 q m q\over m mq 个块的序列, O ( q n 3 m − 1 ) \mathcal O(qn^3m^{-1}) O(qn3m1) 建立线段树,每次询问需要 log ⁡ q \log q logq 次矩阵乘法;而散块的部分,利用带状矩阵的特点,可以 O ( m n 2 ) \mathcal O(mn^2) O(mn2) 直接乘。

询问时的矩阵乘法,可以优化到 O ( n 2 ) \mathcal O(n^2) O(n2),因为只询问最终结果矩阵的某一个位置,每次的乘积只保留那一行。那么总复杂度就是 O ( q n 3 m − 1 + q n 2 m + q n 2 log ⁡ q ) \mathcal O(qn^3m^{-1}+qn^2m+qn^2\log q) O(qn3m1+qn2m+qn2logq),平衡一下取 m = n m=\sqrt{n} m=n 时复杂度为
O ( q n 2.5 + q n 2 log ⁡ q ) \mathcal O(qn^{2.5}+qn^2\log q) O(qn2.5+qn2logq)

此题中 n ⩽ log ⁡ q \sqrt{n}\leqslant\log q n logq,所以其实就是比标算多了一个 log ⁡ q \log q logq不过线段树的常数行不行啊


2021 / 9 / 14    u p d a t e \tt 2021/9/14\;update 2021/9/14update:直接猫树(分治)就可以做到 q n 2 log ⁡ q qn^2\log q qn2logq 了……为什么要写前面这些复杂的东西啊……


发现上面的做法的问题是:并没有用上 “只在上面删除、下面加入” 的约束条件。本来这东西应该是一个 队列 才对。

如果我们是栈的操作,就很简单了。飞快地联想起双端栈!只要一想起这玩意儿,整道题就已经做完了。

时间复杂度 O ( q n 2 ) \mathcal O(qn^2) O(qn2),大概是理论最优吧……

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxN = 20;
const int MaxQ = 300005;
const int Mod = 1e9+7;
int dp[MaxQ<<1][MaxN][MaxN], n;
char maze[MaxQ<<1][MaxN+2], opt[9];

void trans(int from,int to){
	rep(i,0,n-1) rep(j,0,n-1){
		dp[to][i][j] = 0;
		rep(k,max(0,i-1),i+1)
			if(k < n && maze[to][i] == maze[from][k]){
				dp[to][i][j] += dp[from][k][j];
				dp[to][i][j] %= Mod;
			}
	}
}
int main(){
	n = readint();
	int q = readint();
	int fro = MaxQ, bac = fro-1;
	for(int ans,a,b; q; --q){
		scanf("%s",opt);
		if(*opt == 'a'){
			scanf("%s",maze[++bac]);
			if(bac == MaxQ)
				rep(i,0,n-1) rep(j,0,n-1)
					dp[bac][i][j] = (i == j);
			else trans(bac-1,bac);
		}
		else if(*opt == 'r'){
			if(fro == MaxQ){
				int d = bac+1-MaxQ;
				rep(i,fro=MaxQ-d,MaxQ-1)
					memcpy(maze[i],maze[i+d],n);
				rep(i,0,n-1) rep(j,0,n-1)
					dp[MaxQ-1][i][j] = (i == j);
				drep(p,MaxQ-2,fro) trans(p+1,p);
				bac = MaxQ-1; // clear
			}
			++ fro; // pop_front()
		}
		else if(*opt == 'p'){
			a = readint()-1, b = readint()-1;
			if(fro == MaxQ || bac == MaxQ-1)
				rep(i,ans=0,n-1) ans = (ans+1ll*
					dp[fro][a][i]*dp[bac][b][i])%Mod;
			else rep(i,ans=0,n-1) rep(j,max(0,i-1),i+1)
				if(j < n && maze[MaxQ-1][i] == maze[MaxQ][j])
					ans = (ans+1ll*dp[fro][a][i]*dp[bac][b][j])%Mod;
			printf("%d\n",ans);
		}
	}
	return 0;
}

后记

记得 l l s w \sf llsw llsw 上课的时候还给我说:滑动窗口求任意区间最小值,只需要用这个双端栈,两边维护 S T \rm ST ST 表就行。然后我对着这道题想了一个小时。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值