动态规划考试test20170429

17 篇文章 0 订阅

前言

这次考试整体来言还是不错的,但是还是存在一些问题,就第三题而言考试时并没有深入思考,然后暴力还挂了。

试题

【道路修建】(Road.pas/c/cpp Time:1.2s Memory:256M)

【问题描述】

现在在 LJY 星球上有 N 个城市。LJY 为了使各个国家的经济发展,决定在
各个国家之间建设双向道路使得国家之间连通。但是 LJY 很吝啬,只愿意修建
恰好 n–1 条双向道路。每条道路的修建都要付出一定的费用,这个费用等于道
路长度乘以道路两端的国家个数之差的绝对值。例如,在下图中,虚线所示道路
两端分别有 2 个、4 个国家,如果该道路长度为 1,则费用为 1×|2 – 4|=2。图
中圆圈里的数字表示国家的编号。
由于国家的数量十分庞大,道路的建造方案有很多种,同时每种方案的修建
费用难以用人工计算,LJY 决定找人设计一个软件,对于给定的建造方案,计算
出所需要的费用。请你帮助 LJY 设计一个这样的软件。

【输入】

输入文件名为 Road.in。
输入第一行为一个正整数 N,代表总的点数的个数。
下接 N-1 行,每行三个正整数 ai、bi、ci,代表有一条长度为 ci 的双向道路
连接 ai 和 bi 两个国家之间。

【输出】

输出文件名为 Road.txt。
输出一行一个正整数,为修建所有道路的总费用。

【输入输出样例】

Road.in
6
1 2 1
1 3 1
1 4 2
6 3 1
5 2 1
Road.txt
20

【数据范围】

对于 40%的数据, N1000
对于 70%的数据, N100000
对于 100%的数据, N1000000
不允许开开关。

【题解】

原题竟然是NOI2011,但是这道题真的很水
我们可以直接以某个点为根节点,然后将对这个树进行一次搜索遍历。那么显然我们可以算出每个以每个点为根节点的子树包含的点数,简单的记为 Si
那么,连接第I个点的树边连接城市之差就是 |NSI2| ,记住,这里的点不包括根节点。
那么进行一次简单的统计即可。
但是本题直接dfs搜索会爆掉栈空间,但是测评时全机房决定还是开大栈空间(似乎大部分人打的dfs,我当时怕爆栈就打了bfs(虽然有点慢))。

【代码】

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;

const int maxn = 2000000+20;
int point[maxn],nxt[maxn],g[maxn],val[maxn];
int p[maxn],f[maxn],fx[maxn],s[maxn];
bool v[maxn];
int n,tot;

inline int read() {
    int in=0,f=1;
    char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())
        if(ch=='-')
            f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())
        in=in*10+ch-'0';
    return in*f;
}

inline void add_edge(int x,int y,int z) {
    point[++tot]=y;nxt[tot]=g[x];g[x]=tot;val[tot]=z;
}

int main() {
    freopen("road.in","r",stdin);
    freopen("road.out","w",stdout);
    n=read();
    for(int i=1;i<n;i++) {
        int x=read(),y=read(),z=read();
        add_edge(x,y,z);add_edge(y,x,z);
    }
    p[1]=1;v[1]=true;
    for(int i=0,j=1;i<=j;i++) {
        for(int k=g[p[i]];k!=0;k=nxt[k]) {
            if(v[point[k]]==false) {
                v[point[k]]=true;fx[point[k]]=val[k];
                f[point[k]]=p[i];p[++j]=point[k];
            }
        }
    }
    for(int i=n;i>=1;i--) ++s[p[i]],s[f[p[i]]]+=s[p[i]];
    LL ans=0;
    for(int i=1;i<=n;i++) {
        ans+=((LL)fx[i])*(abs((LL)n-((s[i])<<1)));
    }
    printf("%lld\n",ans);
    return 0;
}

【迷宫巡回】(maze.pas/c/cpp Time:1s Memory:256M)

【问题描述】

现在有一个 N*M 的迷宫,LJY 处在第一行第一列这个位置,也就是起点上,迷宫的补
给点在(N,M)。这个迷宫中的每个格子都有一个激情度,也就是说,LJY 走到这个格子
便可以获得这个格子上的激情度。但是,走过一遍的格子便没有激情度了。所以,LJY 为了
获得最大的激情度,便不希望走到同一个格子上,除了起点。首先,LJY 会从起点走到补给
点,此时,LJY 只能向下或者向右运动到相邻的格子。到了补给点之后,LJY 又从补给点开
始,向上或者向左运动到相邻的格子,一直到起点。当然,万一 LJY 走到了迷宫之外,他
就挂定了,所以他绝对不会走到迷宫之外的。现在 LJY 想知道,自己巡回一遍迷宫之后,
能获得的最大的激情度有多少?

【输入】

输入文件名为 maze.in。
输入一行两个正整数 N 和 M,代表迷宫的行数和列数。
下接一个 N 行 M 列的矩阵,其中第 I 行第 J 列的数代表游历这个格子的激情度。

【输出】

输出文件名为 maze.txt。
输出一行一个正整数,代表 LJY 巡回一遍迷宫能获得的最大的激情度。

【输入输出样例】

maze.in
3 3
0 3 9
2 8 5
5 7 0
maze.txt
34

【数据范围】

对于 30%的数据, 1N,M10
对于 100%的数据, 1N,M50
保证每个格子的激情度均为不大于 100 的非负整数。

【题解】

刚开始打了个暴力,把题目大概意思搞清楚了。
然后发现人都只能向下和向右走。
所以我们可以用dp的思想,用一个三维数组 F[i][j][k] 代表这两个人走到了第I条斜线时,第一个人在第J列,第二个人在第K列时能获得的最大激情度。

F[i][j][k]+Wij+1+Wik+1>F[i][j+1][k+1]

F[i][j][k]+Wij+Wik+1>F[i][j][k+1]

F[i][j][k]+Wij+1+Wik>F[i][j+1][k]

F[i][j][k]+Wij+Wik>F[i][j][k]

【代码】

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;

const int size = 50+5;
int f[size<<1][size][size],w[size][size];
int n,m;

inline int read() {
    int in=0,f=1;
    char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())
        if(ch=='-')
            f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())
        in=in*10+ch-'0';
    return in*f;
}

namespace my_dp{
    inline int cal(int x,int y) {
        x-=y;
        if (x<=0||x>n) return -1;
        return w[x][y];
    }

    inline void work() {
        f[3][1][2]=w[1][1]+w[1][2]+w[2][1];
        for(int i=3;i<=m+n;i++) {
            for(int x=1;x<=m;x++) {
                for(int y=x+1;y<=m;y++) {
                    // dp_start-------------------------
                    int sta=cal(i,x),stb=cal(i,y);
                    if(sta<0 or stb<0) continue;
                    // dp_1-------------------------
                    sta=cal(i+1,x);stb=cal(i+1,y);
                    if(sta>=0 and stb>=0 and sta+stb+f[i][x][y]>f[i+1][x][y])
                        f[i+1][x][y]=f[i][x][y]+sta+stb;
                    // dp_2-------------------------
                    sta=cal(i+1,x);stb=cal(i+1,y+1);
                    if(sta>=0 and stb>=0 and sta+stb+f[i][x][y]>f[i+1][x][y+1])
                        f[i+1][x][y+1]=f[i][x][y]+sta+stb;
                    // dp_3-------------------------
                    sta=cal(i+1,x+1);stb=cal(i+1,y);
                    if(sta>=0 and stb>=0 and sta+stb+f[i][x][y]>f[i+1][x+1][y])
                        f[i+1][x+1][y]=f[i][x][y]+sta+stb;
                    // dp_4-------------------------
                    sta=cal(i+1,x+1);stb=cal(i+1,y+1);
                    if(sta>=0 and stb>=0 and sta+stb+f[i][x][y]>f[i+1][x+1][y+1])
                        f[i+1][x+1][y+1]=f[i][x][y]+sta+stb;
                    // dp_end-------------------------
                }
            }
        }
        printf("%d\n",f[n+m-1][m-1][m]+w[n][m]);
    }
}

int main() {
    freopen("maze.in","r",stdin);
    freopen("maze.out","w",stdout);
    n=read();m=read();
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            w[i][j]=read();
        }
    }
    //my_bfs::print();
    my_dp::work();
    return 0;
}

【单词矩阵】(twofive.pas/c/cpp Time:1s Memory:256M)

【问题描述】

LJY开始研究英语的单词。对于包含字母A到Y各一次的单词S,将其从上到下从左到右写
在一个5*5的矩阵中,如单词ADJPTBEKQUCGLRVFINSWHMOXY写出来如下:
合法的
A D J P T
A D J P T
B E K Q U
B E G Q U
C G L R V
不合法的
C K L R V
F I N S W
F I N S W
H M O X Y
H M O X Y
若该矩阵满足每一行每一列的字母都是字典序递增的则满足LJY对优美的要求,如上述
单词就是优美的,而ADJPTBEGQUCKLRVFINSWHMOXY则不是(第二列不满足要求)。
LJY将所有优美的单词按字典序列出,从小到大编号1,2,……
请你完成以下两种任务:
1. 给定一个优美的单词,求其编号。
2. 给定一个编号,求对应的优美的单词。

【输入】

输入文件名为twofive.in。
输入第一行一个字母,W表示任务1,N表示任务2。
若是任务1,第二行是一个优美的单词,否则第二行是一个正整数,表示某个优美的单
词的编号,保证该数不超过优美的单词的总数。

【输出】

输出文件名为twofive.txt。
输出仅一行,若是任务1,输出对应编号,否则输出对应的优美的单词

【输入输出样例】

twofive.in
W
ABCDEFGHIJKLMNOPQRSUTVWXY
twofive.txt
2
twofive.in
N
20
twofive.txt
ABCDEFGHIJKLMNOPQSUWRTVXY

【数据范围】

保证数据合法。保证数据有梯度。

【题解】

(来源于网络)
 以下叙述中,“单词”均指合法单词。
  举个例子说明:若为单词转编码,如求单词ACF……的编码,则设一累加器,先累加以AB开头的单词的个数,再累加以ACB开头的单词的个数(这个数为0,但若已知6个字母的位置,B拐到了第2行,则可能不为0),再累加以ACD开头的单词的个数,再累加以ACE开头的单词的个数……最后加1即得答案。若为编码转单词,如求第n个单词,同样设一累加器s,先累加以AB开头的单词的个数,若 sn 了,说明第二个字母就是B,否则继续累加以AC开头的单词的个数……直到 sn ,这样第二个字母就确定了。将最后一次累加的数减去,用类似的方法确定第三、第四……个字母,直至结束。
  现在的问题是:如何求出以某给定序列开头的单词的个数?这个问题是用记忆化搜索解决的。用 f[a,b,c,d,e](5>=a>=b>=c>=d>=e>=0) 表示把前 a+b+c+d+e 个字母填入第1行的前a个格,第2行的前b个格……第5行的前e个格,且已经确定位置的字母各就各位时可能的单词数,那么 f[0,0,0,0,0] 就表示以给定序列开头的单词数。下面以求以AC开头的单词数为例说明递归求f数组的方法:
第一层递归安置字母A。因其位置已固定,故 f[0,0,0,0,0]=f[1,0,0,0,0] ,进入第二层递归计算 f[1,0,0,0,0]
第二层递归安置字母B。B的位置尚未固定,于是枚举所有合法位置(合法位置指左、上方均已填有字母的位置,认为第0行与第0列均已填满。此例中为12、21),分别进入第三层递归计算 f[2,0,0,0,0] (这个值等于0,下面会讨论其原因)与 f[1,1,0,0,0] f[1,0,0,0,0] 即等于这二者之和。
第三层递归安置字母C。这层递归的过程与第一层递归类似。更深层递归的过程与第二层递归类似。若在某一次递归中,需要计算的f值已经算出,则不必再递归下去,直接退出即可。

【代码】

#include<cstdio>
const char alphabet[]="ABCDEFGHIJKLMNOPQRSTUVWXY";
using namespace std;
int f[6][6][6][6][6],row[6],xpos[30],ypos[30];
int state[26][50][6];
char task,s[30],ans[30];
int snum[26];
int a,b,c,d,e,ANSWER=1;
inline int DP()
{
       f[5][5][5][5][5]=1;
       for (int sum=24;sum>=0;sum--)
         for (int nows=1;nows<=snum[sum];nows++)
         {
             row[1]=state[sum][nows][1];
             row[2]=state[sum][nows][2];
             row[3]=state[sum][nows][3];
             row[4]=state[sum][nows][4];
             row[5]=state[sum][nows][5];
             row[0]=5;
             a=row[1];b=row[2];c=row[3];d=row[4];e=row[5];
             f[a][b][c][d][e]=0;
             if (!xpos[sum])
             {
                            if (a<5) f[a][b][c][d][e]+=f[a+1][b][c][d][e];
                            if (b<a) f[a][b][c][d][e]+=f[a][b+1][c][d][e];
                            if (c<b) f[a][b][c][d][e]+=f[a][b][c+1][d][e];
                            if (d<c) f[a][b][c][d][e]+=f[a][b][c][d+1][e];
                            if (e<d) f[a][b][c][d][e]+=f[a][b][c][d][e+1];
             }
             else
             {
                 if (row[xpos[sum]]<row[xpos[sum]-1]&&row[xpos[sum]]+1==ypos[sum])
                 {
                      ++row[xpos[sum]];
                      f[a][b][c][d][e]+=f[row[1]][row[2]][row[3]][row[4]][row[5]];
                 }
             }
         }
       return f[0][0][0][0][0];
}

inline void init()
{
         for (int i1=0;i1<6;i1++)
           for (int i2=0;i2<=i1;i2++)
             for (int i3=0;i3<=i2;i3++)
               for (int i4=0;i4<=i3;i4++)
                 for (int i5=0;i5<=i4;i5++)
                 {
                     int sum=i1+i2+i3+i4+i5;
                     ++snum[sum];
                     state[sum][snum[sum]][1]=i1;
                     state[sum][snum[sum]][2]=i2;
                     state[sum][snum[sum]][3]=i3;
                     state[sum][snum[sum]][4]=i4;
                     state[sum][snum[sum]][5]=i5;
                 }     
}

void num2word()
{
     for (int i=0;i<25;i++) 
     {
         xpos[i]=0;ypos[i]=0;
     }
     int n;
     scanf("%d",&n);
     for (int i=1;i<6;i++)
       for (int j=1;j<6;j++)
         for (int ch=0;ch<25;ch++) 
           if (!xpos[ch])
           {
                           xpos[ch]=i;ypos[ch]=j;
                           int temp=DP();
                           if (n>temp) n-=temp;
                           else break;
                           xpos[ch]=0;ypos[ch]=0;
           }
     for (int i=0;i<25;i++)
       ans[(xpos[i]-1)*5+ypos[i]-1]=alphabet[i];
     printf("%s\n",&ans);
}

int word2num()
{
    char tmp;
    scanf("%c",&tmp);
    for (int i=1;i<6;i++)
      for (int j=1;j<6;j++)
      {
          scanf("%c",&tmp);
          int nowch=tmp-65;
          for (int ch=0;ch<nowch;ch++)
            if (!xpos[ch])
            {
                          xpos[ch]=i;ypos[ch]=j;
                          ANSWER+=DP();
                          xpos[ch]=0;ypos[ch]=0;
            }
          xpos[nowch]=i;ypos[nowch]=j;
      }
      return ANSWER;
}

int main()
{
    freopen("twofive.in","r",stdin);
    freopen("twofive.out","w",stdout);
    init();
    scanf("%c",&task);
    if (task=='N') num2word();
    else printf("%d\n",word2num());
    return 0;
}

总结

这次考试题目除了最后一题外整体还是不太难的,最后一题的确是一道好题,而且让我对dp的思想似乎有了一种新的突破,但是还需继续努力。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值