题目
题意概要
一维线段上的
2048
\tt 2048
2048 游戏:每次两个相邻
x
x
x 可以合并成
x
+
1
x+1
x+1 。求最少能剩下几个数字。
数据范围与提示
n
≤
500
,
1
≤
a
i
≤
1000
n\le 500,1\le a_i\le 1000
n≤500,1≤ai≤1000 。
思路
初始思路
考虑到数字相对顺序不改变,应该区间 d p \tt dp dp 可做。 f ( l , r ) f(l,r) f(l,r) 表示这些数字合并起来,最少剩下几个数字。
转移? 😂 好像还需要把别的什么奇怪的东西放到状态里才行?线段树?也不行鸭。 😭
正解思路
陷入了思维定式,从一开始就想着一次 d p \tt dp dp 解决。而事实上,我们能做的是这样一件事:求出一个区间能不能变成一个数。这个是容易转移的。复杂度 O ( n 3 ) \mathcal O(n^3) O(n3) 。
求出这个之后,我们再做一次 d p \tt dp dp ,表示前 i i i 个数字最少变成几个数字,然后考虑最后一个数字是由哪个区间凑出来的。复杂度 O ( n 2 ) \mathcal O(n^2) O(n2) 。然后就木有了。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
template < class T >
void getMax(T&a,T b){ if(a < b) a = b; }
template < class T >
void getMin(T&a,T b){ if(b < a) a = b; }
const int MaxN = 502;
int dp[MaxN][MaxN], f[MaxN];
int main(){
int n = readint();
for(int i=1; i<=n; ++i)
dp[i][i] = readint();
for(int len=1; len<n; ++len)
for(int i=1; i+len<=n; ++i)
for(int j=i; j<i+len&&!dp[i][i+len]; ++j)
if(dp[i][j] == dp[j+1][i+len])
if(dp[i][j] != 0) // 不是俩乱码
dp[i][i+len] = dp[i][j]+1;
for(int i=1; i<=n; ++i){
f[i] = MaxN;
for(int j=1; j<=i; ++j)
if(dp[j][i] != 0)
getMin(f[i],f[j-1]+1);
}
printf("%d\n",f[n]);
return 0;
}