删数
今天的题又不会写,所以来写题解总结一下
题意
给你一个数组,每次如果ai=(ai+1+ai?1)/2a_i=(a_{i+1}+a_{i-1})/2ai?=(ai+1?+ai?1?)/2,那么就可以删除aia_iai?。问这个数组经过一系列删除后最短长度是多少?
思路
注意到,能删除的数字是等差数列中间的那个数字,所以我们可以构造差分数组d[i]=a[i+1]=a[i]d[i] = a[i + 1] = a[i]d[i]=a[i+1]=a[i],删除a[i]a[i]a[i]后,新的d[i]d[i]d[i]刚好是旧d[i]d[i]d[i]的两倍,即等价于合并两个相同的d[i]d[i]d[i]。
所以,这个问题就转化为,给你一个数组ddd,每次可以合并相邻且值相同的两个值,问这个数组最后能剩下最少的数字是多少?
因为每次合并都是连续的区间,而且对于d[i]d[i]d[i]所在合并后的区间,其值必定为d[i]?2k(k=0,1,...)d[i]*2^k (k=0,1,...)d[i]?2k(k=0,1,...)。既然到了这里,不妨再进一步处理,把2的因子全部提取出来。
有了上面这个观察,我们再考虑DP,记dp[i]dp[i]dp[i]为合并前i个数字,最后得到最少的数字。对于每个d[i]d[i]d[i],我们只需要包含它,且和为d[i]?2kd[i]*2^kd[i]?2k的区间即可。所以,我们还缺一个数组f[i][j]f[i][j]f[i][j],表示以i开头,和为d[i]?2kd[i]*2^kd[i]?2k的区间最右边是哪个位置。
到了这一步,很明显f[i][j]f[i][j]f[i][j]可以用倍增的思想处理出来,即f[i][j]=f[f[i][j?1][j?1]]f[i][j] = f[f[i][j-1][j - 1]]f[i][j]=f[f[i][j?1][j?1]]。另外,要注意一点,如果d[i]d[i]d[i]不相等,那么f[i][j?1]f[i][j-1]f[i][j?1]不能和f[f[i][j?1][j?1]]f[f[i][j-1][j - 1]]f[f[i][j?1][j?1]]这个位置合并,而且如果d[i]==0d[i]==0d[i]==0的话,那么可以直接无视其他的条件进行合并。具体看代码。
最后就是进行dp过程了,因为fff是考虑右边的位置,所以dp含义这里改变一下,dp[i]dp[i]dp[i]表示从iii开始到n,合并后可以得到的最少数字
- 状态转移 dp[i]=dp[f[i][j]]+1dp[i] = dp[f[i][j]] + 1dp[i]=dp[f[i][j]]+1
- 因为我们合并的是差分数组,所以最后答案为dp[1]+1dp[1]+1dp[1]+1
- 剩余一些细节可以参考代码
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long LL;
using tp = tuple<int,int,int>;
typedef pair<int,int> PII;
const int N = 3e5 + 10, M = 3010, mod = 998244353, INF = 1e9;
bool multi = true;int n;
int d[N], a[N], cnt[N];
//第i个位置,合并变成d[i]*2^k,到什么地方
int f[N][55], dp[N];
void solve() {
scanf("%d", &n);for(int i = 1; i <= n; i++) scanf("%d", &a[i]);for(int i = 1; i <= n; i++)for(int j = 0; j <= 50; j++) f[i][j] = -1;n--;for(int i = 1; i <= n; i++) {
d[i] = a[i + 1] - a[i];cnt[i] = 0, dp[i] = INF;while(d[i] && d[i] % 2 == 0) {
cnt[i]++;d[i] /= 2;}}for(int i = n; i >= 1; i--) {
f[i][cnt[i]] = i + 1;for(int j = 1; cnt[i] + j <= 50; j++) {
if(f[i][cnt[i] + j - 1] > n) break;if(f[i][cnt[i] + j - 1] == -1) break;if(d[i] != d[f[i][cnt[i] + j - 1]]) break;f[i][cnt[i] + j] = f[f[i][cnt[i] + j - 1]][cnt[i] + j - 1];}}dp[n + 1] = 0;//i~n,合并后能剩下的最少区间数量for(int i = n; i >= 1; i--) {
if(i + 1 <= n && !d[i] && !d[i + 1]) dp[i] = dp[i + 1];else {
for(int j = 0; j <= 50; j++) {
if(f[i][j] == -1) continue;dp[i] = min(dp[i], dp[f[i][j]] + 1);}}}cout << dp[1] + 1 << endl;}int main() {
int T = 1;if(multi) cin >> T;while (T--) solve();return 0;
}