题目
有 n 个瓶子,它们在桌子上排成一排。第 i 个瓶子的颜色为 ci,每次操作可以选择两个相邻的瓶子,消耗他们颜色的数值乘积的代价将其中一个瓶子的颜色变成另一个瓶子的颜色。 现在要让所以瓶子的颜色都一样,操作次数不限,但要使得操作的总代价最小。
题解
比赛时看错题了,看成了相邻两个合并,最后只剩下一个瓶子… 突破口,最后剩下的瓶子只有一种颜色。 设瓶子最后的颜色为m。 考虑一种似乎可行的方法:尽量让ci=m的不要动。 每个ci不等于m的连续区间,要么直接变成m,要么先变成该区间的最小的颜色,然后再变成m。 灵感:在拍一个数据的时候想到。 2 2 2 1 2 2答案显然为2。 似乎是对的。但是这个反例下就错了。 2 2 2 1e5 1 2 1e5 2 2 2 结果错了。没有举特殊的反例。 惨痛的教训。 正解: 仍然设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
[
i
,
j
]
[i,j]
[i,j]区间内,全部变成该区间内最小的所需要的代价。 直接转移即可。枚举最后一段区间:全部变成该区间内最小的,然后再变成m。 枚举m,顺着做一遍,倒着做一遍即可。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define LL long long
#define N 305
#define M 100010
using namespace std
;
LL g
[N
][N
],f
[N
][N
],c
[N
],ans
,sum
,mx
,s1
,w
;
LL F
[N
],G
[N
];
int hd
[M
],nxt
[M
],b
[M
],cnt
[M
];
int i
,j
,k
,l
,n
,T
,len
;
int main(){
scanf("%d",&T
);
while(T
--){
memset(b
,0,sizeof(b
));
memset(f
,0,sizeof(f
));
memset(g
,0,sizeof(g
));
memset(F
,0,sizeof(F
));
memset(G
,0,sizeof(G
));
memset(nxt
,0,sizeof(nxt
));
memset(hd
,0,sizeof(hd
));
memset(cnt
,0,sizeof(cnt
));
scanf("%d",&n
);
fo(i
,1,n
){
scanf("%lld",&c
[i
]);
g
[i
][i
]=c
[i
];
f
[i
][i
]=0;
if(b
[c
[i
]])nxt
[b
[c
[i
]]]=i
;
else hd
[c
[i
]]=i
;
b
[c
[i
]]=i
;
cnt
[c
[i
]]++;
}
fo(i
,1,n
)if(!nxt
[i
])nxt
[i
]=n
+1;
fo(i
,1,n
)fo(j
,i
+1,n
)g
[i
][j
]=min(g
[i
][j
-1],c
[j
]);
fo(i
,1,n
){
fo(j
,i
+1,n
){
fo(k
,i
,j
){
if(g
[i
][j
]!=c
[k
])f
[i
][j
]=f
[i
][j
]+g
[i
][j
]*c
[k
];
}
}
f
[i
][n
+1]=f
[i
][n
];
}
ans
=100000000000000000;
fo(i
,1,100000)if(cnt
[i
]){
F
[0]=G
[n
+1]=0;
fo(j
,1,n
){
F
[j
]=100000000000000000;
fo(k
,0,j
-1){
w
=0;
if(g
[k
+1][j
]!=i
)w
=g
[k
+1][j
]*i
*(j
-k
);
F
[j
]=min(F
[j
],F
[k
]+f
[k
+1][j
]+w
);
}
}
fd(j
,n
,1){
G
[j
]=100000000000000000;
fo(k
,j
,n
){
w
=0;
if(g
[j
][k
]!=i
)w
=g
[j
][k
]*i
*(k
-j
+1);
G
[j
]=min(G
[j
],G
[k
+1]+f
[j
][k
]+w
);
}
}
j
=hd
[i
];
while(j
^(n
+1)){
ans
=min(ans
,F
[j
-1]+G
[j
+1]);
j
=nxt
[j
];
}
}
printf("%lld\n",ans
);
}
return 0;
}```