• 20171105模拟题


    T1

    DP

     

    Problem 1 :第一题(a.cpp)

     

     

     

    题目描述

     

    一个序列上共有n个点,两两之间形成一条线段,任意选择其中k条线段,使得这k条线段的长度和最小(k条线段端点不能重合)。

     

    输入格式 

     

    第一行两个整数n k

     

    接下来n行每行一个整数表示每个点在序列上的坐标 。
    输出格式 

     

    一行一个整数表示这个最小的和。

     

    样例输入

     

    5 2

     

    1

     

    3

     

    4

     

    6

     

    12

     

    样例输出

     

    4
    数据范围及提示

     

    对于30%数据

     

    对于60%数据

     

    对于100%数据

     

     

    A:哇,好水啊。

    B:哇,是啊。

    A:那还把我们叫出来干嘛。

    B:走人走人。

    END.

    由于小A小B不想分析水题(QAQ),

    我就来口糊一下~

    很明显可以看出只能取相邻点组成的线段

    而且不能取相邻线段(端点不能重复)

    像不像没有上司的舞会?

    不过更简单,在链上了。

    每条线段选与不选都会影响后面的状态

    所以选和不选的答案都记录下来

    怎么区分?

    0 or 1 !

    如果这条线段选了,上一条只能是不选,答案加上这条线段的长度。

    这条不选,上一条选和不选都有可能

    一直递推。

    设计状态:

    f[i][j][0/1]:到第i条,选了j条,第i条选或不选的min

    根据上面说的,递推式如下:

            f[i][j][1] = f[i - 1][j - 1][0] + len[i];
            f[i][j][0] = min(f[i - 1][j][0],f[i - 1][j][1]);        

    我们发现第i层一定是由i - 1层转移过来的,滚动了吧。

    T1就可以A掉了。

    Codes:

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    
    using namespace std;
    const int N = 10000 + 10;
    int n,k,cn;
    int f[5000 + 10][2],g[5000 + 10][2],len[N],a[N];
    int main(){
        scanf("%d%d",&n,&k);
        memset(f,0x3f3f3f3f,sizeof(f));
        memset(g,0x3f3f3f3f,sizeof(g));
        for(int i = 1;i <= n;++ i){
            scanf("%d",&a[i]);
            if(i > 1){
                len[++ cn] = a[i] - a[i - 1];
            }
        }
        f[1][1] = len[1];
        f[0][0] = f[0][1] = g[0][0] = g[0][1] = 0;
        for(int i = 1;i <= cn;++ i){
            for(int j = 1;j <= k;++ j){
                f[j][1] = g[j - 1][0] + len[i];
                f[j][0] = min(g[j][0],g[j][1]);
            }
            for(int j = 1;j <= k;++ j){
                g[j][1] = f[j][1];
                g[j][0] = f[j][0];
            }
        }
        cout << min(f[k][0],f[k][1]) << '
    ';
        return 0;
    }

    你以为这样就结束了?

    Don't 天真。

    原题可不是这么好欺负的。

    贪心

    题目链接:http://codevs.cn/problem/1615/

     

    A:诶,这题似曾相识?

    B:这不就是上面那个么?

    A:数据数据!

    60%的输入数据满足n≤10 000。

    B:哇……这怎么办……dp%60。

    A:还记得上次的模拟题么?(好吧并没有写:http://codevs.cn/problem/2033/)

    B:贪心?对哦,那个题记得dp只能过%60,好像哦。

    A:那个题是当选和不选都会对答案做出贡献时,也是都记录答案,贪心的话,怎么都记录答案呢?

    B:当时是用堆顶处理出的答案记录一下,然后把删掉的数再扔进去一遍!!

    A:这样就可以都记录下来了!用贪心的方式。这题类似。

    END.

    哇哦……又一个高级贪心QAQ

    容易想到的做法是每次选最小的线段,

    但明显在很多情况下是不对的,

    比如样例,选了最小的反而不能选相比6 + 1更优的2 + 2,

    所以想到dp

    要把选和不选最小的答案都存起来

    类似邮票那个题,

    最小值用来维护,

    每次取出堆顶更新答案,

    如果选了堆顶,它两边的就不能选啦,

    双向链表维护点之间的关系(用来找到它两边的且没有被删除的点),

    就把这个点的pre,nxt更新成隔一个相邻的

    再把与他相邻的点的pre,nxt也更新成与这个点隔一个相邻的。

    举个例子:

    序列:d b a c e

    堆顶是a,然后取出a,更新答案(ans += 堆顶),

    放进去一个b + c - a,(想想为什么减a)

    然后将a的nxt,pre更新成d,e,

    将b到c看成一个点,nxt,pre也更新成d,e。

    (说得好乱啊啊啊,还是看代码更清晰QAQ)

    代码是这样:

         A[++ cnt] = u.v;
            int tmp1 = pre[u.pos],tmp2 = next[u.pos];
            next[pre[tmp1]] = cnt;
            pre[next[tmp2]] = cnt;
            pre[cnt] = pre[tmp1];
            next[cnt] = next[tmp2];

    (把b到c看成一个点,这个新加进去的点的编号是cnt)

    将它相邻的点标记一下(不能再选)

    就是这样:

        del[tmp1] = del[tmp2] = del[u.pos] = true;

     

    把链表的头指针(空)和尾指针(空)位置的答案设为inf

    因为当堆顶刚好是第一个点或者最后一个点时,

    相邻的点不可能有两个(这里只找两边,不符合就放堆底,不会影响更新除了inf外的另一个),

    就直接放在堆底不管就好啦。

    QAQ为什么觉得越说越乱?还是看看代码吧。

    (不想写了,所以很不要脸的贴上lxy大神的Codes,而且还很不要脸的改了码风

    Codes:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<queue>
    #define LL long long
    #define INF 1061109567
    using namespace std;
    const int MAXN = 1000000 + 50;
    LL next[MAXN],pre[MAXN],A[MAXN],D[MAXN];
    struct zt{
        LL pos;
        LL cost,v;
    }l[MAXN];
    bool operator < (zt a,zt b){
        return a.v > b.v;
    }
    priority_queue <zt> q;
    LL n,m,k,cnt;
    int first;
    LL tot,ans,now;
    bool del[MAXN];
    int main(){
        scanf("%lld%lld",&n,&m);
        scanf("%lld",&D[1]);
        for(int i = 2;i <= n;i ++){
            scanf("%lld",&D[i]);
            A[++ cnt] = D[i] - D[i - 1];
        }
        for(int i = 1;i <= cnt;i ++){
            pre[i] = i - 1;
            next[i - 1] = i;
            q.push((zt){i,1,A[i]});
        }
        pre[0] = -1;
        next[cnt] = cnt + 1;
        first = 0;
        A[0] = INF;
        A[cnt + 1] = INF;
        cnt ++;
        while(!q.empty()){
            zt u = q.top();
            q.pop();
            if(del[u.pos])continue;
            tot += u.cost;
            now += u.v;
            if(tot == m){
                ans = now;
                break;
            }
            u.v = -u.v;
            u.v += A[pre[u.pos]] + A[next[u.pos]];
            A[++ cnt] = u.v;
            int tmp1 = pre[u.pos],tmp2 = next[u.pos];
            next[pre[tmp1]] = cnt;
            pre[next[tmp2]] = cnt;
            pre[cnt] = pre[tmp1];
            next[cnt] = next[tmp2];
            del[tmp1] = del[tmp2] = del[u.pos] = true;
            u.pos = cnt;
            q.push(u);
        }
        printf("%lld",ans);
    }

    T2

    DP

    //QAQ状压是什么我不知道

    Problem 2 :第二题(b.cpp)

    题目描述

    给定一个n+1个点(编号为0,1,2..n)的有向图,求从0号点出发,经过所有城市至少一次,且最后回到0号点的最短路。

    输入格式 
    一行一个整数 n

    接下来一个(n + 1)* (n + 1)的邻接矩阵表示城市两两之间的路径(a到b的路径长度不一定等于b到a的路径长度)

    输出格式 

    一个整数表示最短路径。

    样例输入

    3

    0 1 10 10

    1 0 1 2

    10 1 0 10

    10 2 10 0

    样例输出

    8


    数据范围及提示

     1 <= n <= 15

     A:这个题……搜索能过么?

    B:好像可以记忆化。

    A:那就dp?

     END.

    //再次惊叹小A小B分析问题的简洁……

    首先,求最短路嘛,

    很容易想到spfa,边跑最短路边记录边数。

    可行。

    具体怎么实现呢。

     实际上多在队列里存一个变量就好啦~

    不是要求所有点都走过么,

    这样我们就需要知道哪些点走过了,哪些点没走过,

    天哪,我们需要记录的是一个状态

    怎么记录?又快又好用的二进制

    n个点,我就用二进制存n个位置,起始都是0,

    走过把0改成1,这样都走过的状态时什么呢?

    当二进制数为(1 << n) - 1的时候。

    这样最后输出的是dis[0][(1 << n) - 1]嘛。

    复制一波代码,

    Codes:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    int n,tot;
    int dis[20][140000];
    bool done[20][140000];
    int first[20],nxt[1000];
    struct edge{
        int f,t,v;
    }l[1000];
    void build(int f,int t,int v){
        l[++ tot] = (edge){f,t,v};
        nxt[tot] = first[f];
        first[f] = tot;
    }
    struct zt{
        int num , ok;
    };
    queue<zt>q;
    void spfa(){
        q.push((zt){1,2});done[1][2] = 1,dis[1][2] = 0;
        while(!q.empty()){
            zt a = q.front(); q.pop();
            int u = a.num,now = a.ok;done[u][now] = 0;
            for(int i = first[u] ; ~i ; i = nxt[i]){
                int v = l[i].t;
                if(now&(1<<v)){
                    if(dis[v][now] > dis[u][now] + l[i].v){
                        dis[v][now] = dis[u][now] + l[i].v;
                        if(!done[v][now]){
                            q.push((zt){v,now});
                            done[v][now] = 1;
                        }
                    }
                }
                else {
                    if(dis[v][now+(1<<v)] > dis[u][now] + l[i].v){
                        dis[v][now+(1<<v)] = dis[u][now] + l[i].v;
                        if(!done[v][now+(1<<v)]){
                            q.push((zt){v,now+(1<<v)});
                            done[v][now+(1<<v)] = 1;
                        }
                    }
                }
            }
        }
    }
    int main(){
        freopen("b.in","r",stdin);
        freopen("b.out","w",stdout);
        memset(dis,0x3f,sizeof(dis));
        memset(first,-1,sizeof(first));
        scanf("%d",&n);int x;
        for(int i = 1 ; i <= n + 1 ; i ++)
            for(int j = 1 ; j <= n + 1; j ++){
                scanf("%d",&x);
                if(i != j) build(i,j,x);
            }
        spfa();int end=0;
        for(int i = 1 ; i <= n + 1; i ++)
            end += (1<<i);
        printf("%d",dis[1][end]);
        return 0;
    }

    忘了忘了,还有一种解法,

    就是小A小B说的那种,

    dp!!

    f[i][j]:状态为i,到j时的答案。

    那方程就很好推啦~

    f[i|(1<<k-1)][k]=min(f[i|(1<<k-1)][k],f[i][j]+dis[j][k]);

    std Codes:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    int dis[20][20],f[1<<15][20];
    
    int main()
    {
        freopen("b.in","r",stdin);
        freopen("b.out","w",stdout);
        int n;
        scanf("%d",&n);
        for(int i=0;i<=n;++i)
            for(int j=0;j<=n;++j)
                scanf("%d",&dis[i][j]);
        for(int k=0;k<=n;++k)
            for(int i=0;i<=n;++i)
                for(int j=0;j<=n;++j)
                    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
        memset(f,63,sizeof f);
        f[0][0]=0;
        int ful=(1<<n)-1;
        for(int i=0;i<=ful;++i)
        {
            for(int j=0;j<=n;++j)
                if(f[i][j]!=INF)
                {
                    for(int k=1;k<=n;++k)
                        f[i|(1<<k-1)][k]=min(f[i|(1<<k-1)][k],f[i][j]+dis[j][k]);
                }
        }
        int ans=INF;
        for(int i=1;i<=n;++i)
            ans=min(ans,f[ful][i]+dis[i][0]);
        printf("%d",ans);
        return 0;
    }

    T3……不整理了。因为不会。

    MAS:

    这个时候我还能说要好好看题么!!!!

    好好看题好好看题好好看题好好看题好好看题好好看题!!!

  • 相关阅读:
    GitLab 自动触发 Jenkins 构建
    微服务监控探索
    使用QUIC
    非对称加密与证书(上篇)
    Vue框架核心之数据劫持
    如何实现最佳的跨平台游戏体验?Unity成亮解密实时渲染
    网易易盾验证码的安全策略
    CodeForces 339C Xenia and Weights(暴力求解DFS)
    CodeForces 339D Xenia and Bit Operations (线段树)
    CodeForces 339B Xenia and Ringroad(水题模拟)
  • 原文地址:https://www.cnblogs.com/Loizbq/p/7795428.html
Copyright © 2020-2023  润新知