【jzoj 7186】【LOJ 3264】Poster / 海报(线段树)

Poster / 海报

题目链接:jzoj 7186 / LOJ 3264

题目大意

给你一个环,环上每个点有权值。
然后你可以选一些点,保证不能有超过四个在一起的点都被选到,问你能选的出的点的权值和的最大值。
然后会修改一些点的权值,每修改一次,都要重新求最大值。

思路

看到有修改,我们考虑用数据结构,然后就考虑用线段树。

根据不能有四个连在一起的都被选,我们搞一个东西,是保证中间合法,最后又两边已经连续选了多少个的东西给线段树。( n u m i , j num_{i,j} numi,j
不难看到它是 4 ∗ 4 4*4 44 的数组,然后你考虑假设求出来了,怎么求答案。
因为是一个环,那你还要保证连接的头尾也合法,那就枚举 i , j i,j i,j 使得 i + j ⩽ 3 i+j\leqslant3 i+j3,然后这些答案取最大值即可。

然后你考虑如何合并。
首先是普通的情况,你就考虑左边的右边连续,右边的左边连续,然后合再一起。
但是你会发现一个问题,就是可能它有一边整块都选中,那新的左边连续跟右边连续就不是那样的了。
那如果左边整块选中,是 j j j 个,那你右边左边是 i i i,那你左边的转移就是 n u m j , j num_{j,j} numj,j,得到的左边就是 j + i j+i j+i,右边也同理。
(当然也有两个都是整块选中,那就两个都这样搞就可以了)

然后有一个就是合并普通的合并用 4 4 4^4 44 会爆,你可以先枚举最左最右,然后中间的枚举一个,然后让另一个可以选的增加的过程中类似前缀记录下来,就可以优化到 4 3 4^3 43,就可以过了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#define LL long long

using namespace std;

struct node {
	int l, r, nm;
	LL num[4][4];
}t[160001];
int n, q, x;
LL a[40001];

void up(int now) {
	memset(t[now].num, 0, sizeof(t[now].num));
	t[now].nm = t[now << 1].nm + t[now << 1 | 1].nm; 
	
	int l = (now << 1), r = (now << 1 | 1);
	for (int i = 0; i <= min(3, t[l].nm); i++)
		for (int j = 0; j <= min(3, t[r].nm); j++) {
			if (i == t[l].nm && j == t[r].nm) {//都头尾碰到了
				if (i + j <= 3) t[now].num[i + j][i + j] = max(t[now].num[i + j][i + j], t[l].num[i][i] + t[r].num[j][j]);
				continue;
			}
			if (i == t[l].nm) {//有一个头尾碰到了
				for (int k = 0; i + k <= 3 && k <= t[r].nm; k++)
					t[now].num[i + k][j] = max(t[now].num[i + k][j], t[l].num[i][i] + t[r].num[k][j]);
				continue;
			}
			if (j == t[r].nm) {
				for (int k = 0; k + j <= 3 && k <= t[l].nm; k++)
					t[now].num[i][k + j] = max(t[now].num[i][k + j], t[l].num[i][k] + t[r].num[j][j]);
				continue;
			}
			
			LL sum = 0;
			for (int k = 3; k >= 0; k--) {
				sum = max(sum, t[r].num[3 - k][j]);//优化掉另一个枚举不然会超时
				t[now].num[i][j] = max(t[now].num[i][j], t[l].num[i][k] + sum);
			}
		}
}

void build(int now, int l, int r) {
	t[now].l = l; t[now].r = r;
	if (l == r) {
		t[now].nm = 1;
		t[now].num[1][1] = a[l];
		return ;
	}
	
	int mid = (l + r) >> 1;
	build(now << 1, l, mid);
	build(now << 1 | 1, mid + 1, r);
	
	up(now);
}

void insert(int now, int l, int r, int pl, LL val) {
	if (l == r) {
		t[now].num[1][1] = val;
		return ;
	}
	
	int mid = (l + r) >> 1;
	if (pl <= mid) insert(now << 1, l, mid, pl, val);
		else insert(now << 1 | 1, mid + 1, r, pl, val);
	
	up(now);
}

LL get_top() {
	LL re = 0;
	for (int i = 0; i <= 3; i++)
		for (int j = 0; i + j <= 3; j++)//限制是环的条件,两边加起来也不可以大于 3
			re = max(re, t[1].num[i][j]);
	return re;
}

int main() {
	scanf("%d", &n);
	
	for (int i = 1; i <= n; i++)
		scanf("%lld", &a[i]);
	build(1, 1, n);
	
	scanf("%d", &q);
	printf("%lld\n", get_top());
	while (q--) {
		scanf("%d", &x);
		scanf("%lld", &a[x]);
		insert(1, 1, n, x, a[x]);
		printf("%lld\n", get_top());
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值