zoukankan      html  css  js  c++  java
  • 【dp专题】NOIP真题-DP专题练习

    这里学习一下DP的正确姿势。

    也为了ZJOI2019去水一下做一些准备

    题解就随便写写啦.

    后续还是会有专题练习和综合练习的.

    P1005 矩阵取数游戏

    给出$n imes m$矩阵每次在每一行取n个数,一共取m次,

    第i次取数的权值是$2^i$,给出一个取数的顺序,最大化取完所有数的贡献和。

    输出贡献和。

    对于100%的数据$1leq n,m leq 80,0<a_{i,j} leq 10^3 $

    需要使用高精度,考虑一个DP,$f[i][j][k]$表示第i行,共取j次,其中k次在前面,显然(j-k)在后面

    第j次取数在前面取的那么$f[i][j][k]$是由$f[i][j-1][k-1]$转移而来,得dp方程$f[i][j][k]=f[i][j-1][k-1]+2^j imes a[k]$ (取的数在从左往右数第$k$个)

    第j次取数在后面取的那么$f[i][j][k]$是由$f[i][j-1][k]$转移而来,得dp方程$f[i][j-1][k]+2^j imes a[m-(j-k)+1]$ (取的数在从左向右数第$m-(j-k)+1$个)

    然后整理可得DP方程$f[i][j][k]=max{f[i][j-1][k-1]+2^j imes a[k] , f[i][j-1][k]+2^j imes a[m-(j-k)+1]  }$

    考虑边界条件对于$f[i][j][k]$是有意义的当且仅当$0 leq j leq k$,然后使用高精度计算(预处理2的幂的Lint数)

    复杂度$O(km^3)$,其中k是高精度数的位数可视为常数

    code: 

    # include <bits/stdc++.h>
    # define fp(i,s,t) for(int i=s;i<=t;i++)
    # define int long long
    using namespace std;
    const int N=85;
    const int L=50;
    char s[L];
    inline int read()
    {
        int X=0,w=0; char c=0;
        while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
        while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
        return w?-X:X;
    }
    struct Lint{
        int len,num[L];
        Lint () { len=0; memset(num,0,sizeof(num));}
        Lint change(int x) {
            Lint a;
            if (x==0) { a.num[1]=0;a.len=1; return a;}
            a.len=0;
            while (x>0) a.num[++a.len]=x%10,x/=10;
            return a;
        } 
        void print(Lint x) {
            for (int i=x.len;i>=1;i--) putchar(x.num[i]+'0');
            putchar('
    '); 
        }
        Lint operator + (const Lint &t) const {
            Lint ans;
            memset(ans.num,0,sizeof(ans.num));
            ans.len=max(len,t.len);
            for (int i=1;i<=ans.len;i++) {
                ans.num[i]+=num[i]+t.num[i];
                ans.num[i+1]+=ans.num[i]/10;
                ans.num[i]%=10;
            }
            if (ans.num[ans.len+1]>0) ans.len++;
            return ans;
        }
        Lint operator * (const Lint &t) const {
            Lint ans;
            for (int i=1;i<=len;i++)
             for (int j=1;j<=t.len;j++)
              ans.num[i+j-1]+=num[i]*t.num[j];
            for (int i=1;i<=len+t.len;i++) {
                ans.num[i+1]+=ans.num[i]/10;
                ans.num[i]%=10;
            }  
            if (ans.num[len+t.len]>0) ans.len=len+t.len;
            else ans.len=len+t.len-1;
            return ans;
        } 
        bool operator < (const Lint &t) const {
            if (len>t.len) return false;
            else if (len<t.len) return true;
            for (int i=len;i>=1;i--)
             if (num[i]<t.num[i]) return true;
             else if (num[i]>t.num[i]) return false;
            return false; 
        }
    }rd;
    Lint Max(Lint x,Lint y){if (x<y) return y; else return x;}
    int n,m;
    Lint f[N][N],a[N][N],ans;
    Lint pw[N];
    signed main()
    {
        scanf("%lld%lld",&n,&m);
        fp(i,1,n) fp(j,1,m) a[i][j]=rd.change(read());
        pw[0]=rd.change(1); Lint num_2=rd.change(2);
        fp(i,1,m) pw[i]=pw[i-1]*num_2;
        fp(i,1,n) {
            Lint ret=rd.change(0); memset(f,0,sizeof(f));
            fp(j,1,m) fp(k,0,j) {
                if (k!=0) f[j][k]=f[j-1][k-1]+pw[j]*a[i][k];
                if (j-1>=k-1) f[j][k]=max(f[j][k],f[j-1][k]+pw[j]*a[i][m-j+k+1]);
            }
            fp(k,0,m) ret=Max(ret,f[m][k]);
            ans=ans+ret;
        }
        rd.print(ans); 
        return 0;
    }
    P1005 矩阵取数游戏

    P1026 统计单词个数

    给出一个长度为m的字符串,和n个字典$S_i$ ,分成k份,每份里面单独处理,若一个位置为单词首字母可以在后匹配出一个字典,

    那么有1的贡献,求出一个分配方案使得字符串分成k份后,贡献值最大。

    对于100%的数据$1 leq m,n leq 200, 1 leq k leq 40 , 字典S_ileq 6$

    如果我们知道s的唯一连续划分[l,r]中总贡献是GetVal(l,r),那么就是一个简单的动态规划问题了。

    令$f[i][j]$表示前i个字母划分成j份,最大贡献,显然是一个位置k转移过来增加GetVal(k+1,i)这么多贡献

    即$f[i][j]=max{ f[k][j-1]+GetVal(k+1,i)}$,然后考虑GetVal(l,r)的暴力求解,直接枚举每一位i属于[l,r],然后枚举n个字典暴力匹配

    注意判断是否超出r的右边界的限制,不要加进去。

    然后如果枚举i的时候在线做GetVal会多个n的复杂度,不妨离线$O(n^3)$预处理出$val[l,r]=GetVal(l,r)$

    然后$O(n^3)$的动态规划,显然是跑不满的。

    # include <bits/stdc++.h>
    using namespace std;
    string s,th;
    char mp[205][205];
    int p,k,n,m;
    int f[205][205],val[205][205];
    bool check(int pos,int limt)
    {
        for (int i=1;i<=n;i++) {
            int l=strlen(mp[i]);
            if (pos+l-1>limt||pos+l-1>m) continue;
            bool flag=true;
            for (int j=0;j<l;j++) 
              if (s[pos+j]!=mp[i][j]) {
                  flag=false; break;
              }
            if (flag) return true;  
        }
        return false;
    }
    int GetVal(int l,int r)
    {
        int ret=0;
        for (int i=l;i<=r;i++) if (check(i,r)) ret++;
        return ret; 
    }
    int main()
    {
        scanf("%d%d",&p,&k); s="#";
        for (int i=1;i<=p;i++) cin>>th,s+=th;
        scanf("%d",&n);
        for (int i=1;i<=n;i++) cin>>mp[i];
        m=s.length()-1;
        for (int i=1;i<=m;i++)
         for (int j=i;j<=m;j++)
          val[i][j]=GetVal(i,j);
        for (int i=1;i<=m;i++)
         for (int j=1;j<=i;j++) {
           # define k kk
           for (int k=j-1;k<=i-1;k++)
           f[i][j]=max(f[i][j],f[k][j-1]+val[k+1][i]);
           #undef k
         }
        
        printf("%d
    ",f[m][k]);
        return 0;
    }
    P1026 统计单词个数

    P1099 树网的核

    bzoj1999 【数据加强版】

    给定一棵n个点的带边权无根树,在其直径上求出一段长度不超过s的路径F,使得离路径距离最远的点到路径的距离最短。

    求出这个离路径距离最远的点到路径的距离最短的值。

    对于100%的数据$1 leq n leq 10^5 ,0 leq w_i leq 10^3 $

    可以证明,路径F存在于任意一条直径上都可以得到答案。

    考虑找出一条树的直径,拉成一条链,就是m个点m-1条边的链

    二分一个答案MID

    然后对于链上每一个点做一个最长链,记为$w_i$然后从链的两端往里跳,如果可以往里跳尽量往里跳(需要使用MID和$w_i$参数)

    得到最终的左右区间[Left,Right]显然若区间为空那么一定可以,

    若区间不为空,那么扫描[Left,Right]判断其$w_i$是不是超过了$MID$,其(Right-Left)条边权和是不是超过了限制$s$

    然后转化为判定类问题。

    复杂度$O(max{N log_2T,n}) $ 其中T表示最长链长度,N表示最长链节点数,n表示树节点个数。

    可以过BZOJ加强版数据

    # include <bits/stdc++.h>
    using namespace std;
    const int N=500005;
    int dist[N],d[N],head[N],pre[N],w[N],f[N];
    int n,m,s,tot,ans;
    bool vis[N];
    struct rec{
        int pre,to,w;
    }a[N<<1];
    inline int read()
    {
        int X=0,w=0; char c=0;
        while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
        while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
        return w?-X:X;
    }
    void adde(int u,int v,int w)
    {
        a[++tot].pre=head[u];
        a[tot].to=v;
        a[tot].w=w;
        head[u]=tot;
    }
    void dfs1(int u,int fath,int L)
    {
        dist[u]=L;
        for (int i=head[u];i;i=a[i].pre){
            int v=a[i].to;
            if (v==fath) continue;
            pre[v]=u;
            dfs1(v,u,L+a[i].w);
        }
    }
    void dfs2(int u,int fath,int L)
    {
        ans=max(ans,L); vis[u]=true;
        for (int i=head[u];i;i=a[i].pre) {
            int v=a[i].to;
            if (vis[v]) continue;
            dfs2(v,u,L+a[i].w);
        }
    }
    bool check(int MID)
    {
        # define lift Lift
        # define Right Right
        f[0]=0;
        int left=d[0],right=1;
        for (int i=1;i<=d[0];i++) {
            f[i]=max(f[i-1]+dist[i],w[i]);
            if (f[i]>MID) {left=i; break;}
        }
        f[d[0]+1]=0;
        for (int i=d[0];i>=1;i--) {
            f[i]=max(f[i+1]+dist[i-1],w[i]);
            if (f[i]>MID) { right=i; break;}
        }
        if (left>right) return true;
        int Sum=0;
        for (int i=left;i<=right;i++) {
            if (w[i]>MID) return false;
            if (i!=right) Sum+=dist[i];
        }
        if (Sum<=s) return true; 
        else return false;
        #undef left
        #undef right
    }
    void GetZhiJin()
    {
        dfs1(1,0,0);
        int p1=1;
        for (int i=1;i<=n;i++) if (dist[i]>dist[p1]) p1=i;
        dfs1(p1,0,0);
        int p2=1;
        for (int i=1;i<=n;i++) if (dist[i]>dist[p2]) p2=i;
        swap(p1,p2);
        while (p1!=p2) {
            d[++d[0]]=p1;
            for (int i=head[p1];i;i=a[i].pre){
                int v=a[i].to;
                if (v==pre[p1]) dist[d[0]]=a[i].w;
            }
            p1=pre[p1];
        }
        d[++d[0]]=p2;
        dist[d[0]]=0;
    }
    int main()
    {
        n=read();s=read();
        for (int i=2;i<=n;i++) {
            int u=read(),v=read(),w=read();
            adde(u,v,w),adde(v,u,w);
        }
        GetZhiJin();
        int L=0,R=0;
        for (int i=1;i<=d[0];i++) vis[d[i]]=true,R+=dist[i];
        for (int i=1;i<=d[0];i++) {
            ans=0;
            dfs2(d[i],0,0); 
            w[i]=ans;
        } 
        while (L<=R) {
            int mid=(L+R)>>1;
            if (check(mid)) ans=mid,R=mid-1;
            else L=mid+1;
        }
        cout<<ans<<'
    ';
        return 0;
    }
    
    BZOJ1999(Luogu P1099) 树网的核

    P1351 联合权值

    求一棵n个节点的树,所有边权为$1$,距离为$2$的点对权值乘积最大值、权值乘积和,

    其中权值乘积和需要对$10007$取模

    对于100%的数据$nleq 2 imes 10^5 , 1 leq w_i leq 10^3$

    枚举每个点考虑这个点连接到的若干个点,设这个集合为S,那么S重的点互相两两配对都是符合条件的点对。

    然后考虑最大值显然是S中两个最大的点权相乘,求和显然是每一个点和前面的点贡献和的2倍,维护即可

    复杂度O(kn),其中k表示节点的直接儿子数的平均值,可以看做常数

    # include<bits/stdc++.h>
    # define int long long
    using namespace std;
    const int N=200000+10;
    const int mo=10007;
    int val[N],tot,head[N];
    struct rec{int pre,to;}a[N<<1];
    void adde(int u,int v)
    {
        a[++tot].pre=head[u];
        a[tot].to=v;
        head[u]=tot;
    }
    signed main()
    {
        int n; scanf("%lld",&n);
        int u,v;
        for (int i=2;i<=n;i++) {
            scanf("%lld%lld",&u,&v);
            adde(u,v); adde(v,u);
        }
        for (int i=1;i<=n;i++) scanf("%lld",&val[i]);
        int ans1=0,ans2=0;
        for (int i=1;i<=n;i++) {
            int u=i;
            int ret=0;
            int last_sum=0;
            int max1=0,pos1;
            for (int i=head[u];i;i=a[i].pre){
                int v=a[i].to;
                ret+=last_sum*val[v]%mo; ret%=mo; 
                last_sum+=val[v]%mo; last_sum%=mo; 
                if (max1<val[v]) max1=val[v],pos1=v;
            }
            int max2=0;
            for (int i=head[u];i;i=a[i].pre) {
                int v=a[i].to;
                if (max2<val[v]&&v!=pos1) max2=val[v];
            }
            ans1=max(ans1,max1*max2);
            ans2+=(ret<<1)%mo; ans2%=mo;
        }
        cout<<ans1<<' '<<ans2<<'
    '; 
        return 0;
    } 
    P1351 联合权值

     P1514 引水入城

    给出$n imes m$的网格图每个点(i,j)表示该点的高度,然后考虑水只能从高处流向低处。

    在第一行设k个取水站然后要求最后一行所有城市都可以流经水,若可能最小化k,若不可能求有多少个城市无法流经水

    对于100%的数据$n,m leq 500$ 

    考虑一个$O(n^3)$算法求出第一行每一个点可以覆盖到最后一行多少点,我们可以证明覆盖的一定是一个区间:

    若覆盖不是一个区间那么可能存在2条路径相交,而水可以选取任意一条路径流下,那么这两个区间不是独立的,

    必然有交集而形成一个独立的1个区间。

    然后在给出的若干个区间做线段覆盖若不能覆盖[1,n]计算30%的答案,若能覆盖那么计算剩余70%的答案

    贪心做法如下:左端点排序,然后取头线段看后面头在头线段区间之间那么尽可能往右拓展。排序即可。

    常数优化,当且仅当第一行高度比两边都高才进行搜索,显然更优(因为ta隔壁到的ta一定可以到)。

    // luogu-judger-enable-o2
    # include <bits/stdc++.h>
    using namespace std;
    const int N=505;
    const int dx[]={-1,0,1,0};
    const int dy[]={0,1,0,-1};
    int mp[N][N],a[N][N],get[N];
    int n,m,num;
    bool use[N][N];
    struct node{int x,y;};
    struct rec{int l,r;}w[N];
    queue<node>q;
    bool cmp(rec a, rec b){return a.l<b.l;}
    inline int read()
    {
        int X=0,w=0; char c=0;
        while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
        while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
        return w?-X:X;
    }
    void bfs(int sx,int sy)
    {
        memset(use,false,sizeof(use));
        q.push((node){sx,sy});
        use[sx][sy]=true;
        while (!q.empty())  {
            node u=q.front(); q.pop();
            for (int i=0;i<4;i++) {
                node v; v.x=u.x+dx[i];v.y=u.y+dy[i];
                if (v.x<1||v.x>n||v.y<1||v.y>m||use[v.x][v.y]) continue;
                if (a[u.x][u.y]<=a[v.x][v.y]) continue;
                use[v.x][v.y]=true;
                q.push(v); 
            }
        }
        for (int i=1;i<=m;i++)
         if (use[n][i]) get[i]=true;
        int L=0,R=0;
        for (int i=1;i<=m;i++)
         if (use[n][i]) { L=i; break;}
        for (int i=m;i>=1;i--) 
         if (use[n][i]) { R=i; break;} 
        if (L==0&&R==0) return;
        w[++num]=(rec){L,R};
    }
    int main()
    {
        n=read();m=read();
        for (int i=1;i<=n;i++)
         for (int j=1;j<=m;j++)
          a[i][j]=read();
        for (int i=1;i<=m;i++) bfs(1,i);  
        int ret=0;
        for (int i=1;i<=m;i++) 
             if (get[i]) ret++;
        if (ret<m) {
            printf("0
    %d
    ",m-ret);
            return 0;
        }     
        sort(w+1,w+1+num,cmp);
        int i=1,j=1,ans=0;
        while (j<=m) {
            int t=0;
            while (w[i].l<=j) {
                t=max(t,w[i].r);
                i++;
            }
            j=t+1;ans++;
        }
        printf("1
    %d
    ",ans);
        return 0;
    }
    P1514 引水入城

    P1850 换教室

    有n门课程,分在2n个教室上课c[i]和d[i],然后对教室之间有无向图{v,e}联通,每次从一教室走到另一教室走最短路。

    第i门课程默认在c[i]上课,但是可以最多选择m门课程换到d[i]教室上课,有k[i]的期望换教室成功,确定申报若干个教室编号

    使得完成n门课程期望行走路程最短,输出期望最短总路程长度。

    对于100%的数据,$v leq 300,n,m leq 2000,$图可能会有重边和自环

    考虑若选择当前教室申报和不选择当前教室申报期望是不同的,或者说一个有期望,一个没有期望。

    所以要设计一维状态0/1表示当前教室选不选择申报。

    所以显然的设计$f[i][j][0/1]$表示前 i 节课使用 j 次申报其中第 i 节课 不申报/申报 换教室最小期望总路程长度

    考虑$f[i][j][0]$的转移,显然是从$i-1$转移而来,

    第1种 第$i-1$节课不申请,所以对于$i-1$到$i$是唯一的一种可能期望路径 从$c[i-1]$走到$c[i]$,得$f[i-1][j][0]+dist(c[i-1],c[i])$

    第2种 第$i-1$节课申请 , 所以对于$i-1$到$i$有2种可能的期望路径,从$c[i-1]$到$c[i]$($i-1$换课不成功),从$d[i-1]$到$c[i]$($i-1$换课成功),

                得$f[i-1][j][1]+k[i-1] imes dist(d[i-1],c[i]) + (1-k[i-1]) imes dist(c[i-1],c[i])$

        综上所述$f[i][j][0]=max{ f[i-1][j][1]+k[i-1] imes dist(d[i-1],c[i]) + (1-k[i-1]) imes dist(c[i-1],c[i]) , f[i-1][j][0]+dist(c[i-1],c[i]) }$

    考虑$f[i][j][1]$的转移,显然是从$i-1$转移而来,

    第1种 第$i-1$节课不申请,所以对于$i-1$到$i$有2种可能的期望路径,从$c[i-1]$到$c[i]$($i$换课不成功),从$c[i-1]$到$d[i]$($i$换课成功),

                得$f[i-1][j-1][0]+k[i] imes dist(c[i-1],d[i])+(1-k[i]) imes dist(c[i-1],d[i]) $

    第2种 第$i-1$节课申请 , 所以对于$i-1$到$i$有$2 imes 2$种可能的期望路径,

                从$c[i-1]$到$c[i]$($i-1,i$换课不成功),得 $(1-k[i-1]) imes (1-k[i]) imes dist(c[i-1],c[i])$

                从$c[i-1]$到$d[i]$($i-1$换课不成功,$i$换课成功),得$((1-k[i-1]) imes k[i] imes dist(c[i-1],d[i]) $

                从$d[i-1]$到$c[i]$($i-1$换课成功,$i$换课不成功),得$k[i-1] imes (1-k[i]) imes dist(d[i-1],c[i])$

                从$d[i-1]$到$d[i]$($i-1,i$换课成功),得$k[i-1] imes k[i] imes dist(d[i-1],d[i]) $

        综上所述 $f[i][j][1]=max {f[i-1][j-1][1]+(1-k[i-1]) imes (1-k[i])  imes dist(c[i-1],c[i]) +$

             $ ((1-k[i-1]) imes k[i] imes dist(c[i-1],d[i]) + $

             $k[i-1] imes (1-k[i]) imes dist(d[i-1],c[i]) + $

             $ k[i-1] imes k[i] imes dist(d[i-1],d[i]) $

             $, f[i-1][j-1][0]+k[i] imes dist(c[i-1],d[i])+(1-k[i]) imes dist(c[i-1],d[i]) }$

    然后考虑j=0的边界转移,若 k=0 只能从i-1不换课,转移,而k=1根本不能

    边界f[1][0][0]=0,f[1][1][1]=0,f[][][]=INF

    答案是$Min{ f[n][i][0],f[n][i][1] } (0 leq i leq m)$

    # include <bits/stdc++.h>
    # define fp(i,s,t) for (int i=s;i<=t;i++)
    # define INF (1<<20)
    using namespace std;
    const int N=2e3+10;
    int c[N],d[N];
    double mp[N][N];
    int n,m,v,e;
    double f[N][N][2],k[N];
    double Min(double a,double b)
    {
        if (a<b) return a; else return b;
    }
    void floyd()
    {
        fp(k,1,v) fp(i,1,v) fp(j,1,v)
            if (k!=i&&i!=j&&j!=k)
             mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);     
    }
    double D(int x,int y){return mp[x][y];}
    signed main()
    {
        scanf("%d%d%d%d",&n,&m,&v,&e);
        fp(i,1,n) scanf("%d",&c[i]);
        fp(i,1,n) scanf("%d",&d[i]);
        fp(i,1,n) scanf("%lf",&k[i]);
        fp(i,0,v) fp(j,0,v) mp[i][j]=(i==j||i==0||j==0?0:INF);
        fp(i,1,e) {
            int u,v; double w;
            scanf("%d%d%lf",&u,&v,&w);
            mp[u][v]=mp[v][u]=Min(mp[u][v],w);
        }
        floyd();
        fp(i,0,n) fp(j,0,m) f[i][j][0]=f[i][j][1]=INF;
        f[1][0][0]=0.0; f[1][1][1]=0.0;
        fp(i,2,n) {
            f[i][0][0]=f[i-1][0][0]+D(c[i-1],c[i]);
            fp(j,1,min(i,m)) {
                f[i][j][0]=Min(f[i-1][j][0]+D(c[i-1],c[i]),f[i-1][j][1]+k[i-1]*D(d[i-1],c[i])+(1-k[i-1])*D(c[i-1],c[i]));//i没尝试去换
                f[i][j][1]=Min(f[i-1][j-1][0]+k[i]*D(c[i-1],d[i])+(1-k[i])*D(c[i-1],c[i]),
                               f[i-1][j-1][1]+k[i-1]*k[i]*D(d[i-1],d[i])+k[i-1]*(1-k[i])*D(d[i-1],c[i])+(1-k[i-1])*k[i]*D(c[i-1],d[i])+(1-k[i-1])*(1-k[i])*D(c[i-1],c[i]));
            }    
        }
        double ans=INF;
        fp(i,0,m) ans=Min(ans,Min(f[n][i][0],f[n][i][1]));
        printf("%.2lf
    ",ans); 
        return 0;
    }
    P1850 换教室

     P1941 飞扬的小鸟

    有n列m行的网格左下角是(0,0),小鸟每一单位时间横坐标右移1单位,纵坐标在时刻i(出发时为0时刻)有两种情况

    • 1. 自由下落,纵坐标下降Y[i];
    • 2. 花费k$(k >0)$的代价上升$k imes$X[i]。

    注意一点:当点k次后小鸟高度超过m那么会停留在上边缘。

    有k个管道,给出每个管道位置$p_i$,下表面距离x轴高度$L_i$,上表面距离x轴高度$H_i$

    管道覆盖处(下表面高度以下,上表面高度以上)、地面(即x轴)不能触碰。

    此时会有两种可能,

    • 若从0列任意位置出发不可能到达n列,输出0 ,然后输出最多通过管道数
    • 若从0列任意位置出发能够到达n列,输出1 ,然后输出最小代价(点击次数)

    对于70%的数据,$ 5 leq nleq 10^3 ,5 leq mleq 10^2$

    对于85%的数据,$X[i],Y[i]$较为随机,且开启O2常数优化

    对于100%的数据,$5 leq n leq 10^4$, $5 leq m leq 10^3$,$0 leq k < n$, $0 < X < m$, $0 < Y < m$, $0 < P < n$, $0 leq L < H leq m$, $L + 1 < H$

    01背包和完全背包综合体。

    我们会有一个简单的想法,暴力转移,对于以左下角作为(0,0)点的坐标系来说从i-1列到i列可以有两种转移方式。

    记f[i][j]表示小鸟到(i,j)位置(即i列j高度)最小代价。

    对于一般情况($j eq m$),显然是从$f[i-1][j+Y[i-1]]$和$f[i-1][j-k imes X[i-1] ]+k$转移而来。

    对于特殊情况($j=m$),只能从i-1列m及以下行某一处跳至少1次到达,跳1次跳到顶部$f[i-1][j-k]+1(k in [j-X[i-1],m])$,跳2次以上跳到顶部$f[i][j-k]+1(k in [j-X[i-1],m])$,即这次跳到(i,j-X[i-1])最小代价了如果再跳1次一定可以到达顶部。

    复杂度O(knm),显然k取决于数据的随机程度,若给出X[i-1]较小那么程序复杂度会较大。

    对于本题即使数据已经比较随机,给出X[]和Y[]以上算法在开启O2优化的情况下能够得70-85分

    开启O2优化85分代码: 

    // luogu-judger-enable-o2
    # include <bits/stdc++.h>
    # define inf (0x3f3f3f3f)
    using namespace std;
    const int N=1e4+500,M=1e3+500;
    int f[N][M],down[N],up[N],x[N],y[N],pou[N];
    int n,m,p;
    inline int read()
    {
        int X=0,w=0; char c=0;
        while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
        while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
        return w?-X:X;
    }
    int main()
    {
        n=read();m=read();p=read();
        for (int i=0;i<n;i++)
         x[i]=read(),y[i]=read();
        for (int i=0;i<=n;i++) down[i]=0,up[i]=m+1;
        for (int i=1;i<=p;i++) {
            int pos=read();
            down[pos]=read(); up[pos]=read();
            pou[pos]=1;
        }
        memset(f,0x3f,sizeof(f));
        for (int i=0;i<=m;i++) f[0][i]=0;
        int rp;
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=m;j++) {
                 if (j==m) {
                    for (int k=m-x[i-1];k<=m;k++) {
                           f[i][m]=min(f[i][m],f[i-1][k]+1);
                           f[i][m]=min(f[i][m],f[i][k]+1);
                       }
                }
                int k=1;
                 while (true) {
                     if (j-k*x[i-1]<=0||f[i][j]<=k) break;
                     f[i][j]=min(f[i][j],f[i-1][j-k*x[i-1]]+k);
                     k++;
                }
            }
            for (int j=1;j<=m;j++)
                if (j+y[i-1]<=m) f[i][j]=min(f[i][j],f[i-1][j+y[i-1]]);
            for (int j=1;j<=down[i];j++) f[i][j]=inf;
            for (int j=up[i];j<=m;j++) f[i][j]=inf;
            for (int j=1;j<=m;j++) if (f[i][j]!=inf) rp=i;
        }
        int Ans=inf;
        for (int i=1;i<=m;i++) Ans=min(Ans,f[n][i]);
        if (Ans!=inf) {
            printf("1
    %d
    ",Ans);
            return 0;
        }
        Ans=0;
        for (int i=1;i<=rp;i++)
         if (pou[i]==1) Ans++;
        printf("0
    %d
    ",Ans);
        return 0;
    }
    P1941 飞扬的小鸟-85pts

    考虑优化常数k,主要是做背包的时候枚举了个数k,可以考虑使用上述特判处的思想,从i这一列已经转移成功的那一行转移过来代替枚举的k,充分利用求得的东西,即f[i][j]从f[i][j-X[i-1]]+1转移过来,即跳到(i,j-X[i-1])已经最小化步数,再多点1次就跳到(i,j)了。

    复杂度O(nm)

    注意上述dp需要注意边界问题,不要出现负数下标,即使排除不可能决策的转移方案。

    然后最后赋值水管位置为inf,两个背包分开来转移。

    不开启O2优化100分代码: 

    # include <bits/stdc++.h>
    # define inf (0x3f3f3f3f)
    using namespace std;
    const int N=1e4+50,M=1e3+50;
    int f[N][M],down[N],up[N],x[N],y[N],pou[N];
    int n,m,p;
    inline int read()
    {
        int X=0,w=0; char c=0;
        while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
        while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
        return w?-X:X;
    }
    int main()
    {
        n=read();m=read();p=read();
        for (int i=0;i<n;i++)
         x[i]=read(),y[i]=read();
        for (int i=0;i<=n;i++) down[i]=0,up[i]=m+1;
        for (int i=1;i<=p;i++) {
            int pos=read();
            down[pos]=read(); up[pos]=read();
            pou[pos]=1;
        }
        memset(f,0x3f,sizeof(f));
        for (int i=0;i<=m;i++) f[0][i]=0;
        int rp;
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=m;j++) {
                 if (j==m) {
                       for (int k=m-x[i-1];k<=m;k++) {
                        f[i][m]=min(f[i][m],f[i-1][k]+1);
                        f[i][m]=min(f[i][m],f[i][k]+1);
                       }
                 }
                 if (j-x[i-1]>0) f[i][j]=min(f[i][j],f[i-1][j-x[i-1]]+1);
                 if (j-x[i-1]>0) f[i][j]=min(f[i][j],f[i][j-x[i-1]]+1);
            }
            for (int j=1;j<=m;j++)
                if (j+y[i-1]<=m) f[i][j]=min(f[i][j],f[i-1][j+y[i-1]]);
            for (int j=1;j<=down[i];j++) f[i][j]=inf;
            for (int j=up[i];j<=m;j++) f[i][j]=inf;
            for (int j=1;j<=m;j++) if (f[i][j]!=inf) rp=i;
        }
        int Ans=inf;
        for (int i=1;i<=m;i++) Ans=min(Ans,f[n][i]);
        if (Ans<inf) {
            printf("1
    %d
    ",Ans);
            return 0;
        }
        Ans=0;
        for (int i=1;i<=rp;i++)
         if (pou[i]==1) Ans++;
        printf("0
    %d
    ",Ans);
        return 0;
    }
    P1941 飞扬的小鸟-100pts
  • 相关阅读:
    Ubuntu中开启Telnet服务
    单片机串口通讯RXD与TXD如何对接详解
    KEIL, a Smart Comliler
    linux mail 命令(转载)
    VC程序在没装VC的机器中运行
    srand()以及rand()函数用法
    VC菜菜鸟:基于CFree的HelloWorld程序
    Keil使用中的若干问题(转)
    VMWARE 重新安装出错
    Linux个人学习笔记(编辑中)
  • 原文地址:https://www.cnblogs.com/ljc20020730/p/10428131.html
Copyright © 2011-2022 走看看