【PR #1 A】删数(DP)

删数

题目链接:PR #1 A

题目大意

给你一个数组,每次你可以选择一个满足条件的位置(条件是他旁边两个数的平均数是它,边界不可能满足),把它删去。
然后问你这个数组在若干次操作之后最短可以有多段。

思路

首先看到这个条件自然就是差分一下。
然后就会发现操作变成了把差分数组两个相邻相同的数合并得到它们的和。

然后不难看出一个数合并不会超过 \(\log\) 值域次。(除了 \(0\),这个特判)
然后你会发现你可以把数分类,不同类之间是绝对不可能合并到的。
然后同一类的就是正负相同而且把低位的 \(0\) 去掉(方法是 \(/lowbit(x)\))是一样的。

那我们就可以把差分数组按这个分段,每段都是一样的,然后逐个解决。
首先特判 \(0\),那最后就合剩一个 \(0\),所以留下一个。
然后别的我们考虑 DP,设 \(f_i\) 为搞前 \(i\) 个的答案,然后我们考虑每次直接放入合并的一块转移。
那我们就要预处理出 \(g_{i,j}\)\(i\) 为右端点搞出 \(2^j\) 大小的话左端点在哪里(如果没有就是 \(0\)
然后转移即可。

然后最后还原乘原数组,长度 \(+1\),所以输出答案加一。

代码

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N = 3e5 + 100;
int n, a[N], ans, f[N], g[N][41];

int ops(int x) {
	if (!x) return 0;
	return x / (x & -x);
}

int get_ji(int x) {
	int re = 0; while (!(x & 1)) re++, x >>= 1;
	return re;
}

int clac(int l, int r) {
	for (int i = l; i <= r; i++) g[i][get_ji(a[i] & -a[i])] = i;
	for (int i = 1; i <= 40; i++)
		for (int j = l; j <= r; j++) {
			if (g[j][i - 1] > l && g[g[j][i - 1] - 1][i - 1] >= l) {
				g[j][i] = g[g[j][i - 1] - 1][i - 1]; 
			}
		}
	f[l - 1] = 0;
	for (int i = l; i <= r; i++) {
		f[i] = 2e9;
		for (int j = 0; j <= 40; j++) if (g[i][j] >= l) f[i] = min(f[i], f[g[i][j] - 1] + 1);
	}
	return f[r];
}

int main() {
	int T; scanf("%d", &T);
	while (T--) {
		scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
		for (int i = 1; i < n; i++) a[i] = a[i + 1] - a[i];
		int ans = 0;
		for (int L = 1, R; L < n; L = R + 1) {
			R = L; while (R < n - 1 && ops(a[R]) == ops(a[R + 1])) R++;
			if (ops(a[R])) ans += clac(L, R);
				else ans += 1;
		}
		printf("%d\n", ans + 1);
		
		for (int i = 1; i < n; i++) for (int j = 0; j <= 40; j++) g[i][j] = 0;
	}
	
	return 0;
}
posted @ 2022-04-09 10:59  あおいSakura  阅读(74)  评论(0编辑  收藏  举报