Time Limits: 2000 ms Memory Limits: 524288 KB
Description
众所周知,小 naive 有 n 个瓶子,它们在桌子上排成一排。第 i 个瓶子的颜色为 ci,每个瓶子都有灵性,每次操作可以选择两个相邻的瓶子,消耗他们颜色的数值乘积的代价将其中一个瓶子的颜色变成另一个瓶子的颜色。
现在 naive 要让所以瓶子的颜色都一样,操作次数不限,但要使得操作的总代价最小。
Input
输入文件为 colour.in。
一个测试点内多组数据。
第一行,一个正整数 T,表示数据组数。
每组数据内:
第一行一个整数 n,为瓶子的个数。
第二行共 n 个整数,第 i 个整数为第 i 个瓶子的颜色 ci。
Output
输入文件为 colour.out。
共一行,一个整数,为最小的总代价。
Sample Input
4
7 4 6 10
Sample Output
92
样例解释
{7 4 6 10}− > {4 4 6 10}− > {4 4 4 10}− > {4 4 4 4}。
总代价为 7 × 4 + 4 × 6 + 4 × 10 = 92。
Data Constraint
1 ≤ T ≤ 10。
对于测试点内的每组数据:
Solution
早上比赛发现了一个神奇的结论:瓶子要么直接被染成目标颜色,要么被染成一个较小的数值,再被染成目标颜色,然而我还是只有暴力分,见DP死选手……
首先,我们可以枚举最后的颜色,我们肯定是把一些颜色不为目标颜色的数值比较大的瓶子染成数值比较小颜色,但要确保还存在目标颜色,再最后把所有其他颜色染成目标颜色。在第一步中,一个瓶子颜色的数值肯定不会变大,此外,也不会变小两次,假设他变小了两次,那么我们完全可以直接变小成第二次,这样肯定不会变劣。
接下来,我们就可以DP了
设
h
[
l
]
[
r
]
h[l][r]
h[l][r] 表示
[
l
,
r
]
[l,r]
[l,r] 中的最小数
g
[
l
]
[
r
]
g[l][r]
g[l][r] 表示将
[
l
,
r
]
[l,r]
[l,r] 全部染成
h
[
l
]
[
r
]
h[l][r]
h[l][r] 的代价
f
[
i
]
f[i]
f[i] 为把前
i
i
i 个瓶子染成目标颜色的最小代价
每次转移选择接下来的一个区间,先把他们染成这个区间中数值最小的颜色,再染成目标颜色即可
Code
#include<algorithm>
#include<cstring>
#include<cstdio>
#define fo(i,a,b) for(ll i=a;i<=b;++i)
#define fd(i,a,b) for(ll i=a;i>=b;--i)
#define ll long long
using namespace std;
const ll N=310;
const ll MX=1e18;
ll T,n,a[N],b[N];
ll f[N],g[N][N],h[N][N];
void solve()
{
scanf("%lld",&n);
memset(h,127,sizeof(h));
fo(i,1,n)
{
scanf("%lld",&a[i]);
h[i][i]=b[i]=a[i];
}
sort(b+1,b+1+n);
ll cnt=unique(b+1,b+1+n)-b-1;
fd(l,n,1)
fo(r,l+1,n)
{
h[l][r]=min(h[l+1][r],h[l][r-1]);
ll mn=a[l],gs=1,s=a[l];
fo(i,l+1,r)
{
s+=a[i];
if(a[i]<mn) mn=a[i],gs=1;
else if(a[i]==mn) ++gs;
}
g[l][r]=(s-mn*gs)*mn;
}
ll ans=MX;
fo(i,1,cnt)
{
memset(f,127,sizeof(f));
if(a[1]==b[i]) f[1]=0; else f[1]=a[1]*b[i];
fo(en,2,n)
fo(r,1,en-1)
{
if(h[r+1][en]==b[i]) f[en]=min(f[en],f[r]+g[r+1][en]);
else f[en]=min(f[en],f[r]+g[r+1][en]+h[r+1][en]*b[i]*(en-r));
}
ans=min(ans,f[n]);
}
printf("%lld\n",ans);
}
int main()
{
freopen("colour.in","r",stdin);
freopen("colour.out","w",stdout);
scanf("%lld",&T);
while(T--) solve();
}