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]) << '\n';
    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:

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

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

 

转载于:https://www.cnblogs.com/Loizbq/p/7795428.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值