[IOI2000] 邮局
## 非常神仙的 wqs 二分优化dp,又学了一招。
首先我们需要先想到一个人类智慧版的前缀和优化。
# part 1:violence
然鹅在前缀和优化之前我们先考虑暴力做法:
我们可以枚举 i 、 j 令其表示前 i 个村庄设立 j 个邮局的最小贡献。
然后枚举 k 表示前 k 个村庄已经设立邮局,现在处理 k+1~i 的村庄。
接着再枚举当前邮局设立在哪里,然后 O(n) 累加每个村庄的贡献。
这样的复杂度是 O(n^5) 的,也许达不到这个上限,但是 O(n^4) 的时间总是要的。
于是这样...已经炸掉了。
# part 2:optimization(human wisdom)
我们考虑在一段区间内建立一个邮局,那么这个邮局会使得附近村庄的贡献降低。
那么如何使得这个降低的贡献最大呢?我们可以由 **~~人类智慧~~ 推论** 得出:
当我们将邮局设立在一个要产生贡献的区间的中点时,降低的贡献最大。
那么这时我们不妨设区间中点坐标为 k ,左端点坐标 i ,右端点坐标 j 。
此时这段区间对答案的贡献为:# $$(S[j]-S[k])-a[k] \times (j-k) + a[k] \times (k-i)-(S[k]-S[i])$$ #
那么这样的复杂度是 O(n^3) 的,已经有了较大进步,起码30分是到手了。
# part 3:optimization(Quadrilateral inequality)
于是我们考虑进一步优化,看到 满数据 是 3e3 的数据范围,那么应该是要用 O(n^2) 的算法。
那这里就要用 四边形不等式优化了(我不会)。同学们可以自行研究,大概就是根据 f[i][j] 的一个性质:
f[i][j]+f[i-1][j+1]>f[i-1][j]+f[i][j+1] => f[i][j] 的决策点在 f[i-1][j] 和 f[i][j+1] 之间之类的。
(怎么证我就母鸡了)
于是 O(n^2) 满分。
# part4:optimization(wqs binary cut)
然鹅我们还可以考虑进一步升华算法。
我们可以考虑二分将算法复杂度优化成 O(nlogn)
而且是 wqs 二分。
如何二分? 我们考虑给区间的每次分割增加一个贡献。
那么我们可以看出:增加贡献越大,将会分割的次数就越少。
容易想到,当分割出的段数恰好为 m 时,该状态下的 f[n] 减去增加贡献就是答案。
这样一个 log 去了。 那么怎么 O(n) 转移方程?
我们用单调队列优化转移,单调队列内每个点记录上次转移位置以及其控制的后方最优解范围。
也就是说,最后一种算法是用了二分优化 part3 ,将一个 n 变成了 log 。
# part 5:coding(s)
$$ O(n^3) $$
1 //by Judge 2 #include<algorithm> 3 #include<iostream> 4 #include<cstring> 5 #include<cstdio> 6 const int M=511; 7 #ifndef Judge 8 #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 9 #endif 10 char buf[1<<21],*p1=buf,*p2=buf; 11 inline int read(){ 12 int x=0,f=1; char c=getchar(); 13 for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; 14 for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; 15 } int n,m,a[M],s[M],f[M][M]; 16 inline void cmin(int& a,int b){ if(a>b) a=b; } 17 int main(){ 18 n=read(),m=read(),memset(f,0x3f,sizeof(f)),f[0][0]=0; 19 for(int i=1;i<=n;++i) a[i]=read(); std::sort(a+1,a+1+n); 20 for(int i=1;i<=n;++i) s[i]=s[i-1]+a[i]; 21 for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) for(int k=0,t;k<i;++k) 22 t=i+k+1>>1,cmin(f[i][j],f[k][j-1]+(s[i]-s[t])-a[t]*(i-t)+a[t]*(t-k)-(s[t]-s[k])); 23 return printf("%d\n",f[n][m]),0; 24 }
$$ O(n^2) $$
1 //by Judge 2 #include<algorithm> 3 #include<iostream> 4 #include<cstdio> 5 #define ll long long 6 const int M=3011; 7 #ifndef Judge 8 #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 9 #endif 10 char buf[1<<21],*p1=buf,*p2=buf; 11 inline int read(){ 12 int x=0,f=1; char c=getchar(); 13 for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; 14 for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; 15 } int n,m,a[M],mk[M][M]; ll f[M][M],w[M][M]; 16 int main(){ 17 n=read(),m=read(); 18 for(int i=1;i<=n;++i) a[i]=read(); std::sort(a+1,a+1+n); 19 for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) 20 w[i][j]=w[i][j-1]+a[j]-a[i+j>>1]; 21 for(int i=1;i<=n;++i) f[1][i]=w[1][i],mk[1][i]=0; 22 for(int i=2;i<=m;++i){ mk[i][n+1]=n; 23 for(int j=n;j>i;--j){ 24 f[i][j]=1ll<<60; 25 for(int k=mk[i-1][j];k<=mk[i][j+1];++k) 26 if(f[i][j]>f[i-1][k]+w[k+1][j]) 27 f[i][j]=f[i-1][k]+w[k+1][j],mk[i][j]=k; 28 } 29 } return printf("%lld\n",f[m][n]),0; 30 }
$$ O(n logn) $$
1 //by Judge 2 #include<algorithm> 3 #include<iostream> 4 #include<cstdio> 5 #define mid (l+r>>1) 6 #define ll long long 7 using namespace std; 8 const int M=1e5+11; 9 const ll inf=1e18+7; 10 #ifndef Judge 11 #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 12 #endif 13 char buf[1<<21],*p1=buf,*p2=buf; 14 inline int read(){ 15 int x=0,f=1; char c=getchar(); 16 for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; 17 for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; 18 } int n,m,las[M]; ll a[M],S[M],f[M]; 19 struct node{ int pos,l,r; // l~r 为 pos 点所控制的最优区间 20 node(int pos,int l,int r):pos(pos),l(l),r(r){} node(){} 21 }Q[M]; 22 inline ll calc(int l,int r,int tim){ 23 if(l>=r) return inf; int t=l+r+1>>1; //人类智慧+前缀和优化 24 return f[l]+(S[r]-S[t])-(r-t)*a[t]+(t-l)*a[t]-(S[t]-S[l])+tim; 25 } inline bool check(int tim){ 26 int siz=1,ans=0; Q[1]=node(0,1,n); 27 for(int i=1;i<=n;++i){ int l=1,r=siz,pos; 28 while(l<=r) Q[mid].l<=i?l=(pos=mid)+1:r=mid-1; 29 f[i]=calc(Q[pos].pos,i,tim),las[i]=Q[pos].pos,pos=n+1; 30 while(siz&&calc(Q[siz].pos,Q[siz].l,tim)>=calc(i,Q[siz].l,tim)) pos=Q[siz--].l; 31 if(siz && calc(Q[siz].pos,Q[siz].r,tim)>=calc(i,Q[siz].r,tim)){ l=Q[siz].l,r=Q[siz].r; 32 while(l<=r) calc(Q[siz].pos,mid,tim)>=calc(i,mid,tim)?r=(pos=mid)-1:l=mid+1; 33 Q[siz].r=pos-1; 34 } if(pos!=n+1) Q[++siz]=node(i,pos,n); 35 } for(int i=n;i;i=las[i]) ++ans; return ans<m; 36 } 37 int main(){ 38 n=read(),m=read(); 39 for(int i=1;i<=n;++i) a[i]=read(); 40 sort(a+1,a+1+n); 41 for(int i=1;i<=n;++i) S[i]=S[i-1]+a[i]; 42 int l=0,r=5e6; ll ans=0; 43 while(l<=r) check(mid)?r=mid-1:(ans=f[n]-m*mid,l=mid+1); 44 return printf("%lld\n",ans),0; 45 }