[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 }
n^3

 


$$ 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 }
n^2

 

 

$$ 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 }
n log n

 

posted @ 2018-10-07 11:04  Jμdge  阅读(892)  评论(0编辑  收藏  举报