NOIP2016提高組Day1 真題測試

T1:玩具謎題

題目描述
小南有一套可愛的玩具小人,它們各有不同的職業。

有一天,這些玩具小人把小南的眼鏡藏了起來。小南發現玩具小人們圍成了一個圈,它們有的面朝圈內,有的面朝圈外。如下圖:
這裏寫圖片描述

這時 singer 告訴小南一個謎題:“眼鏡藏在我左數第 3 個玩具小人的右數第 1 個玩具小人的左數第 2 個玩具小人那裏。”

小南發現,這個謎題中玩具小人的朝向非常關鍵,因爲朝內和朝外的玩具小人的左右方向是相反的:面朝圈內的玩具小人,它的左邊是順時針方向,右邊是逆時針方向;而面向圈外的玩具小人,它的左邊是逆時針方向,右邊是順時針方向。

小南一邊艱難地辨認着玩具小人,一邊數着:
“singer 朝內,左數第 3 個是 archer。
“archer 朝外,右數第 1 個是 thinker。
“thinker 朝外,左數第 2 個是 writer。
“所以眼鏡藏在 writer 這裏!”

雖然成功找回了眼鏡,但小南並沒有放心。如果下次有更多的玩具小人藏他的眼鏡,或是謎題的長度更長,他可能就無法找到眼鏡了。所以小南希望你寫程序幫他解決類似的謎題。這樣的謎題具體可以描述爲:

有 n 個玩具小人圍成一圈,已知它們的職業和朝向。現在第 1 個玩具小人告訴小南一個包含 m 條指令的謎題,其中第 i 條指令形如“左數/右數第 si 個玩具小人”。你需要輸出依次數完這些指令後,到達的玩具小人的職業。

輸入格式
輸入的第一行包含兩個正整數 n ,m ,表示玩具小人的個數和指令的條數。

接下來 n 行,每行包含一個整數和一個字符串,以逆時針爲順序給出每個玩具小人的朝向和職業。其中 0 表示朝向圈內,1 表示朝向圈外。保證不會出現其他的數。字符串長度不超過 10 且僅由小寫字母構成,字符串不爲空,並且字符串兩兩不同。整數和字符串之間用一個空格隔開。

接下來 m 行,其中第 i 行包含兩個整數 ai,si ,表示第 i 條指令。若 ai = 0 ,表示向 左數 si 個人;若 ai = 1 ,表示向右數 si 個人。保證 ai 不會出現其它的數,1 ≤si <n。

輸出格式
輸出一個字符串,表示從第一個讀入的小人開始,依次數完 m 條指令後到達的小人的職業。

樣例數據 1
輸入 

7 3
0 singer
0 reader
0 mengbier
1 thinker
1 archer
0 writer
1 mogician
0 3
1 1
0 2

輸出
writer

樣例數據 2
輸入 
10 10
1 c
0 r
0 p
1 d
1 e
1 m
1 t
1 y
1 u
0 v
1 7
1 1
1 4
0 5
0 3
0 1
1 6
1 2
0 8
0 4

輸出
y

備註
【樣例1說明】
這組數據就是【題目描述】中提到的例子。

【數據規模與約定】
這裏寫圖片描述

其中一些簡寫的列意義如下:

全朝內:若爲“√”,表示該測試點保證所有的玩具小人都朝向圈內;
全左數:若爲“√”,表示該測試點保證所有的指令都向左數,即對任意的 1≤i≤m ,ai = 0 ;
si = 1 :若爲“√”,表示該測試點保證所有的指令都只數 1 個,即對任意的 1≤i≤m , si = 1 ;
職業長度爲 1 :若爲“√”,表示該測試點保證所有玩具小人的職業一定是一個 長度爲 1 的字符串。


T1題解:

【算法分析】
簡單模擬。


【代碼】

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
#include<map>
using namespace std;

struct node
{
    int turn;
    char name[15];
}num[500050];

int n,m;
int ans=0;

inline void work(int x,int y)
{
    if(num[ans].turn-x==0)  
    {   ans=(ans-y)%n;

        if(ans<0)
            ans+=n;
    }
    else
        ans=(ans+y)%n;
}

int main()
{
    cin>>n>>m;

    for(int i=0;i<n;++i)
        cin>>num[i].turn>>num[i].name;

    for(int i=0;i<m;++i)
    {
        long long x,y;

        cin>>x>>y;

        work(x,y);
    }

    cout<<num[ans].name<<endl;

    return 0;
}

T2:天天愛跑步

題目描述
小 C 同學認爲跑步非常有趣,於是決定製作一款叫做《天天愛跑步》的遊戲。《天天愛跑步》是一個養成類遊戲,需要玩家每天按時上線,完成打卡任務。

這個遊戲的地圖可以看作一一棵包含 n 個結點和 n-1 條邊的樹, 每條邊連接兩個結點,且任意兩個結點存在一條路徑互相可達。樹上結點編號爲從 1 到 n 的連續正整數。

現在有 m 個玩家,第 i 個玩家的起點爲 Si ,終點爲 Ti 。每天打卡任務開始時,所有玩家在第 0 秒同時從自己的起點出發,以每秒跑一條邊的速度,不間斷地沿着最短路徑向着自己的終點跑去, 跑到終點後該玩家就算完成了打卡任務。 (由於地圖是一棵樹, 所以每個人的路徑是唯一的)

小 C 想知道遊戲的活躍度, 所以在每個結點上都放置了一個觀察員。 在結點 j 的觀察員會選擇在第 Wj 秒觀察玩家, 一個玩家能被這個觀察員觀察到當且僅當該玩家在第 Wj 秒也正好到達了結點 j 。 小 C 想知道每個觀察員會觀察到多少人?

注意: 我們認爲一個玩家到達自己的終點後該玩家就會結束遊戲, 他不能等待一段時間後再被觀察員觀察到。 即對於把結點 j 作爲終點的玩家: 若他在第 Wj 秒前到達終點,則在結點 j 的觀察員不能觀察到該玩家;若他正好在第 Wj 秒到達終點,則在結點 j 的觀察員可以觀察到這個玩家。

輸入格式
第一行有兩個整數 n 和 m 。其中 n 代表樹的結點數量, 同時也是觀察員的數量, m 代表玩家的數量。
接下來 n-1 行每行兩個整數 u 和 v ,表示結點 u 到結點 v 有一條邊。
接下來一行 n 個整數,其中第 j 個整數爲 Wj, 表示結點 j 出現觀察員的時間。
接下來 m 行,每行兩個整數 Si 和 Ti ,表示一個玩家的起點和終點。
對於所有的數據,保證 1≤Si,Ti≤n,0≤Wj≤n 。

輸出格式
輸出 1 行 n 個整數,第 j 個整數表示結點 j 的觀察員可以觀察到多少人。

樣例數據 1
輸入 
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6

輸出
2 0 0 1 1 1

樣例數據 2
輸入 
5 3
1 2
2 3
2 4
1 5
0 1 0 3 0
3 1
1 4
5 5

輸出
1 2 1 0 1

備註
【樣例1說明】

對於 1 號點,W1 = 0 ,故只有起點爲 1 號點的玩家纔會被觀察到,所以玩家 1 和玩家 2 被觀察到,共有 2 人被觀察到。
對於 2 號點,沒有玩家在第 2 秒時在此結點,共 0 人被觀察到。
對於 3 號點,沒有玩家在第 5 秒時在此結點,共 0 人被觀察到。
對於 4 號點,玩家 1 被觀察到,共 1 人被觀察到。
對於 5 號點,玩家 1 被觀察到,共 1 人被觀察到。
對於 6 號點,玩家 3 被觀察到,共 1 人被觀察到。

【數據規模與約定】
這裏寫圖片描述

【提示】
如果你的程序需要用到較大的棧空間(這通常意味着需要較深層數的遞歸),請務必仔細閱讀選手目錄下的文檔“running/stack.pdf”,以瞭解在最終評測時棧空間的限制與在當前工作環境下調整棧空間限制的方法。

附:running/stack.pdf 文件內容

在最終評測時,調用棧佔用的空間大小不會有單獨的限制,但在我們的工作環境中默認會有 8 MB 的限制。這可能會引起函數調用層數較多時,程序發生棧溢出崩潰。

我們可以使用一些方法修改調用棧的大小限制。例如,在終端中輸入下列命令:

  ulimit -s 1048576

此命令的意義是,將調用棧的大小限制修改爲 1 GB。

例如,在選手目錄建立如下sample.cpp 或 sample.pas
這裏寫圖片描述

將上述源代碼編譯爲可執行文件 sample 後,可以在終端中運行如下命令運行該程序

./sample

如果在沒有使用命令“ulimit -s 1048576”的情況下運行該程序,sample 會因爲棧溢出而崩潰;如果使用了上述命令後運行該程序,該程序則不會崩潰。

特別地,當你打開多個終端時,它們並不會共享該命令,你需要分別對它們運行該命令。

請注意,調用棧佔用的空間會計入總空間佔用中,和程序其他部分佔用的內存共同受到內存限制。


T2題解:

【算法分析】
我們思考一下從x到y的路徑,這個可以拆成從x到lca的路徑和從lca到y的路徑,
這個很明顯。
如 果 一 個 點i 在從x 到lca 的 路 徑 可 以 檢 測 到 的 話 , 那 麼 就 有
deep[i]+w[i]=deep[x]。
如 果 一 個 點i 在從lca 到y 的 路 徑 上 可 以 檢 測 到 的 話 , 那 麼 就 有
deep[i]-w[i]=deep[y]-t(t表示x到y的路徑長度)。
對於每個點i,我們已知deep[i],w[i],只需要在路徑上標上等號左側的數字。
考慮樹上差分,將問題轉化爲子樹集。
dfs維護兩個桶,一個向上的桶a和一個向下的桶b(分別表示deep[x],deep[y]-t)。
每次ans[x]的答案就是子樹a[deep[x]+w[x]]+b[deep[x]-w[x]]的數量。
答案用進入該節點時候的值減去出節點即可。


【代碼】

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
#include<map>
using namespace std;

const int N=300015;

struct node1
{
    int next,to;
}edge[N<<1];

struct node2
{
    int s,t;
};
vector<node2>point[N];

int n,m;
int first[N],jump[N][20],deep[N],w[N],ans[N],num[N<<2];
int tot;

inline int read()       //讀入優化 
{
    int k=0,f=1;
    char c=getchar();

    while(c>'9'||c<'0') 
    {
        if(c=='-') 
            f=-1; 

        c=getchar();
    }

    while(c>='0'&&c<='9') 
    {
        k=k*10+(c-'0'); 
        c=getchar();
    }

    return k*f;
}

inline int add(int s,int t)
{
    ++tot;
    edge[tot].to=t;
    edge[tot].next=first[s];
    first[s]=tot;
}


inline void DFS(int x)
{
    for(int i=1;i<=18;++i)
    {
        jump[x][i]=jump[jump[x][i-1]][i-1];

        if(!jump[x][i]) 
            break;
    }

    for(int i=first[x];i;i=edge[i].next)
        if(edge[i].to!=jump[x][0])
        {
            deep[edge[i].to]=deep[x]+1;
            jump[edge[i].to][0]=x;
            DFS(edge[i].to);
        }
}

inline int LCA(int a,int b)
{
    if(deep[a]<deep[b]) 
        swap(a,b);

    int k=deep[a]-deep[b];

    for(int i=18;i>=-1;--i)
        if(k&(1<<i)) 
            a=jump[a][i];

    if(a==b) 
        return a;

    for(int i=18;i>=-1;--i)
        if(jump[a][i]!=jump[b][i]) 
        {
            a=jump[a][i];
            b=jump[b][i];
        }

    return jump[a][0];
}

inline void work(int x)
{
    int last=num[deep[x]+w[x]]+num[w[x]-deep[x]+n*3+1];
    int k=point[x].size();

    for(int i=0;i<k;++i)
    {
        node2 u=point[x][i];
        num[u.s]+=u.t;
    }

    for(int i=first[x];i;i=edge[i].next)
        if(edge[i].to!=jump[x][0]) 
            work(edge[i].to);

    ans[x]=num[deep[x]+w[x]]+num[w[x]-deep[x]+n*3+1]-last;
}

int main()
{
    int _q=20<<20; 
    char *_p=(char*)malloc(_q)+_q;
    __asm__("movl %0, %%esp\n"::"r"(_p));

    int i,j,k,s,t;
    node2 u;

    n=read();
    m=read();

    for(i=1;i<=n-1;++i)
    {
        s=read();
        t=read();

        add(s,t);
        add(t,s);
    }

    DFS(1);

    for(i=1;i<=n;++i) 
        w[i]=read();

    for(i=1;i<=m;++i)
    {
        s=read();
        t=read();
        k=LCA(s,t);

        u.s=deep[s];
        u.t=1;

        point[s].push_back(u);
        u.t=-1;
        point[jump[k][0]].push_back(u);

        u.s=deep[s]-deep[k]*2+n*3+1;u.t=1;
        point[t].push_back(u);
        u.t=-1;
        point[k].push_back(u);
    }

    work(1);

    for(i=1;i<=n;++i) 
        cout<<ans[i]<<" ";

    return 0;
}

T3:換教室

題目描述
對於剛上大學的牛牛來說,他面臨的第一個問題是如何根據實際情況申請合適的課程。

在可以選擇的課程中,有 2n 節課程安排在 n 個時間段上。在第 i(1≤i≤n)個時間段上,兩節內容相同的課程同時在不同的地點進行,其中,牛牛預先被安排在教室 ci 上課,而另一節課程在教室 di 進行。

在不提交任何申請的情況下,學生們需要按時間段的順序依次完成所有的 n 節安排好的課程。如果學生想更換第 i 節課程的教室,則需要提出申請。若申請通過,學生就可以在第 i 個時間段去教室 di 上課,否則仍然在教室 ci 上課。

由於更換教室的需求太多,申請不一定能獲得通過。通過計算,牛牛發現申請更換第 i 節課程的教室時,申請被通過的概率是一個已知的實數 ki ,並且對於不同課程的申請,被通過的概率是互相獨立的。

學校規定,所有的申請只能在學期開始前一次性提交,並且每個人只能選擇至多 m 節課程進行申請。這意味着牛牛必須一次性決定是否申請更換每節課的教室,而不能根據某些課程的申請結果來決定其他課程是否申請;牛牛可以申請自己最希望更換教室的 m 門課程,也可以不用完這 m 個申請的機會,甚至可以一門課程都不申請。

因爲不同的課程可能會被安排在不同的教室進行,所以牛牛需要利用課間時間從一間教室趕到另一間教室。

牛牛所在的大學有 v 個教室,有 e 條道路。每條道路連接兩間教室,並且是可以雙向通行的。由於道路的長度和擁堵程度不同,通過不同的道路耗費的體力可能會有所不同。當第 i(1≤i≤n-1)節課結束後,牛牛就會從這節課的教室出發,選擇一條耗費體力最少的路徑前往下一節課的教室。

現在牛牛想知道,申請哪幾門課程可以使他因在教室間移動耗費的體力值的總和的期望值最小,請你幫他求出這個最小值。

輸入格式
第一行四個整數 n,m,v,e 。n 表示這個學期內的時間段的數量;m 表示牛牛最多可以申請更換多少節課程的教室;v 表示牛牛學校裏教室的數量;e 表示牛牛的學校裏道路的數量。

第二行 n 個正整數,第 i(1≤i≤n)個正整數表示 ci ,即第 i 個時間段牛牛被安排上課的教室;保證 1≤ci≤v。

第三行 n 個正整數,第 i(1≤i≤n)個正整數表示 di ,即第 i 個時間段另一間上同樣課程的教室;保證 1≤di≤v。

第四行 n 個實數,第 i(1≤i≤n)個實數表示 ki ,即牛牛申請在第 i 個時間段更換教室獲得通過的概率。保證 0≤ki≤1。

接下來 e 行,每行三個正整數 aj ,bj ,wj ,表示有一條雙向道路連接教室 aj ,bj ,通過這條道路需要耗費的體力值是 wj;保證 1≤aj,bj≤v,1≤wj≤100

保證 1≤n≤2000,0≤m≤2000,1≤v≤300,0≤e≤90000。

保證通過學校裏的道路,從任何一間教室出發,都能到達其他所有的教室。

保證輸入的實數最多包含 3 位小數。

輸出格式
輸出一行,包含一個實數,四捨五入精確到小數點後恰好 2 位,表示答案。你的輸出必須和標準輸出完全一樣纔算正確。

測試數據保證四捨五入後的答案和準確答案的差的絕對值不大於 4×10-3。(如果你不知道什麼是浮點誤差,這段話可以理解爲:對於大多數的算法,你可以正常地使用浮點數類型而不用對它進行特殊的處理)

樣例數據 1

輸入 
3 2 3 3
2 1 2
1 2 1
0.8 0.2 0.5
1 2 5
1 3 3
2 3 1

輸出
2.80

備註
【樣例1說明】
所有可行的申請方案和期望收益如下表:
這裏寫圖片描述

【提示】

  1. 道路中可能會有多條雙向道路連接相同的兩間教室。也有可能有道路兩端連接的是同一間教室。

  2. 請注意區分 n,m,v,e 的意義,n 不是教室的數量,m 不是道路的數量。

【數據規模與約定】
這裏寫圖片描述

特殊性質1:圖上任意兩點 ai,bi,ai≠bi 間 ,存在一條耗費體力最少的路徑只包含一條道路。

特殊性質2:對於所有的 1≤i≤n,ki=1。


T3題解:

【算法分析】
先用floyd預處理一下。
設f[i][j][k]表示第i個時間段,選了j個教室要申請(不一定成功),k表示i申請
或不申請。
我們從i推到i+1,如果要推到f[i+1][j][0]的話,那麼第i+1個時間段肯定在a[i+1]。
這個可以從i的0狀態和1狀態推過來,如果從0推過來,那麼i時間段肯定在a[i]這個
地方,那麼期望是d[a[i]][a[i+1]],概率是1。如果從1推過來,那麼i有兩種情況,申
請成功和申請不成功,所以i時間段可能在a[i]或b[i],概率爲1-gai[i]和gai[i]。

所以f[i+1][j][0]=min(f[i][j][0]+d[a[i]][a[i+1]],f[i][j][1]+(1−gai[i])∗
d[a[i]][a[i+1]]+gai[i]∗d[b[i]][a[i+1]]);

要推倒f[i+1][j][1]情況類似,不過要考慮i+1申請成不成功的情況。

f[i+1][j+1][1]=
min(f[i][j][0]+d[a[i]][b[i+1]]∗gai[i+1]+d[a[i]][a[i+1]]∗(1−
gai[i+1]),f[i][j][1]+d[a[i]][a[i+1]]∗(1−gai[i])∗(1−gai[i+1])+d[b[i]][a[i+1]]∗gai[i]∗(1−
gai[i+1])+d[a[i]][b[i+1]]∗(1−gai[i])∗gai[i+1]+d[b[i]][b[i+1]]∗gai[i]∗gai[i+1]);

最後答案就是min(f[n][j][0,1])


【代碼】

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
#include<map>
using namespace std;

int n,m,v,e;
int c[2050],d[2050];
int dis[2050][2050];
double ans=1000000007;
double f[2050][2050][2],k[2050];

inline void getin()
{
    for(int i=1;i<=n;++i)   cin>>c[i];
    for(int i=1;i<=n;++i)   cin>>d[i];
    for(int i=1;i<=n;++i)   cin>>k[i];

    for(int i=1;i<=v;i++)
        for(int j=1;j<=v;j++)
            if(i!=j) 
                dis[i][j]=1000000007;   

    for(int i=1;i<=e;++i)
    {
        int a,b,w;

        cin>>a>>b>>w;

        dis[a][b]=min(w,dis[a][b]); 
        dis[b][a]=min(w,dis[b][a]);
    }
}

inline void FL()
{
    for(int k=1;k<=v;++k)
        for(int i=1;i<=v;++i)
            for(int j=1;j<=v;++j)
                if(i!=j&&i!=k&&k!=j)
                    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}

inline void DP()
{
    for(int i=1;i<=n;++i)
        for(int j=0;j<=m;++j)
            f[i][j][0]=f[i][j][1]=1000000007;

    f[1][1][1]=f[1][0][0]=0;

    for(int i=1;i<n;++i)
        for(int j=0;j<=m;++j)
        {
            f[i+1][j][0]=min(f[i][j][1]+k[i]*dis[d[i]][c[i+1]]+(1.0-k[i])*dis[c[i]][c[i+1]],f[i][j][0]+dis[c[i]][c[i+1]]);
            f[i+1][j+1][1]=min(f[i][j][1]+k[i]*k[i+1]*dis[d[i]][d[i+1]]+(1.0-k[i])*k[i+1]*dis[c[i]][d[i+1]]+k[i]*(1.0-k[i+1])*dis[d[i]][c[i+1]]+(1.0-k[i])*(1.0-k[i+1])*dis[c[i]][c[i+1]],f[i][j][0]+k[i+1]*dis[c[i]][d[i+1]]+(1.0-k[i+1])*dis[c[i]][c[i+1]]);
        }
}

inline void giveout()
{
    for(int i=0;i<=m;++i)
        ans=min(ans,min(f[n][i][0],f[n][i][1]));

    printf("%0.2f",ans);
}

int main()
{   
    cin>>n>>m>>v>>e;

    getin();
    FL();
    DP();
    giveout();

    return 0;
}
                                                             2017年7月19日15:55:36
發佈了43 篇原創文章 · 獲贊 8 · 訪問量 8175
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章