11.03T1 DP

#3874 naive 的瓶子

 

描述

众所周知,小 naive 有 n 个瓶子,它们在桌子上排成一排。第 i 个瓶子的颜色为 ci,每个瓶子都有灵性,每次操作可以选择两个相邻的瓶子,消耗他们颜色的数值乘积的代价将其中一个瓶子的颜色变成另一个瓶子的颜色。

现在 naive 要让所以瓶子的颜色都一样,操作次数不限,但要使得操作的总代价最小。

输入

输入文件为 colour.in。

一个测试点内多组数据。

第一行,一个正整数 T,表示数据组数。 每组数据内:

第一行一个整数 n,为瓶子的个数。

第二行共 n 个整数,第 i 个整数为第 i 个瓶子的颜色 ci。

输出

输入文件为 colour.out。

共一行,一个整数,为最小的总代价。

样例输入[复制]
1
4
7 4 6 10
样例输出[复制]
92
提示

样例解释

{7 4 6 10}− > {4 4 6 10}− > {4 4 4 10}− > {4 4 4 4}。

总代价为 7 × 4 + 4 × 6 + 4 × 10 = 92。

【数据范围】

1 ≤ T ≤ 10。

n<=300,ci<=10^5

标签
ZYH
 
 
 
方法

测试点 1-8


naive 的瓶子 (colour)


记忆化搜索,把当前的状态用 vector<int> 表示,用 map<vetor<int>, long long> 记录到达 vector<int> 的最小总代价,O(n) 枚举转移。设 m = min(n, max{ai}),时间复杂度为O(mn× n × m × log2m)。

测试点 9-20

首先我们可以枚举最后的颜色 sc

可以发现,一个瓶子要么直接被染成目标颜色,要么先被染成数值比较小的颜色,再被染成目标颜色。也就是说一个瓶子颜色的数值最多变小一次。这个结论十分显然。首先我们肯定是把一些颜色不为目标颜色的数值比较大的瓶子染成数值比较小颜色,但要确保还存在目标颜色,再最后把所有其他颜色染成目标颜色。在第一步中,一个瓶子颜色的数值肯定不会变大, 此外,也不会变小两次,假设他变小了两次,那么我们完全可以直接变小成第二次,这样肯定不会变劣。

所以我们就可以动态规划了,设 fi为把前 i 个瓶子染成目标颜色的最小代价,每次转移选择接下来的一个区间,先把他们染成这个区间中数值最小的颜色,再染成目标颜色。

但需要注意的是,在第一步中不能把所有颜色都染成非目标颜色,那么我们可以类似 fi的再做一遍 gi,为把 [i, n] 染成目标颜色的最小代价。那么最后的答案为 min{fi1+gi+1|ci= sc}

感觉这题的复杂度还可以优化,但是出题人太菜了。

 

 

 

code:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #define N 1000
 5 using namespace std;
 6 long long a[N],Min[N][N],dp[N][N],vis[N],f[N];
 7 int main(){
 8     long long T;
 9     cin>>T;
10     while(T--){
11         long long n;
12         cin>>n;
13         memset(a,0,sizeof a);
14         memset(Min,0,sizeof Min);
15         for(long long i=1;i<=n;i++)cin>>a[i];
16         memset(dp,0,sizeof dp);
17         for(long long i=1;i<=n;i++){
18             Min[i][i]=a[i];
19             dp[i][i]=0;
20             for(long long j=i+1;j<=n;j++){
21                 Min[i][j]=min(Min[i][j-1],a[j]);
22                 for(long long k=i;k<=j;k++){
23                     if(a[k]==Min[i][j])continue;
24                     dp[i][j]+=Min[i][j]*a[k];//calc val
25                 }
26             }
27         }
28         memset(vis,0,sizeof(vis));
29         long long min0=9999999999999;
30         for(long long i=1;i<=n;i++){//枚举我要变成的值 
31             if(vis[a[i]])continue;
32             vis[a[i]]=1;
33             long long now=a[i];
34             memset(f,0x3f3f3f3f,sizeof f);
35             f[0]=0;
36             for(long long j=1;j<=n;j++){//枚举我当前计算的f[j] 
37                 for(long long k=0;k<j;k++){
38                     f[j]=min(f[j],f[k]+dp[k+1][j]+(Min[k+1][j]!=now)*Min[k+1][j]*now*(j-k));
39                 } 
40             }
41             min0=min(min0,f[n]);
42         }
43         cout<<min0<<'\n';
44     }
45     return 0;
46 }

over

转载于:https://www.cnblogs.com/saionjisekai/p/9902149.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值