题目大意:lee要把一串珠子涂成目标颜色,给一段珠子染色的代价是这一段珠子中珠子种类数的平方,求怎么染色才能花费最小
我的思路:开始先想到用贪心,发现情况太多了,贪不了。然后想到用dp考虑所有情况,看上去可行,但是t了,我先来讲讲我的思路,看看大家有没有什么优化的方案
首先用双指针预处理出来b[][]数组,b[i][j]表示i到b[i][j]中恰好包含j种珠子。当然j<=根号m(珠子总数),因为如果>根号m了,还不如一个一个选划得来预处理完后,就开始dp了。
用dp[i]表示以第i颗珠子结尾的最小花费是多少。
状态转移方程是:dp[i]=min(dp[i],dp[b[i][j]-1]+j*j)
尝试从b[i][j](0<j<根号i)这个点来更新i号点,不重不漏考虑所有情况,时间复杂度m根号m
但是t了,可能是常数大了
tle代码如下:
#include<bits/stdc++.h> using namespace std; #define sf(x) scanf("%d",&x) #define sff(x,y) scanf("%lld%lld",&x,&y) //#define endl '\n' #define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) #define pf(x) printf("%lld",x) #define pii pair<int,int> #define f first #define s second //#define int long long // const int N=5e4+10,M=230; int m; int a[N]; int b[N][M]; int dp[N]; int sum[N]; vector<int> ve; // int erfen(int x) { int l=-1,r=ve.size(); while(l+1<r) { int mid =l+r>>1; if(ve[mid]<=x) l=mid; else r=mid; } return l; } // void solve() { while(~sf(m)) { ve.clear(); for(int i=1;i<=m;i++){ // cin>>a[i]; sf(a[i]); ve.push_back(a[i]); dp[i]=0x3f3f3f3f; for(int j=1;j*j<=m;j++) b[i][j]=0; } //离散化 sort(ve.begin(),ve.end()); ve.erase(unique(ve.begin(),ve.end()),ve.end()); for(int i=1;i<=m;i++)sum[i-1]=0, a[i]=erfen(a[i]); //预处理 int cnt=0; for(int i=1;i*i<=m;i++) { for(int l=1,r=0;l<=m;l++) { while(r<=m&&cnt<=i){ if(cnt==i){ b[r][i]=l; } int x=a[++r]; if(!sum[x]) cnt++; sum[x]++; } if(!(--sum[a[l]])) cnt--; } } //dp for(int i=1;i<=m;i++){ dp[i]=dp[i-1]+1; for(int j=1;j*j<=i;j++){ if(b[i][j]) dp[i]=min(dp[i],dp[b[i][j]-1]+j*j);//cout<<i<<" "<<j<<" "<<b[i][j]<<endl; } } printf("%d\n",dp[m]); } } signed main() { // IOS; int _=1; // cin>>_;23 while(_--) solve(); return 0; }
后来喵了眼题解,发现可以用一个双链表代替预处哩,而且最快时间复杂度可以到o(m),但是极端情况也要到m根号m,题目也没说有多少组,这就很头疼。
怎么用双链表解决呢,见代码注释
ac代码:#include<bits/stdc++.h> using namespace std; #define sf(x) scanf("%d",&x) #define sff(x,y) scanf("%d%d",&x,&y) #define endl '\n' #define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) #define pf(x) printf("%lld",x) #define pii pair<int,int> #define f first #define s second //#define int long long // const int N=5e4+10; int l[N],r[N],nel[N],ner[N],e[N]; int m,lr,rr,idx; int a[N],dp[N]; // void init()//初始化,双链表 { lr=1,rr=2; ner[lr]=2,nel[rr]=1; idx=3; } void insert_r(int x)//从右根节点插入元素 { ner[idx]=rr; nel[idx]=nel[rr]; e[idx]=x; ner[nel[rr]]=idx; nel[rr]=idx++; } void delate(int x)//删除编号为x的点 { ner[nel[x]]=ner[x]; nel[ner[x]]=nel[x]; } // void solve() { while(~sf(m)){ map<int,int> mp;//mp存已经出现过的点 init(); for(int i=1;i<=m;i++) { sf(a[i]); dp[i]=0x3f3f3f3f; insert_r(i); //先全部插入插入双链表 } dp[1]=1; for(int i=1;i<=m;i++) { int id=i+2;//每个数的编号是下表+2,因为idx初始为3 if(!mp.count(a[i])) mp[a[i]]=id;//如果该元素还没有出现过,则记录他的下标,顺便维护该元素已经出现过了 else{ delate(mp[a[i]]);//否现删除之前的遍号,保留现在的编号,每种珠子只保留最右边的,因为左边重复的对i的种类数不会有任何影响,大家可以举个例子试一下 mp[a[i]]=id; } int cnt=0; for(int k=nel[id];k!=0;k=nel[k]) { cnt++; int j=e[k];//cnt表示j到k之间有多少种珠子,不包括j这号点 dp[i]=min(dp[i],dp[j]+cnt*cnt);//更新方式同理 if(cnt*cnt>k) break;//优化方式同理 } } printf("%d\n",dp[m]); } } signed main() { // IOS; int _=1; // cin>>_; while(_--) solve(); return 0; }
谢谢大家