1. 程式人生 > >JZOJ 5925. 【NOIP2018模擬10.25】naive 的瓶子

JZOJ 5925. 【NOIP2018模擬10.25】naive 的瓶子

題目

有 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] 表示 [
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; }```