動態規劃考試test20170429

前言

這次考試整體來言還是不錯的,但是還是存在一些問題,就第三題而言考試時並沒有深入思考,然後暴力還掛了。

試題

【道路修建】(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的思想似乎有了一種新的突破,但是還需繼續努力。

發佈了39 篇原創文章 · 獲贊 7 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章