zoukankan      html  css  js  c++  java
  • [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] imes (j-k) + a[k] imes (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
    ",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
    ",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
    ",ans),0;
    45 }
    n log n
  • 相关阅读:
    AGC030 简要题解
    CF1601 简要题解
    CSP2021 题解
    2021.11.1-2021.11.7总结
    超快速梅森旋转SFMT(SIMD-oriented Fast Mersenne Twister)一览
    2021.10.25-2021.10.31总结
    CSP 2021 游记
    在Windows vs2015环境下编译使用Libevent
    在Windows环境下实现一个简单的libevent服务器
    Thinking in C++ 课后习题自己实现 第二章
  • 原文地址:https://www.cnblogs.com/Judge/p/9749603.html
Copyright © 2011-2022 走看看