0x57~0x59
目錄
- 0x57~0x59
- 0x57 倍增優化DP
- 0x58 數據結構優化DP
- 0x59 單調隊列優化DP
0x57~0x59
0x57 倍增優化DP
a.[√] 開車旅行
題目傳送
吐槽:
這道題是真的E心。。題面看了不知道多少遍吧,開始一直沒看懂,然后怒寫4k代碼,幾經絕望,,,亂搞過了
sol:
第一個首先要預處理出每一個城市的最近的城市和次近的城市,分別記為M1[],M2[]。
這個可以用鏈表解決,稍微表述一下:
首先把所有城市從小到大排一個序,把其建為一個鏈表。
然后把城市1~n在鏈表中的對應位置記錄下來。
從1~n依次考慮每一個城市的最近和次近城市,
假設算到了i,其對應位置link[i],那么只需考慮link[i-1],link[i-2],link[i+1],link[i+2]即可。
最后算完i把i在鏈表中刪掉即可。
其中有一些細節和需要注意的點,比如“本題中如果當前城市到兩個城市的距離相同,則認為離海拔低的那個城市更近”,“超出n的范圍”(后面同樣需要注意)等,反正我是無腦怒剛了30行,,,
第二個的話需要考慮如何通過DP得到想要的信息。
首先如果已知出發城市,行車天數,誰先開車,就能得到結束城市。容易想到設\(f[i,j,0/1]\)表示這一結果。
但是由于ij都與n同階的,所以用倍增優化一下(后同),即上式表示從j出發,a/b先開車,開\(2^i\)天后到達的城市。
那么,
\[ f[0,j,0]=M2[j]\ \ f[0,j,1]=M1[j]\\ f[1,j,k]=f[0,f[0,j,k],k\ xor\ 1] \ (k\in[0,1])\\ f[i,j,k]=f[i-1,f[i-1,j,k],k]\ (i>1 \ \ \ k\in[0,1]) \]
然后得到f以后就可以分別計算出a,b的路程,
令\(da[i,j,0/1]\)表示從j出發,a/b先開車,開\(2^i\)天后a走的路程,\(db[i,j,0/1]\)類似。
那么,
\[ da[0,j,0]=dist(j,M2[j])\ \ da[0,j,1]=0\\ da[1,j,k]=da[0,j,k]+da[0,f[0,j,k],k\ xor\ 1] \ (k\in[0,1])\\ da[i,j,k]=da[i-1,j,k]+da[i-1,f[i-1,j,k],k]\ (i>1 \ \ \ k\in[0,1])\\ \]
\[ db[0,j,0]=0\ \ da[0,j,1]=dist(j,M1[j])\\ db[1,j,k]=db[0,j,k]+db[0,f[0,j,k],k\ xor\ 1] \ (k\in[0,1])\\ db[i,j,k]=db[i-1,j,k]+db[i-1,f[i-1,j,k],k]\ (i>1 \ \ \ k\in[0,1])\\ \]
最后就是答案的計算了,用一個calc(S,X,0/1)表示從S出發最多走X米,a/b走的路程。
對于此考慮把路程從大到小掃描,把這個長度給拼湊出來即可,不多說了。
最后的最后,提醒:
由于本人腦抽+懶,沒有把上述過程在代碼中用函數把他們好好地體現出來,同時很多地方很蠢可以進行優化(但都懶得改),,,唉有點看不下去
code:
#include<cmath> #include<string> #include<cstdio> #include<cstring> #include<algorithm> #define RG register #define IL inline #define int long long #define DB double using namespace std;IL int gi() {RG int x=0,w=0; char ch=getchar();while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();return w?-x:x; }const int N=1e5+3; const int inf=2e9+7;int f[19][N][2],da[19][N][2],db[19][N][2]; int n,m,ans,H[N],S[N],X[N],M1[N],M2[N],link[N];struct List{int v,id,pre,next;}lis[N]; struct CITY{int h,id;}c[N]; IL bool cmp(CITY x,CITY y) {return x.h<y.h;}IL int calc(int S,int X,int p) {RG int i,d[2]={0,0};for(i=18;i>=0;--i)if(f[i][S][0]&&d[0]+d[1]+da[i][S][0]+db[i][S][0]<=X)d[0]+=da[i][S][0],d[1]+=db[i][S][0],S=f[i][S][0];return d[p]; }signed main() {RG DB Min=1e18;RG int i,j,k,Id,pr,ne,d1,d2;for(i=1,n=gi();i<=n;++i) H[i]=gi();X[0]=gi(),m=gi();for(i=1;i<=m;++i) S[i]=gi(),X[i]=gi();for(i=1;i<=n;++i) c[i]=(CITY){H[i],i};sort(c+1,c+n+1,cmp);for(i=1;i<=n;++i) lis[i]=(List){c[i].h,c[i].id,i-1,i+1},link[c[i].id]=i;lis[0].v=lis[n+1].v=2e9+1;for(i=1;i<=n;++i) {Id=link[i],pr=lis[Id].pre,ne=lis[Id].next;if(abs(lis[pr].v-H[i])<abs(lis[ne].v-H[i])) {M1[i]=lis[pr].id,pr=lis[pr].pre;if(abs(lis[pr].v-H[i])<abs(lis[ne].v-H[i])) M2[i]=lis[pr].id;else if(abs(lis[pr].v-H[i])>abs(lis[ne].v-H[i])) M2[i]=lis[ne].id;else {if(lis[pr].v<lis[ne].v) M2[i]=lis[pr].id;else M2[i]=lis[ne].id;}}else if(abs(lis[pr].v-H[i])>abs(lis[ne].v-H[i])) {M1[i]=lis[ne].id,ne=lis[ne].next;if(abs(lis[pr].v-H[i])<abs(lis[ne].v-H[i])) M2[i]=lis[pr].id;else if(abs(lis[pr].v-H[i])>abs(lis[ne].v-H[i])) M2[i]=lis[ne].id;else {if(lis[pr].v<lis[ne].v) M2[i]=lis[pr].id;else M2[i]=lis[ne].id;}}else {if(lis[pr].v<lis[ne].v) M1[i]=lis[pr].id,pr=lis[pr].pre;else M1[i]=lis[ne].id,ne=lis[ne].next;if(abs(lis[pr].v-H[i])<abs(lis[ne].v-H[i])) M2[i]=lis[pr].id;else if(abs(lis[pr].v-H[i])>abs(lis[ne].v-H[i])) M2[i]=lis[ne].id;else {if(lis[pr].v<lis[ne].v) M2[i]=lis[pr].id;else M2[i]=lis[ne].id;} }pr=lis[Id].pre,ne=lis[Id].next;lis[pr].next=ne,lis[ne].pre=pr;}for(i=1;i<n;++i) f[0][i][0]=M2[i],f[0][i][1]=M1[i];for(i=1;i<=18;++i)for(j=1;j<=n;++j)if(j+(1<<i)<=n)for(k=0;k<=1;++k) if(i==1) f[1][j][k]=f[0][f[0][j][k]][k^1];else f[i][j][k]=f[i-1][f[i-1][j][k]][k];memset(da,inf,sizeof(da)),memset(db,inf,sizeof(db));for(i=1;i<=n;++i) da[0][i][1]=0,da[0][i][0]=abs(H[M2[i]]-H[i]);for(i=1;i<=18;++i)for(j=1;j<=n;++j)if(j+(1<<i)<=n)for(k=0;k<=1;++k)if(i==1) da[1][j][k]=da[0][j][k]+da[0][f[0][j][k]][k^1];else da[i][j][k]=da[i-1][j][k]+da[i-1][f[i-1][j][k]][k];for(i=1;i<=n;++i) db[0][i][0]=0,db[0][i][1]=abs(H[M1[i]]-H[i]);for(i=1;i<=18;++i)for(j=1;j<=n;++j)if(j+(1<<i)<=n)for(k=0;k<=1;++k)if(i==1) db[1][j][k]=db[0][j][k]+db[0][f[0][j][k]][k^1];else db[i][j][k]=db[i-1][j][k]+db[i-1][f[i-1][j][k]][k];for(i=1;i<=n;++i) {d1=calc(i,X[0],0),d2=calc(i,X[0],1);if(d2==0) continue;else if((DB)d1<d2*Min) Min=(DB)d1/d2,ans=i;}if(i==n+1) printf("%lld\n",ans);for(i=1;i<=m;++i) printf("%lld %lld\n",calc(S[i],X[i],0),calc(S[i],X[i],1));return 0; }b.[√] Count The Repetitions
題目傳送
sol:
倍增優化DP做法。
首先\(conn(conn(s_2,n_2),m)=conn(s_2,n_2*m)\),那么等價于求一個最大的m‘即可。
由于m'很大,考慮可以二進制分解一下:
如果\(m'=2^{p_{t}}+2^{p_{t-1}}+2^{p_{t-2}}…+2^{p_{1}}\),那么\(conn(s_2,m')\)可以看成\(conn(s_2,2^{p_i})\ (i\in [1,t])\)拼接而成。
此時相當于把原問題的一部分的規模縮小到了\(log_{m'}\)級別。
然后考慮如何得到\(conn(s_2,2^i)\ (i\in [0,log_2m'])\),
可以先假設\(s_1\)可以重復無限次,則只需考慮一個循環即可。
那么令\(f[i,j]\)表示從\(s_1[i]\)開始到能夠構成\(conn(s_2,2^i)\)還至少需要多少個字符。
所以存在轉移:
\[ f[i,j]=f[i,j-1]+f[(i+f[i,j-1])\%|s_1|,j-1] \]
至于初值\(f[i,0]\)可以考慮直接BF做。
當把f值全部求出來之后,則只需要考慮枚舉起點,
在字符數不超過\(|s_1|*n_1\)的前提下,把答案拼湊出來,并使其盡可能大即可。
code:
#include<cmath> #include<string> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define RG register #define IL inline #define LL long long #define DB double using namespace std;const int N=303;char s1[N],s2[N]; LL ans,f[N][N]; int n1,n2,len1,len2;int main() {while(cin>>s2>>n2>>s1>>n1) {// why can not use "scanf(...)" ,,RG int i,j,fl;memset(f,0,sizeof(f));len1=strlen(s1),len2=strlen(s2);for(i=0,fl=0;i<len1&&!fl;++i) {RG int pos=i;for(j=0;j<len2&&!fl;++j) {RG int cnt=0;while(s1[pos]!=s2[j]) {pos=(pos+1)%len1;if(++cnt>=len1) {fl=1;break;}}pos=(pos+1)%len1,f[i][0]+=cnt+1;}}if(fl) {puts("0");continue;}for(i=1;i<=30;++i)for(j=0;j<len1;++j) f[j][i]=f[j][i-1]+f[(j+f[j][i-1])%len1][i-1];for(i=0,ans=0;i<len1;++i) {RG LL pos=i,k=0;for(j=30;j>=0;--j)if(pos+f[pos%len1][j]<=n1*len1)pos+=f[pos%len1][j],k+=1<<j;ans=max(ans,k);}printf("%lld\n",ans/(LL)n2);}return 0; }0x58 數據結構優化DP
c.[√] Cleaning Shifts
題目傳送
sol:
事實上直接跑最短路或許是最輕松的做法了。
但是數據結構優化DP也是沒有問題的。
令\(f[x]\)表示覆蓋\([L,x]\)所需要的最小代價,那么存在轉移:
\[ f[b_i]=min_{a_i-1≤x<b_i}\{f[x]\}+c_i \]
可以發現需要維護區間最值和支持單點修改,一顆\([L-1,R]\)線段樹即可。
code:
#include<cmath> #include<string> #include<cstdio> #include<cstring> #include<algorithm> #define RG register #define IL inline #define LL long long #define DB double #define lson p<<1 #define rson p<<1|1 using namespace std;IL int gi() {RG int x=0,w=0; char ch=getchar();while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();return w?-x:x; }const int N=1e5+3; const int M=1e6+1;int n,P,Q,f[M],Min[M<<2];struct cows{int a,b,c;}s[N]; IL bool cmp(cows x, cows y) {return x.b<y.b;}IL void pushup(int p) {Min[p]=min(Min[lson],Min[rson]);}void build(int p,int l,int r) {if(l==r) {Min[p]=f[l];return;}RG int mid=l+r>>1;build(lson,l,mid),build(rson,mid+1,r);pushup(p); }void modify(int p,int l,int r,int pos) {if(l==r) {Min[p]=min(Min[p],f[pos]);return;}RG int mid=l+r>>1;if(pos<=mid) modify(lson,l,mid,pos);else modify(rson,mid+1,r,pos);pushup(p); }int query(int p,int l,int r,int L,int R) {if(L<=l&&R>=r) return Min[p];RG int mid=l+r>>1,ans=0x3f3f3f3f;if(L<=mid) ans=min(ans,query(lson,l,mid,L,R));if(R>mid) ans=min(ans,query(rson,mid+1,r,L,R));return ans; }int main() {RG int i;n=gi(),P=gi()+1,Q=gi()+1;for(i=1;i<=n;++i)s[i].a=gi()+1,s[i].b=gi()+1,s[i].c=gi();sort(s+1,s+n+1,cmp);memset(f,0x3f,sizeof(f));f[P-1]=0; build(1,P-1,Q);for(i=1;i<=n;++i) {if(P>s[i].b||s[i].a>Q) continue;f[s[i].b]=query(1,P-1,Q,max(s[i].a,P)-1,min(s[i].b,Q))+s[i].c;if(s[i].b<=Q) modify(1,P-1,Q,s[i].b);}RG int ans=0x3f3f3f3f;for(i=1;i<=n;++i)if(s[i].b>=Q) ans=min(ans,f[s[i].b]);printf("%d\n",ans==0x3f3f3f3f?-1:ans);return 0; }d.[√] The Battle of Chibi
題目傳送
sol:
容易考慮到令\(f[i,j]\)表示以\(A[j]\)為結尾的,長度為i的嚴格遞增子序列的數量。
那么轉移應該為:
\[ f[i,j]=\sum_{k<j\&\&A_k<A_j}f[i-1,k] \]
復雜度\(O(n^3)\),考慮優化。
可以發現可以成為決策的k都在j之前可以考慮得到,只需要快速求出滿足\(A_k<A_j\)的即可。
考慮可以建立一個權值樹狀數組,實際上即下標為它的A[]值大小的數組。
每次對于j,只需查詢比\(A[j]\)小的那些和即可,得到結果之后在把\(f[i-1,j]\)插入數組即可。
由于A[]的范圍較大,離散化即可。
code:
#include<cmath> #include<string> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define RG register #define IL inline #define LL long long #define DB double #define lowbit(x) x&-x using namespace std;const int N=1003; const int mod=1e9+7;int n,m,a[N],b[N],c[N]; int ans,f[N][N],bit[N];IL void add(int x,int v) {for(;x<=n;x+=lowbit(x)) bit[x]=(bit[x]+v)%mod;} IL int query(int x) {RG int ans=0;for(;x;x-=lowbit(x)) ans=(ans+bit[x])%mod;return ans;}int main() {RG int T,G,i,j;for(G=1,cin>>T;G<=T;++G) {cin>>n>>m; for(i=1;i<=n;++i) cin>>a[i],b[i]=a[i];sort(b+1,b+n+1);for(i=1;i<=n;++i) a[i]=lower_bound(b+1,b+n+1,a[i])-b+1;memset(f,0,sizeof(f));a[0]=1,f[0][0]=1;for(i=1;i<=m;++i) {memset(bit,0,sizeof(bit));add(a[0],f[i-1][0]);for(j=1;j<=n;++j) f[i][j]=query(a[j]-1),add(a[j],f[i-1][j]);}for(i=1,ans=0;i<=n;++i) ans=(ans+f[m][i])%mod;printf("Case #%d: %d\n",G,ans);}return 0; }0x59 單調隊列優化DP
d.[√] Fence
題目傳送
sol:
首先可以按照\(s_i\)從小到大排序,保證可以進行順序DP。
然后考慮設\(f[i,j]\)表示前i個工匠,粉刷到了第j塊(可以有空缺的不刷)的最大收益。
那么:
\[ f[i,j]=f[i-1,j]\ \ \ \ \ i工匠不刷\\ f[i,j]=f[i,j-1]\ \ \ \ \ j墻不刷\\ f[i,j]=max_{j-L_i≤k<s_i}\{f[i-1,k]+p_i*(j-k)\} \]
再把第三個式子轉化一下:
\[ f[i,j]=p_i*j+max_{j-L_i≤k<s_i}\{f[i-1,k]-p_i*k\} \]
可以發現需要維護的max中只涉及k,并且有可能成為決策的k存在范圍限制,那么考慮用單掉隊列維護最值。
既可以:先掐頭,然后求值,然后去尾,最后插值即可;
也可以:現一次性把所有可能的決策插入,然后只需掐頭和取值即可。
code:
#include<cmath> #include<string> #include<cstdio> #include<cstring> #include<algorithm> #define RG register #define IL inline #define LL long long #define DB double using namespace std;IL int gi() {RG int x=0,w=0; char ch=getchar();while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();return w?-x:x; }const int N=2e4+1; const int M=103;int n,m,q[N],f[M][N];struct woker{int L,p,s;}wk[M]; IL bool cmp(woker A, woker B) {return A.s<B.s;}IL int calc(int i,int k) {return f[i-1][k]-wk[i].p*k;}int main() {RG int i,j;n=gi(),m=gi();for(i=1;i<=m;++i) wk[i].L=gi(),wk[i].p=gi(),wk[i].s=gi();sort(wk+1,wk+m+1,cmp);for(i=1;i<=m;++i) {RG int h=1,t=0;/*for(j=max(wk[i].s-wk[i].L,0);j<wk[i].s;++j) {while(h<=t&&calc(i,q[t])<=calc(i,j)) --t;q[++t]=j;}*/q[++t]=0;for(j=1;j<=n;++j) {f[i][j]=max(f[i-1][j],f[i][j-1]);if(wk[i].s<=j) {while(h<=t&&q[h]<j-wk[i].L) ++h;if(h<=t) f[i][j]=max(f[i][j],calc(i,q[h])+wk[i].p*j);}if(j<wk[i].s&&j>=max(wk[i].s-wk[i].L,0)) {while(h<=t&&calc(i,q[t])<=calc(i,j)) --t;q[++t]=j;}}}printf("%d\n",f[m][n]);return 0; }f.[√] Cut the Sequence
題目傳送
sol:
容易考慮到令\(f[i]\)表示前i個數分成若干段的最小答案,那么存在轉移:
\[ f[i]={min}_{0≤j<i并且\sum_{k=j+1}^ia[k]≤m}\{f[j]+max_{j+1≤k<i}\{a[k]\}\} \]
發現這個DP不好一眼看出優化,,那么一步一步來:
先從優化\(max_{j+1≤k<i}\{a[k]\}\)入手,
考慮對于一個i,從大到小考慮每一個決策j,
可以發現集合中每次只會多出一個新的a值,如果之前的最值大于這個新加入的值,那么上式的值不變。
利用這一點,可以直接用一個變量維護\(max\)值,可以使原問題復雜度降為\(O(n^2)\)。
考慮繼續優化,
可以注意到f[]是非嚴格遞增的,那么結合上面可以知道,在\(max_{j+1≤k<i}\{a[k]\}\)一定的情況下,使j盡量小肯定優。
那么進而可以發現,一個決策j可能成為最優決策,
① 要么\(a[j]=max_{j≤k<i}\{a[k]\}\),因為否則就有\(max_{j≤k<i}\{a[k]\}=max_{j+1≤k<i}\{a[k]\}\),
即存在\(f[i-1]+max_{j≤k<i}\{a[k]\}≤f[i]+max_{j+1≤k<i}\{a[k]\}\)顯然j不優。
可以用一個單調隊列維護所有可能的最優決策,但由于決策的結果并不具備單調性,所以用\(multiset\)映射一下每一個決策的結果即可。
② 要么j是滿足\(\sum_{k=j+1}^i≤m\)最小的j,可以通過處理出每一個這樣的位置,用合法的隊首進行一次轉移即可。
代碼實現需要認真考慮,很巧妙的,
事實上結合代碼可以發現,利用單調隊列把這個問題分成了一段一段的,對問題進行了極大地優化。
code:
#include<set> #include<cmath> #include<string> #include<cstdio> #include<cstring> #include<algorithm> #define RG register #define IL inline #define LL long long #define DB double using namespace std;const int N=1e5+3;LL m,f[N],sum[N]; int n,h,t,p,a[N],b[N],q[N];multiset<LL> s;int main() {RG int i;scanf("%d%lld",&n,&m);for(i=1;i<=n;++i) {scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];if(a[i]>m) return puts("-1"),0;}h=1,t=0,p=1;for(i=1;i<=n;++i) {while(sum[i]-sum[p-1]>m) ++p;//最小的符合上面②的位置while(h<=t&&a[i]>=a[q[t]]) {if(h<t) s.erase(a[q[t]]+f[q[t-1]]);--t;}q[++t]=i;if(h<t) s.insert(a[i]+f[q[t-1]]);while(h<=t&&q[h]<p) {if(h<t) s.erase(a[q[h+1]]+f[q[h]]);++h;}f[i]=f[p-1]+a[q[h]];//先直接進行一次轉移if(h<t) f[i]=min(f[i],*s.begin());}printf("%lld\n",f[n]);return 0; }轉載于:https://www.cnblogs.com/Bhllx/p/10996519.html
總結
- 上一篇: 小白一路走来,连续刷题三年,谈谈我的算法
- 下一篇: 在navicat中查看所有表的注释