1. 程式人生 > 其它 >【PR #1 A】刪數(DP)

【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;
}