1. 程式人生 > >2018-10-25 模擬測試題解

2018-10-25 模擬測試題解

目錄

本篇題解也發表於zwcblog

問題 A: 魏傳之長阪逆襲

題目描述

眾所周知,劉備在長阪坡上與他的一眾將領各種開掛,硬生生從曹操手中逃了出去,隨後與孫權一起火燒赤壁、佔有荊益、成就霸業。而曹操則在赤壁一敗後再起不能,終生無力南下。
建安二十五年(220年),曹操已到風燭殘年,但仍難忘當年長阪的失誤,霸業的破滅。他想如果在劉備逃亡的路中事先佈下一些陷阱,便能拖延劉備的逃脫時間了。你作為曹操身邊的太傅,有幸穿越到了208年的長阪坡,為大魏帝國貢獻一份力,佈置一些陷阱。但時空守衛者告訴你你不能改變歷史,不能拖增大劉備的最大逃脫時間,但你身為魏武之仕,忠心報國,希望能新增一些陷阱使得劉備不論怎麼逃跑所用的時間都一樣。
已知共有n個據點,n-1條棧道,保證據點聯通。1號據點為劉備軍逃跑的起點,當劉備軍跑到某個據點後不能再前進時視為劉備軍逃跑結束。在任意一個棧道上放置1個陷阱會使通過它的時間+1,且你可以在任意一個棧道上放置任意數量的陷阱。
現在問你在不改變劉備軍當前最大逃跑時間的前提下,需要新增最少陷阱,使得劉備軍的所有逃脫時間都儘量的大。

輸入

第一行一個數n,表示據點個數。
接下來n-1行每行三個數,ai、bi、ti,表示從據點ai通過第i個棧道到bi耗時ti

輸出

僅包含一個數,為需要新增的最少陷阱數。

樣例輸入

3
1 2 1
1 3 3

樣例輸出

2

對於 5%的資料,1<=n<=100000,1<=ti<=200000
對於 100%的資料,1<=n<=500000,0<ti<=1000000

題解


此題是一道樹形DP,很顯然,它就是讓你求 從根開始到每一個葉子結點的權值和相等需要改多少邊權
\(f[x]\)表示以\(x\)為跟,到它的子樹中權值的最大值
再設\(sum\)表示已\(x\)

為根的權值總和
再設x為根的節點有\(cnt\)個孩子
則對於\(x\),答案要加上\(cnt*f[x]-sum\)

程式碼如下(因為比較簡單,就不解釋了)
還有,記得開\(long\ long\)

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N=1000005;
int n,edge;
LL ans;
int Next[N],head[N],vet[N],val[N];
LL f[N];
inline void add(int u,int v,int va){
    Next[++edge]=head[u];
    head[u]=edge;
    vet[edge]=v;
    val[edge]=va;
}
void dfs(int u,int father){
    LL Max=0,sum=0;int cnt=0;
    for (int i=head[u];i;i=Next[i]){
        int v=vet[i];
        if (v==father) continue;
        dfs(v,u);
        Max=max(Max,f[v]+val[i]);
        sum+=(0LL+f[v]+val[i]);++cnt;
    }
    ans+=(1LL*Max*cnt-sum);
    f[u]=Max;
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<n;++i){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    dfs(1,0);
    printf("%lld\n",ans);
    return 0;
} 

問題 B: 蜀傳之單刀赴會

題目描述

公元215年,劉備取益州,孫權令諸葛瑾找劉備索要荊州。劉備不答應,孫權極為惱恨,便派呂蒙率軍取長沙、零陵、桂陽三郡。長沙、桂陽蜀將當即投降。劉備得知後,親自從成都趕到公安(今湖北公安),派大將關羽爭奪三郡。孫權也隨即進駐陸口,派魯肅屯兵益陽,抵擋關羽。雙方劍拔弩張,孫劉聯盟面臨破裂,在這緊要關頭,魯肅為了維護孫劉聯盟,不給曹操可乘之機,決定當面和關羽商談。“肅邀羽相見,各駐兵馬百步上,但諸將軍單刀俱會”。雙方經過會談,緩和了緊張局勢。隨後,孫權與劉備商定平分荊州,“割湘水為界,於是罷軍”,孫劉聯盟因此能繼續維持。

【問題描述】

關羽受魯肅邀請,為了大局,他決定冒險赴會。他帶著侍從周倉,義子關平,騎著赤兔馬,手持青龍偃月刀,從軍營出發了,這就是歷史上赫赫有名的“單刀赴會”。關羽平時因為軍務繁重,決定在這次出行中拜訪幾個多日不見的好朋友。然而局勢緊張,這次出行要在限定時間內完成,關公希望你能夠幫助他安排一下行程,安排一種出行方式,使得從軍營出發,到達魯肅處赴會再回來,同時拜訪到儘可能多的朋友,在滿足這些條件下行程最短。注意拜訪朋友可以在赴會之前,也可以在赴會之後。現在給出地圖,請你完成接下來的任務。

輸入

第一行n,m,k,t,代表有n個地點,m條道路,有k個朋友(不包括魯肅),以及限定時間t(行走1單位長度的路程用時1單位時間)。
接下來m行,每行有x,y,w三個整數,代表x和y之間有長度為w的道路相連。
接下來一行有k個整數,代表朋友所在的都城編號(保證兩兩不同,且不在1和n)(我們約定1是關羽的營地,n是魯肅的營地)

輸出

輸出兩個整數,分別是最多可以拜訪的朋友數,以及在這種情況下最少需要耗費的時間,如果連到達魯肅再回來都無法完成,輸出一個-1就可以了。

樣例輸入

5 7 2 15
1 2 5
1 3 3
2 3 1
2 4 1
3 4 4
2 5 2
4 5 3
2 4

樣例輸出

2 14

有10%資料,n<=10,m<=50,k<=5;
有10%資料,k=0;
有10%資料,k=1;
另30%資料,k<=5;
對於100%資料, n<=10000,m<=50000,k<=15,t<=2147483647,w<=10000

題解


這道題目發現k非常小,於是考慮狀態壓縮

這個時候,我發現n似乎沒有什麼用……
我們只需要知道朋友之間的距離即可

因為關羽的陣地和魯肅的陣地是必須去的
我們不妨把關羽和魯肅分別設為\(0\)號朋友與\(k+1\)號朋友
在統計答案時令關羽的陣地和魯肅的陣地必須到即可

我們先統計出每一個朋友之間的距離為\(dis[i][j]\)
狀態\(f[sta][i]\)表示\(sta\)的狀態,且當前留在最後一個朋友的位置

\(f[sta\ \ | \ \ (1&lt;&lt;(i-1))][i]=\displaystyle\min_{j\ \in\ sta\ \ and \ \ i\ not \in\ sta}{(f[sta][j]+dis[j][i])}\)

這是什麼意思呢??

如果關羽還沒有到過i,但他到過j,他就可以從j走到i

最後的答案就是\(f[sta][i]+dis[i][0]\)(若\(sta\)到過\(0\)號與\(k+1\)號且\(f[sta][i]+dis[i][0]&lt;=d\))
及到過他自己的與魯肅的陣地中且最後再返回自己的陣地

上程式碼:

#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int N=100005;
struct ac{
    int now,value;
    bool operator < (const ac &A) const {return A.value<value;}
};
int Dis[N],Friend[20];
int n,m,k,t,cnt,ans,ANS;
bool vis[N];
int dp[17][132000];
int dis[20][20];
int Next[N],head[N],vet[N],val[N],edge;
inline void read(int &x){//讀入優化
    x=0;char ch=getchar();int f=0;
    while (ch>'9'||ch<'0') f|=ch=='-',ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
    x=(f==1)?-x:x;
}
inline void add(int u,int v,int va){
    Next[++edge]=head[u];
    head[u]=edge;
    val[edge]=va;
    vet[edge]=v;
}
void dijkstra(int st){//堆優化的最短路
    priority_queue<ac> Q;
    memset(Dis,0x3f3f3f,sizeof(Dis));
    memset(vis,0,sizeof(vis));
    Q.push((ac){st,0});Dis[st]=0;
    while (!Q.empty()){
        int now=Q.top().now;Q.pop();
        if (vis[now]) continue;
        vis[now]=1;
        for (int i=head[now];i;i=Next[i]){
            int v=vet[i],va=val[i];
            if (Dis[v]>Dis[now]+va) {  
                Dis[v]=Dis[now]+va;
                Q.push((ac){v,Dis[v]});
            }
        }
    }
}
int main(){//重要的來了!!
    read(n);read(m);read(k);read(t);
    for (int i=1;i<=m;++i){
        int u,v,w;
        read(u);read(v);read(w);
        add(u,v,w);
        add(v,u,w);
    }
    Friend[0]=1;Friend[k+1]=n;//新增兩個朋友,及自己與魯肅
    for (int i=1;i<=k;++i) read(Friend[i]);
    for (int i=0;i<=(k+1);++i){
        dijkstra(Friend[i]);//對於每一個朋友求出到另外朋友的最短路
        for (int j=0;j<=(k+1);++j){
            if (i==j) continue;
            dis[i][j]=Dis[Friend[j]];
        }
    }
    int N=1<<k+2;//因為新增了兩個朋友
    memset(dp,0x3f3f3f,sizeof(dp));int oo=dp[1][1];
    for (int i=0;i<=(k+1);++i) dp[i][1<<i]=dis[0][i];
    //初始化   到i需要dis[0][i]
    for (int sta=0;sta<N;++sta){
        for (int i=0;i<=(k+1);++i)
        if ((sta&(1<<i))==0)//如果i不屬於sta
        for (int j=0;j<=(k+1);++j)
        if ((sta&(1<<j))>0) //如果j屬於sta
            dp[i][sta|(1<<i)]=min(dp[i][sta|(1<<i)],dp[j][sta]+dis[j][i]);
        //轉移
    }
    ANS=2000000000;
    for (int sta=0;sta<N;++sta){
        if ((sta&(1<<0))>0&&(sta&(1<<k+1))>0){
                    //到過0號(關羽自己)與k+1號(魯肅)
            for (int i=0;i<=(k+1);++i){
                if (dp[i][sta]+dis[i][0]<=t){
                    cnt=0;
                    int x=sta;
                    while (x){
                        x-=(x&(-x));
                        ++cnt;
                    }//判斷sta有多少1,及關羽拜訪了多少朋友
                    if (cnt>ans){
                        ans=cnt;
                        ANS=dp[i][sta]+dis[i][0];
                    } else 
                    if (cnt==ans) ANS=min(ANS,dis[i][0]+dp[i][sta]);
                }
            }
        }
    }
    if (ANS==2000000000) puts("-1"); else printf("%d %d\n",ans-2,ANS);//判斷-1
    return 0;
}

問題 C: 吳傳之火燒連營

【題目背景】

蜀漢章武元年(221年),劉備為報吳奪荊州、關羽被殺之仇,率大軍攻吳。吳將陸遜為避其鋒,堅守不戰,雙方成對峙之勢。蜀軍遠征,補給困難,又不能速戰速決,加上入夏以後天氣炎熱,以致銳氣漸失,士氣低落。劉備為舒緩軍士酷熱之苦,命蜀軍在山林中安營紮寨以避暑熱。陸遜看準時機,命士兵每人帶一把茅草,到達蜀軍營壘時邊放火邊猛攻。蜀軍營寨的木柵和周圍的林木為易燃之物,火勢迅速在各營漫延。蜀軍大亂,被吳軍連破四十餘營。陸遜火燒連營的成功,決定了夷陵之戰蜀敗吳勝的結果。

【問題描述】

劉備帶兵深入吳境,陸遜卻避而不出,蜀軍只得在山林中安營紮寨。而劉備在紮營時卻犯了兵家大忌,將兵營排列成一條直線,遠遠看去,就像是一條串著珠子的鏈,美其名曰:鏈寨。如果吳軍將領是一般人,那麼這也許不算什麼,而陸遜何許人也,他可是江東才子,能力不低於周瑜的一代儒將。他看到劉備這樣排陣,心生一計,決定用火攻破陣。然而,火計除了要有風,選定引火點也非常重要,對於劉備的佈陣,最佳引火點一定是n個兵營中的一個。而因為風水輪流轉,每天的最佳引火點都不一樣。我們給每個兵營定下一個固定不變的火攻值Ai,每天定下一個風水值K,對於每天的最佳引火點,顯然是所有兵營中火攻值與風水值異或的結果最大的那一個兵營。然而,陸遜是個謹慎的人,他要觀察時機,在m天中選定一個最佳的進攻的日期,為此他演算出了這m天每天的風水值,然後他希望你能夠告訴他這m天每天最佳引火點的兵營編號。

輸入

第一行n,m,代表有n個兵營,m天.
接下來一行有n個非負整數,代表這n個兵營的火攻值。
接下來一行有m個非負整數,代表這m天的風水值。

輸出

輸出共m行,每行輸出一個整數,代表第m天最佳引火點的編號。
如果存在多個最佳引火點使得火攻值與風水值的異或值最小,請任意輸出一組解即可。

樣例輸入

3 2
1 2 3
4 5

樣例輸出

3
2

【樣例解釋】

對於第1天,由於4 xor 1=5, 4 xor 2=6, 4 xor 3=7,選擇第3個引火點是最佳的。
對於第2天,由於5 xor 1=4, 5 xor 2=7, 5 xor 3=6,選擇第2個引火點是最佳的。

【資料規模和約定】

對於30%資料,n<=1000,m<=1000
對於100%資料,n<=100000,m<=100000, 0<=k,ai<=2147483647

題解


其實這道題目就是一道Trie樹模板題
Trie樹我就不再贅述了,可以戳這裡
\(2^{33}-&gt;2^{0}\)每次掃一遍即可
平均時間複雜度\(O(35n)\)
陣列一定要開大一點!!
程式碼:

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int Trie[8000000][2],m,a[100],b[8000000],root,Tot,tot,n,x;
void read(int &a){
    char ch=getchar();int f=0;a=0;
    while (ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
    while (ch>='0'&&ch<='9') a=(a<<3)+(a<<1)+ch-48,ch=getchar();
    a=(f==0)?a:-a;
}
void build(int yyy){//構建
    int root=1;
    for (int i=33;i>=0;--i){
        if (Trie[root][a[i]]==0) Trie[root][a[i]]=++Tot;
        root=Trie[root][a[i]];
    }
    b[root]=yyy;
}
int ask(){//查詢
    int root=1;
    for (int i=33;i>=0;--i){
        if (Trie[root][1-a[i]]!=0) root=Trie[root][1-a[i]];
            else root=Trie[root][a[i]];
    }
    return b[root];
}
int main()
{
    read(n);read(m);Tot=1;
    for (int i=1;i<=n;++i) {
        read(x);tot=-1;
        memset(a,0,sizeof(a));
        while (x){
            a[++tot]=(x&1);
            x=x>>1;
        }
        build(i);
    }
    for (int i=1;i<=m;++i){
        read(x);tot=-1;
        memset(a,0,sizeof(a));
        while (x){
            a[++tot]=(x&1);
            x=x>>1;
        }
        printf("%d\n",ask());
    }
    return 0;
}