状压 \(DP\),把经过的点压缩成01串。若第 \(i\) 位为 \(0\) 表示未到达,为 \(1\) 则表示已到达。
用 \(f[i][j]\) 表示以 \(i\) 为起点,经过 \(j\) 所含 \(1\) 位置的所有点的最小距离。
先预处理出点两两之间的距离,记为 \(dis[i][j]\),初始化 \(f\) 数组为极大值(\(memset(f,127,sizeof(f))\) 可以为浮点数赋极大值)。将所有的 \(f[i][1<<(i-1)]\) 赋为 \(dis[0][i]\),也就是原点到它的距离。
转移方程:\(f[i][k]=min(f[i][k],f[j][k-(1<<(i-1))]+dis[i][j])\)
注意,有前提条件,就是 \(k\&(1<<(i-1)) = 0\),\(k\&(1<<(j-1)) = 0\),意为当前的01串 \(k\) 包含了 \(i\) 点和 \(j\) 点。同时要 \(i \ne j\),这个很好理解。
Code:
#include<bits/stdc++.h> using namespace std; double x[20],y[20],dis[20][20],f[20][50005],ans=1e9; int n; double calc(int i,int j){ return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])); } void pre(){ for(int i=0;i<n;i++){ for(int j=i+1;j<=n;j++) dis[i][j]=dis[j][i]=calc(i,j); } } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]); memset(f,127,sizeof(f)); pre(); for(int i=1;i<=n;i++) f[i][1<<(i-1)]=dis[0][i]; for(int k=1;k<(1<<n);k++){ for(int i=1;i<=n;i++){ if((k&(1<<(i-1)))==0) continue; for(int j=1;j<=n;j++){ if(i==j) continue; if((k&(1<<(j-1)))==0) continue; f[i][k]=min(f[i][k],f[j][k-(1<<(i-1))]+dis[i][j]); } } } for(int i=1;i<=n;i++) ans=min(ans,f[i][(1<<n)-1]); printf("%.2lf",ans); return 73; }
记 \(f[i][j]\) 表示 \(i\) 到 \(j\) 一段区间合并的最小花费。
可以考虑将一个区间 \(i\) ~ \(j\) 从任意位置 \(k\) 分成两段,再合并的最小值。
可以写出转移方程:
\(f[i][j]=\min \{\) \(f[i][k]+f[k+1][j]\) \(\}\) \(+\sum_{x=i}^{j} a[x]\) \((i\le k\le j)\)
其中 \(\sum_{x=i}^{j} a[x]\) 可以用前缀和求出。
因为求最小值,所以初始化 \(f\) 为极大值,同时将 \(f[i][i]\) 赋为 \(0\)。
Code:
#include<bits/stdc++.h> using namespace std; int n,f[1005][1005],a[1005],sum[1005]; int main(){ scanf("%d",&n); memset(f,0x3f,sizeof(f)); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); f[i][i]=0; sum[i]=sum[i-1]+a[i]; } for(int len=2;len<=n;len++){ for(int i=1;i<=n-len+1;i++){ int j=i+len-1; for(int k=i;k<j;k++){ f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]); } } } printf("%d",f[1][n]); return 76; }
记 \(f[i][j]\) 表示前 \(i\) 个数,删掉 \(j\) 个时匹配的最大个数。
首先考虑直接删掉这个数, \(f[i][j]=f[i-1][j-1]\),相当于不变。注意:当 \(j>0\) 时才考虑这一种情况。
其次,考虑不删这个数。当 \(a[i]=i-j\) 是,这个数刚好是匹配的。所以 \(f[i][j]=max(f[i][j],f[i-1][j]+(a[i]==i-j))\)
最后答案是所有 \(f[i][j]\) 中的最大值。
Code:
#include<bits/stdc++.h> using namespace std; int n,a[1005],f[1005][1005],ans; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++){ for(int j=0;j<i;j++){ if(j>0) f[i][j]=f[i-1][j-1]; f[i][j]=max(f[i][j],f[i-1][j]+(a[i]==i-j)); ans=max(ans,f[i][j]); } } printf("%d",ans); return 111; }
记 \(f[i][j]\) 表示小猫在第 \(i\) 层(设高度为 \(h\) 为第 \(1\) 层,高度为 \(1\) 为第 \(h\) 层),第 \(j\) 棵树上时,最多可以吃到的柿子数量。
\(a[i][j]\) 表示第 \(i\) 棵树第 \(j\) 层有几个柿子,所以读入时这样记(为配合 \(f\),有点别扭):
for(int j=1;j<=x;j++) scanf("%d",&y),a[i][h-y+1]++;
两种转移:
但这样时间复杂度 \(O(n^3)\),显然 \(TLE\)。考虑优化,\(f[k][i-de]\) 这一维可以用 \(mx[i-de]\) 代替,其中 \(mx[i-de]\) 表示 \(\max \{\) \(f[k][i-de]\) \(\}\),是可以边算边更新的。
Code:
#include<bits/stdc++.h> using namespace std; int n,h,de,x,y,f[2005][2005],mx[2005]; int a[2005][2005],ans; int main(){ scanf("%d%d%d",&n,&h,&de); for(int i=1;i<=n;i++){ scanf("%d",&x); for(int j=1;j<=x;j++) scanf("%d",&y),a[i][h-y+1]++; } for(int i=1;i<=h;i++){ for(int j=1;j<=n;j++){ f[i][j]=f[i-1][j]+a[j][i]; if(i>de) f[i][j]=max(f[i][j],mx[i-de]+a[j][i]); mx[i]=max(mx[i],f[i][j]); } } for(int i=1;i<=n;i++) ans=max(ans,f[h][i]); printf("%d",ans); return 118; }
\(f[i]\) 表示到 \(i\) 为止最大子段和。
转移:\(f[i]=max(f[i-1],0)+a[i]\)
答案为 \(\max \{\) \(f[i]\) \(\}\)
Code:
#include<bits/stdc++.h> using namespace std; int n,a[200005],f[200005],ans=-1e9; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); f[1]=a[1]; for(int i=2;i<=n;i++) f[i]=max(0,f[i-1])+a[i],ans=max(ans,f[i]); printf("%d",ans); return 101; }
分两种情况讨论。
记 \(f1\) 为头尾在 \(1\) ~ \(n\) 范围内最大子段和:\(f1[i]=max(f1[i-1]+a[i],a[i])\)
记 \(f2\) 为头尾在 \(1\) ~ \(n\) 范围内最小子段和:\(f2[i]=min(f2[i-1]+a[i],a[i])\)
初始化 \(f1[1]=f2[1]=a[1]\)
Code:
#include<bits/stdc++.h> using namespace std; int f1[200005],f2[200005]; int n,a[200005],sum,mx,mn; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i]; mx=mn=f1[1]=f2[1]=a[1]; for(int i=2;i<=n;i++) f1[i]=max(f1[i-1]+a[i],a[i]),mx=max(mx,f1[i]); for(int i=2;i<=n;i++) f2[i]=min(f2[i-1]+a[i],a[i]),mn=min(mn,f2[i]); printf("%d",max(mx,sum-mn)); return 67; }
对于每个 \(k(2<k<n)\),我们让它作隔点,计算出其左边的最大子段和与右边最大子段和,相加,最后取 \(\max\) 即可。
每个点左侧的最大子段和记 \(f1[i]\),右侧的最大子段和记 \(f2[i]\),都可以 \(O(n)\) 预处理。
Code:
#include<bits/stdc++.h> #define ll long long using namespace std; ll f1[1000005],f2[1000005]; ll n,a[1000005],ans=-1e18; int main(){ scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); f1[1]=a[1],f2[n]=a[n]; for(int i=2;i<=n;i++) f1[i]=max(f1[i-1]+a[i],a[i]); for(int i=2;i<=n;i++) f1[i]=max(f1[i],f1[i-1]); for(int i=n-1;i>=1;i--) f2[i]=max(f2[i+1]+a[i],a[i]); for(int i=n-1;i>=1;i--) f2[i]=max(f2[i],f2[i+1]); for(int i=2;i<n;i++) ans=max(ans,f1[i-1]+f2[i+1]); printf("%lld",ans); return 89; }
环状最大子段和 与 双子序列最大和 的结合体。
需要注意的是,这题中两个子段可以去相邻的,所以更麻烦一些。
for(int i=1;i<n;i++) mx=max(mx,f1[i]+f2[i+1]);
Code:
#include<bits/stdc++.h> using namespace std; int f1[200005],f2[200005],f3[200005],f4[200005]; int n,a[200005],sum,mx=-2e9,mn; int mmn(int k,int n){ int tmp=2e9; f3[1]=a[1+k],f4[n]=a[n+k]; for(int i=2;i<=n;i++) f3[i]=min(f3[i-1]+a[i+k],min(a[i+k],0)); for(int i=2;i<=n;i++) f3[i]=min(f3[i-1],f3[i]); for(int i=n-1;i>0;i--) f4[i]=min(f4[i+1]+a[i],min(a[i],0)); for(int i=n-1;i>0;i--) f4[i]=min(f4[i+1],f4[i]); for(int i=2;i<n;i++) tmp=min(tmp,f3[i-1]+f4[i+1]); return tmp; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i]; f1[1]=a[1],f2[n]=a[n]; for(int i=2;i<=n;i++) f1[i]=max(f1[i-1]+a[i],a[i]); for(int i=2;i<=n;i++) f1[i]=max(f1[i],f1[i-1]); for(int i=n-1;i>=1;i--) f2[i]=max(f2[i+1]+a[i],a[i]); for(int i=n-1;i>=1;i--) f2[i]=max(f2[i],f2[i+1]); for(int i=1;i<n;i++) mx=max(mx,f1[i]+f2[i+1]); mn=min(mmn(0,n-1),mmn(1,n-1)); printf("%d",max(mx,sum-mn)); return 89; }