【CodeForces】【线段树】【矩阵乘法】663H-Fibonacci-ish II

CodeForces 663H Fibonacci-ish II

题目大意

◇题目传送门◆

给定一个长度为 N N N的序列,每次询问一个区间 [ L i , R i ] [L_i,R_i] [Li,Ri],要求将区间排序去重后求出 ( F 1 a 1 + F 2 a 2 + ⋯ + F n a n ) m o d    M (F_1a_1+F_2a_2+\cdots+F_na_n)\mod M (F1a1+F2a2++Fnan)modM,其中 F i F_i Fi是斐波那契数列的第 i i i项。

分析

对于一个离线的区间查询问题,而且还要求去重,我们可以很容易想到莫队。

但如何维护序列所对应的 F i F_i Fi呢?

众所周知 F i F_i Fi可以由矩阵 [ 1 1 1 0 ] \begin{bmatrix}1&1\\1&0\end{bmatrix} [1110] i i i次幂得到。

这样的话,我们考虑用一棵权值线段树来维护这个序列,线段树维护一个矩阵,即斐波那契数列的矩阵。

加入一个重复的值时,我们直接不管。

加入一个不重复的值时,我们需要将这个值以后的所有值全部乘上一个斐波那契数列矩阵。

当我们需要删除一个值时,我们就需要将这个值以后的所有区间全部乘上一个斐波那契数列的逆矩阵。

关于这个逆矩阵,我们可以手算得到 [ 0 1 1 − 1 ] \begin{bmatrix}0&1\\1&-1\end{bmatrix} [0111]

总时间复杂度为 O ( N N log ⁡ N T 3 ) O(N\sqrt{N}\log NT^3) O(NN logNT3),其中 T 3 T^3 T3是矩阵乘法的复杂度,是一个常数。

注意这道题常数巨大,我们需要一些优化(比如输入输出挂等)。

一种更好的优化是将所有的斐波那契数列矩阵全部预处理出来,查询时统计左子区间的值的个数,即可计算出右子区间的对应的斐波那契矩阵 (但我不想写)

据说放过了暴力QAQ…

参考代码

其实这道题输入时给定模数是为了卡掉那些用long long无脑取模的。

#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;
const int Maxn = 30000;

int N, Mod, Q, block_size;
int A[Maxn + 5], mp[Maxn + 5];
int block[Maxn + 5];

struct Query {
	int lef, rig, id;
};
bool cmp_query(Query lhs, Query rhs) {
	return block[lhs.lef] == block[rhs.lef] ? (block[lhs.lef] & 1 ? lhs.rig < rhs.rig : lhs.rig > rhs.rig) : block[lhs.lef] < block[rhs.lef];
}//据说这样会让莫队更快一点

struct Matrix {
	int mat[2][2];
	Matrix() {
		mat[0][0] = mat[1][1] = 1, mat[0][1] = mat[1][0] = 0;
	}
	Matrix(int typ) {
		if(typ == 1) mat[0][0] = mat[1][0] = mat[0][1] = 1, mat[1][1] = 0;
		if(typ == 0) mat[0][0] = mat[1][0] = mat[1][1] = mat[0][1] = 0;
		if(typ == -1) mat[0][0] = 0, mat[0][1] = mat[1][0] = 1, mat[1][1] = -1;
	}
	inline int * operator [] (int x) {return mat[x];}
	inline const int * operator [] (const int &x) const {return mat[x];}
	friend Matrix operator + (const Matrix &lhs, const Matrix &rhs) {
		Matrix ret(0);
		for(register int i = 0; i < 2; i++)
			for(register int j = 0; j < 2; j++)
				ret[i][j] += lhs[i][j] + rhs[i][j];
		for(register int i = 0; i < 2; i++)
			for(register int j = 0; j < 2; j++)
				ret[i][j] %= Mod;
		return ret;
	}
	friend Matrix operator * (const Matrix &lhs, const Matrix &rhs) {
		Matrix ret(0);
		for(register int i = 0; i < 2; i++)
			for(register int j = 0; j < 2; j++)
				for(register int k = 0 ; k < 2; k++)
					ret[i][j] += lhs[i][k] * rhs[k][j];
		for(register int i = 0; i < 2; i++)
			for(register int j = 0; j < 2; j++)
				ret[i][j] %= Mod;
		return ret;
	}
	Matrix operator * (int rhs) {
		Matrix ret(0);
		for(register int i = 0; i < 2; i++)
			for(register int j = 0; j < 2; j++)
				ret[i][j] += mat[i][j] * rhs;
		for(register int i = 0; i < 2; i++)
			for(register int j = 0; j < 2; j++)
				ret[i][j] %= Mod;
		return ret;
	}
	//加上大量的 register 和引用来卡常
	//其实另一个优化是优化这里的取模:
	//if(ret[i][j] >= Mod) ret[i][j] -= Mod;
	friend bool operator == (const Matrix &lhs, const Matrix &rhs) {
		for(register int i = 0; i < 2; i++)
			for(register int j = 0; j < 2; j++)
				if(lhs[i][j] != rhs[i][j])
					return false;
		return true;
	}
};

const Matrix One, G(1), invG(-1);

struct SegmentTree {
	struct Segment {
		int w;
		Matrix tag, val;
	};
	Segment t[Maxn * 4 + 5];
	void pushup(int rt) {
		Matrix t1 = t[rt << 1].val, t2 = t[rt << 1 | 1].val;
		if(t[rt << 1].w != 1) t1 = t1 * t[rt << 1].w;
		if(t[rt << 1 | 1].w != 1) t2 = t2 * t[rt << 1 | 1].w;
		t[rt].val = t1 + t2;
	}
	void pushdown(int rt) {
		if(t[rt].tag == One) return;
		t[rt << 1].tag = t[rt << 1].tag * t[rt].tag;
		t[rt << 1 | 1].tag = t[rt << 1 | 1].tag * t[rt].tag;
		t[rt << 1].val = t[rt << 1].val * t[rt].tag;
		t[rt << 1 | 1].val = t[rt << 1 | 1].val * t[rt].tag;
		t[rt].tag = One;
	}
	void build(int rt, int l, int r) {
		t[rt].w = (l != r);
		if(l == r) {
			t[rt].val = G;
			return;
		}
		int mid = (l + r) >> 1;
		build(rt << 1, l, mid), build(rt << 1 | 1, mid + 1, r);
		pushup(rt);
	}
	void insert(int rt, int l, int r, int pos) {
		if(l == r) {
			t[rt].w = mp[pos];
			return;
		}
		pushdown(rt);
		int mid = (l + r) >> 1;
		if(pos <= mid) {
			insert(rt << 1, l, mid, pos);
			t[rt << 1 | 1].val = t[rt << 1 | 1].val * G;
			t[rt << 1 | 1].tag = t[rt << 1 | 1].tag * G;
			//我也不知道为什么这个标记必须打了立即生效
			//其实我更喜欢打了延迟生效的方法
			//但这样好像过不了QAQ...
		} else insert(rt << 1 | 1, mid + 1, r, pos);
		pushup(rt);
	}
	void erase(int rt, int l, int r, int pos) {
		if(l == r) {
			t[rt].w = 0;
			return;
		}
		pushdown(rt);
		int mid = (l + r) >> 1;
		if(pos <= mid) {
			erase(rt << 1, l, mid, pos);
			t[rt << 1 | 1].val = t[rt << 1 | 1].val * invG;
			t[rt << 1 | 1].tag = t[rt << 1 | 1].tag * invG;
		} else erase(rt << 1 | 1, mid + 1, r, pos);
		pushup(rt);
	}
};

int cnt[Maxn + 5], siz;
int val[Maxn + 5], ans[Maxn + 5];
Query q[Maxn + 5];
SegmentTree tree;

void add(int pos) {
	if(!cnt[A[pos]]) tree.insert(1, 1, siz, A[pos]);
	cnt[A[pos]]++;
}
void del(int pos) {
	cnt[A[pos]]--;
	if(!cnt[A[pos]]) tree.erase(1, 1, siz, A[pos]);
}

#ifndef LOACL
#define getchar() (*(IOB.in.p++))
#define putchar(c) (*(IOB.out.p++) = (c))
#define io_eof() (IOB.in.p >= IOB.in.pend)
//输入输出挂,长度1KB  QAQ...
struct IOBUF {
	struct {
		char buff[1 << 27], *p, *pend;
	} in;
	struct {
		char buff[1 << 27], *p;
	} out;
	IOBUF() {
		in.p = in.buff, out.p = out.buff;
		in.pend = in.buff + fread(in.buff, 1, 1 << 27, stdin);
	}
	~IOBUF() {
		fwrite(out.buff, 1, out.p - out.buff, stdout);
	}
} IOB;
#endif
template <typename IO>
inline void write(IO val) {
	if(val == 0) {
		putchar('0');
		return;
	}
	if(val < 0) {
		putchar('-');
		val = -val;
	}
	static char buf[30];
	char *p = buf;
	while(val) {
		*(p++) = val % 10 + '0';
		val /= 10;
	}
	while(p > buf) putchar(*(--p));
}
inline void writestr(const char *s) {
	while(*s != 0) putchar(*(s++));
}
template <typename IO>
inline void writeln(IO val) {write(val), putchar('\n');}
template <typename IO>
inline void writesp(IO val) {write(val), putchar(' ');}
inline int readstr(char *s) {
	char *begin = s, c = getchar();
	while(c < 33 || c > 127)
		c = getchar();
	while(c >= 33 && c <= 127)
		*(s++) = c, c = getchar();
	*s = 0;
	return s - begin;
}
template <typename IO>
inline IO read() {
	IO ret = 0;
	register bool w = 0;
	register char c = getchar();
	while(c > '9' || c < '0') {
		if(c == '-') w = 1;
		c = getchar();
	}
	while(c >= '0' && c <='9') {
		ret = (ret << 3) + (ret << 1) + (c ^ 48);
		c = getchar();
	}
	return w ? -ret : ret;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	N = read<int>(), Mod = read<int>();
	for(register int i = 1; i <= N; i++)
		A[i] = val[i] = read<int>();
	
	block_size = sqrt(N);
	sort(val + 1, val + N + 1);
	siz = unique(val + 1, val + N + 1) - val - 1;
	for(register int i = 1; i <= N; i++) {
		int tmp = A[i];
		A[i] = lower_bound(val + 1, val + siz + 1, A[i]) - val;
		mp[A[i]] = tmp % Mod;
	}//注意离散化和取模
	for(register int i = 1; i <= N; i++)
		block[i] = (i - 1) / block_size + 1;
	
	Q = read<int>();
	for(register int i = 1; i <= Q; i++) {
		q[i].lef = read<int>(), q[i].rig = read<int>();
		q[i].id = i;
	}
	sort(q + 1, q + Q + 1, cmp_query);
	tree.build(1, 1, siz);
	int l = 1, r = 0;
	for(register int i = 1; i <= Q; i++) {
		while(l > q[i].lef) add(--l);
		while(r < q[i].rig) add(++r);
		while(l < q[i].lef) del(l++);
		while(r > q[i].rig) del(r--);
		ans[q[i].id] = tree.t[1].val[0][1] % Mod;
	}
	for(register int i = 1; i <= Q; i++)
		writeln((ans[i] % Mod + Mod) % Mod);
	//注意搞出来的数可能是个负数
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值