合并 union
Description
给出一个 1 ∼ N 的序列 A ( A 1 , A 2 , ..., A N ) 。你每次可以将两个相邻的元素合并,合并后的元素权值即为 这两个元素的权值之和。求将 A 变为一个非降序列,最少需要多少步操作。
Input
输入的第一行一个整数 N ( N ≤ 5000) 。
接下来一行 N 个整数,描述序列 A 。保证序列 A 中的每个元素的值不超过 1000 。
Output
输出一行一个整数,表示最少的操作数。
Sample Input
5
9 7 5 13 15
Sample Output
1
解析
这题很明显是用DP啊啊啊!!
我们设(f[i])表示将序列(1)~(i)合并的最小次数,
(g[i])表示将序列(1)~(i)合后的最后一个元素的权值,
那么,从(1)到(n)枚举(i),
再从(i)~(1)枚举(j),表示将$ j(~)i(合并成一个点,再添加到已经合并完后的序列) 1(~)j$-(1)后面,
什么?你问我为什么要将$ j(~)i$合并成一个点?
仔细想一下,如果有一个(k),并且将(j)~(k)合并,再将(k)+$1 (~)i$合并后会使答案更优,
那么在枚举到(k)时,就会将当前情况统计一次,
而在枚举(i)时,当(j)枚举到(k)时,就会统计到这个答案了!!(口胡证明可能有点乱,自己画图理解下哈).
然后,我们考虑状态转移,
如果(g[i-1])<=(a[i])-(a[j-1])((a)为前缀和,表示(i)到(j)合并后的权值,整个式子就表示(i)到(j)合并后能接到(j)-(1)后面).
并且(f[j-1])+((i-j))<=(f[i])(即次数更少,((i)-(j))表示将(j)~(i)合并成(1)个点的次数),
那么我们就更新(f[i])和(g[i]),(g[i])就是(j)~(i)合成的点的权值.
那么最后,(f[n])即为答案.
不清楚的看代码吧:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int sum=0,f=1;char ch=getchar();
while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0' && ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return f*sum;
}
int n,a[100001];
int f[100001],g[100001];
int main(){
n=read();
memset(f,0x3f,sizeof(f));f[0]=0;
for(int i=1;i<=n;i++) g[i]=read();//并没什么用但也没影响,只是存一下权值
for(int i=1;i<=n;i++) a[i]=g[i]+a[i-1];//前缀和
for(int i=1;i<=n;i++){
for(int j=i;j;j--){
if(g[j-1]<=a[i]-a[j-1]){
if(f[j-1]+i-j<f[i]){//更新
f[i]=f[j-1]+i-j;
g[i]=a[i]-a[j-1];
}
}
}
}
printf("%d
",f[n]);
return 0;
}