[GKOI 2021 提高組]抄寫
抄寫copy
題目大意
對於 \(26\) 個英文字母,每個英文字母對應一個權值,對於任意小寫字母 \(ch\) ,我們稱抄寫它的花費為 \(cost_{ch}\) 。
當然,你有一種節約時間的方法,你可以花費時間 \(c\) ,將之前抄寫的字元對稱到另一邊,即如下兩種操作:
-
操作一,在 \(s_m\) 後對稱,即使得抄寫進度變為 \(s_1s_2……s_ms_ms_{m-1}……s_k,1\leq k \leq m\)
-
操作一,在 \(s_m\) 處對稱,即使得抄寫進度變為 \(s_1s_2……s_ms_{m-1}……s_k,1\leq k < m\)
現在給你一個長度為 \(n\)
分析
暴力解法
首先,這道題的 \(n^2\) 做法並不難想到,我們設 \(dp[i]\) 表示抄寫前 \(i\) 個字元的最小花費,顯然, \(dp[i]\) 有兩種被更新的方式。
\[dp[i]=min(dp[i-1]+cost[s[i]-'a'+1],dp[k]+c) \]很顯然,這個 \(k\) 即我們的對稱點,如果不加優化的話,我們只能通過列舉去找,且判斷是否對稱又是一維,這樣我們的時間複雜度顯然是不優秀的。
我們考慮在每個點的時候更新後面的點,就是以當前更新到的點作為題面中的 \(s_m\) ,左右兩邊同時向外判斷,如果一直相同,就更新即可。
這樣我們的時間複雜度能夠達到 \(n^2\)
然後這題其實還有一點性質分,二分的做法很容易想到,這裡就不贅述了。
#include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,INF=1e10; inline int read() { int s=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') w*=-1; ch=getchar(); } while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar(); return s*w; } int n,c; int f[N],cost[N]; char s[N]; inline int find(int l,int r,char x) { int res=-1; while(l<=r){ int mid=(l+r)/2; if(s[mid]!=x) l=mid+1; else res=mid,r=mid-1; } return res; } //目測60分 signed main() { // freopen("copy.in","r",stdin); // freopen("copy.out","w",stdout); n=read(),c=read(); for(register int i=1;i<=26;i++) cost[i]=read(); for(register int i=1;i<=n;i++) s[i]=getchar(); for(register int i=1;i<=n;i++) f[i]=INF; f[0]=0; if(n<=1000){ //40分 for(register int i=1;i<=n;i++){ //cout<<i<<"\n"; f[i]=min(f[i-1]+cost[s[i]-'a'+1],f[i]); int l=i,r=i+1; while(l>=1&&r<=n){ //操作1 if(s[l]==s[r]){ //能夠關於其對稱 f[r]=min(f[r],f[i]+c); l--,r++; } else break; } l=i-1,r=i+1; while(l>=1&&r<=n){ //操作2 if(s[l]==s[r]){ //能夠關於其對稱 f[r]=min(f[r],f[i]+c); l--,r++; } else break; } } printf("%lld\n",f[n]); } else{ //性質分20分 for(register int i=1;i<=n;i++){ //cout<<i<<"\n"; int k=find(1,i-1,s[i]); if(k==-1) f[i]=f[i-1]+cost[s[i]-'a'+1]; else{ int mid=(i-k+1)/2; if((i-k+1)&1) mid+=1; f[i]=min(f[i-1]+cost[s[i]-'a'+1],f[k+mid-1]+c); } } printf("%lld\n",f[n]); } return 0; }
正解(hash+二分+線段樹)
這是一種 \(nlogn\) 的解法,擴充套件 \(kmp\) 和 \(manacher\) 等演算法也能做到。
想想怎麼優化我們的過程,通過列舉去找到最大的邊界然後一個一個更新,還是太暴力了。對於這種區間更新,線段樹是比較顯然的一種想法,但是我們如何在優秀的時間複雜度內找到迴文半徑。
我們可以把原來的字串正著 \(hash\) 一遍,倒著 \(hash\) 一遍,在二分迴文半徑即可。
這樣做的時間是 \(nlogn\) 。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,INF=1e18;
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') w*=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
int n,c;
int a[N],f[N],dp[N],tree[4*N]; //f[i]記錄第i個點通過迴文能夠更新到的點的最遠位置
char s[N];
unsigned long long p[N],hash1[N],hash2[N];
inline void build(int k,int l,int r)
{
if(l==r) { tree[k]=INF; return; }
int mid=(l+r)/2;
build(k*2,l,mid),build(k*2+1,mid+1,r);
tree[k]=min(tree[k*2],tree[k*2+1]);
}
inline void change(int k,int l,int r,int x,int y,int v)
{
if(l>y||r<x) return;
if(l>=x&&r<=y) { tree[k]=min(tree[k],v); return; }
int mid=(l+r)/2;
change(k*2,l,mid,x,y,v),change(k*2+1,mid+1,r,x,y,v);
}
inline int ask(int k,int l,int r,int x)
{
if(r<x||l>x) return INF;
if(l==r) return tree[k];
int mid=(l+r)/2;
return min(tree[k],min(ask(k*2,l,mid,x),ask(k*2+1,mid+1,r,x)));
}
signed main()
{
n=read(),c=read();
for(register int i=1;i<=26;i++) a[i]=read();
scanf("%s",s+1);
p[0]=1;
for(register int i=1;i<=n;i++){
hash1[i]=hash1[i-1]*131+(s[i]-'a'+1);
hash2[n-i+1]=hash2[n-i+2]*131+(s[n-i+1]-'a'+1);
p[i]=p[i-1]*131;
}
// for(register int i=1;i<=n;i++) cout<<hash2[i]<<" ";
// puts("");
for(register int i=1;i<=n;i++){
int l=0,r=min(i-1,n-i),res=-1;
while(l<=r){ //奇數
int mid=(l+r)/2;
if(hash1[i]-hash1[i-mid-1]*p[mid+1]==hash2[i]-hash2[i+mid+1]*p[mid+1]) res=mid,l=mid+1;
else r=mid-1;
}
if(res!=-1) f[i]=max(f[i],i+res);
l=0,r=min(i-1,n-i-1),res=-1;
while(l<=r){ //偶數
int mid=(l+r)/2;
if(hash1[i]-hash1[i-mid-1]*p[mid+1]==hash2[i+1]-hash2[i+mid+2]*p[mid+1]) res=mid,l=mid+1;
else r=mid-1;
}
if(res!=-1) f[i]=max(f[i],i+res+1);
}
build(1,1,n);
for(register int i=1;i<=n;i++){
dp[i]=min(dp[i-1]+a[s[i]-'a'+1],ask(1,1,n,i));
if(i+1>f[i]) continue;
else change(1,1,n,i+1,f[i],dp[i]+c);
}
printf("%lld\n",dp[n]);
return 0;
}
作者: ╰⋛⋋⊱๑落葉๑⊰⋌⋚╯
-------------------------------------------
海到無邊天作岸,山登絕頂我為峰!