线段树维护矩阵转移

题目: CF New Year and Old Subsequence

题意:给定一个字符串,询问某一个区间内至少需要删除多少个字母,使得剩下的数字含有2017,但不含有2016。

思路
定义5个状态

0:空状态,连2都没有
1:只有2,没有0连在后面
2: 只有20,没有1在后面
3: 只有201,没有7在后面
4:完整的2017

状态转移

对于任意两个相邻的连续的区间之间的状态的转移,我们可以借助一个状态作为中间变量进行转移,对于状态a->b,我们可以借助中间状态K进行转移,a->k->b, k的取值范围在[0,4]。最后取最小的一个值

下面的转移过程和矩阵乘法的过程很像,于是考虑用矩阵乘法去维护,*变+,取最小

node operator+(node A) {
		node ans=node();
		for (int i = 0; i < 5; ++i) {
			for (int j = 0; j < 5; ++j) {
				for (int k = 0; k < 5; ++k) {
					ans.a[i][j] = min(ans.a[i][j], a[i][k] + A.a[k][j]);;
				}
			}
		}
		return ans;
	}

初始化
对于每一数字单独形成的叶子节点来说。

node() { memset(a, 0x3f, sizeof(a)); }
	void init(int x) {
		for (int i = 0; i < 5; i++)a[i][i] = 0;
		switch(x)
		{
		case 2: {a[0][0] = 1, a[0][1] = 0; break; }
		case 0: {a[1][1] = 1, a[1][2] = 0; break; }
		case 1: {a[2][2] = 1, a[2][3] = 0; break; }
		case 7: {a[3][3] = 1; a[3][4] = 0; break; }
		case 6: {a[3][3] = 1; a[4][4] = 1; break; }
		}
	}

如果出现2,那么0到0的转移就是1(0是空状态,需要删去2),0到1为0(从无到2,刚好需要一个,正好有,因此不用删除)。
其它的转移类似。
但如果出现的是数字6,这个是根据题目要求6是不能出现在201的后面的,故3到3(201)的转移需要减去这个6,4到4(2017)也需要减去。

接下就可以敲代码了,用线段树去维护这个合并过程,每个节点维护这个区间内的转移状态

AC—CODE

#include<bits/stdc++.h>
using namespace std;
const int N = 200 * 1000 + 10;
int n, q;
char s[N];
 
struct node {
	int a[5][5];
	node() { memset(a, 0x3f, sizeof(a)); }
	void init(int x) {
		for (int i = 0; i < 5; i++)a[i][i] = 0;
		switch(x)
		{
		case 2: {a[0][0] = 1, a[0][1] = 0; break; }
		case 0: {a[1][1] = 1, a[1][2] = 0; break; }
		case 1: {a[2][2] = 1, a[2][3] = 0; break; }
		case 7: {a[3][3] = 1; a[3][4] = 0; break; }
		case 6: {a[3][3] = 1; a[4][4] = 1; break; }
		}
	}
	node operator+(node A) {
		node ans=node();
		for (int i = 0; i < 5; ++i) {
			for (int j = 0; j < 5; ++j) {
				for (int k = 0; k < 5; ++k) {
					ans.a[i][j] = min(ans.a[i][j], a[i][k] + A.a[k][j]);;
				}
			}
		}
		return ans;
	}
}T[N<<2];
 
void build(int rt, int l, int r) {
	if (l == r) {
		T[rt].init(s[l]-'0');
		return;
	}
	int mid = l + r >> 1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
 
	T[rt] = T[rt << 1] + T[rt << 1 | 1];
}
 
node query(int rt,int l,int r,int ql,int qr){
	if (l>=ql && r<=qr) return T[rt];
	int mid = l + r >> 1;
	if (qr <= mid)return query(rt<<1,l,mid,ql,qr);
	if (ql > mid)return query(rt << 1 | 1, mid + 1, r, ql, qr);
	return query(rt << 1, l, mid, ql, qr) + query(rt << 1 | 1, mid + 1, r, ql, qr);
}
 
int main() {
	scanf("%d%d",&n,&q);
	scanf("%s",s+1);
	build(1, 1, n);
	while (q--) {
		int l, r;
		scanf("%d%d",&l,&r);
		int ans = query(1, 1, n, l, r).a[0][4];
		printf("%d\n",ans==0x3f3f3f3f?-1:ans);
	}
	return 0;
}

另一个题目:2019 南昌网络赛 C. Hello 2019

这题其实和上面是一样的,就是把变成了2019和2018,并且反过来了。

那么我们只需要把所给的字符串反转一下,查询的区间也反转一下就可以了。

#include<bits/stdc++.h>
using namespace std;
const int N = 200 * 1000 + 10;
int n, q;
char s[N];

struct node {
	int a[5][5];
	node() { memset(a, 0x3f, sizeof(a)); }
	void init(int x) {
		for (int i = 0; i < 5; i++)a[i][i] = 0;
		switch(x)
		{
		case 2: {a[0][0] = 1, a[0][1] = 0; break; }
		case 0: {a[1][1] = 1, a[1][2] = 0; break; }
		case 1: {a[2][2] = 1, a[2][3] = 0; break; }
		case 9: {a[3][3] = 1; a[3][4] = 0; break; }
		case 8: {a[3][3] = 1; a[4][4] = 1; break; }
		}
	}
	node operator+(node A) {
		node ans=node();
		for (int i = 0; i < 5; ++i) {
			for (int j = 0; j < 5; ++j) {
				for (int k = 0; k < 5; ++k) {
					ans.a[i][j] = min(ans.a[i][j], a[i][k] + A.a[k][j]);;
				}
			}
		}
		return ans;
	}
}T[N<<2];

void build(int rt, int l, int r) {
	if (l == r) {
		T[rt].init(s[l]-'0');
		return;
	}
	int mid = l + r >> 1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);

	T[rt] = T[rt << 1] + T[rt << 1 | 1];
}

node query(int rt,int l,int r,int ql,int qr){
	if (l>=ql && r<=qr) return T[rt];
	int mid = l + r >> 1;
	if (qr <= mid)return query(rt<<1,l,mid,ql,qr);
	if (ql > mid)return query(rt << 1 | 1, mid + 1, r, ql, qr);
	return query(rt << 1, l, mid, ql, qr) + query(rt << 1 | 1, mid + 1, r, ql, qr);
}

int main() {
	scanf("%d%d",&n,&q);
	scanf("%s",s+1);
	reverse(s+1,s+n+1);
	build(1, 1, n);
	while (q--) {
		int l, r;
		scanf("%d%d",&l,&r);
		int ans = query(1, 1, n, n-r+1, n-l+1).a[0][4];
		printf("%d\n",ans==0x3f3f3f3f?-1:ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值