1. 程式人生 > >暑期集訓模擬賽3

暑期集訓模擬賽3

#前言 今天除了改成$0$分的$T4$一切安好…… ## NO.1 中中救援隊 原型:安慰奶牛 ### 題目描述 中中酷愛滑雪,某日突發奇想,帶領所有$BDEZ$的$OIER$去$Alps$滑雪,不幸的是,中中和$OIER$們遭遇了雪崩,除了中中,所有的$OIER$們都埋在了雪坑裡,此時,中中救援隊閃亮登場~!(中中救援隊只有中中一個人!$Orz$!) 雪崩之後,出現了$N$個雪坑,每個雪坑都有一名$OIER$深陷其中,只有中中倖免,現在中中找到了M條雙向道路,每條道路都會連線兩個雪坑,但是,由於中中是路痴(-_-||),所以中中希望去除$M$條道路中儘可能多的道路,但是還要保證任一雪坑都能到達其他的雪坑,所以要先選擇留下$N-1$條道路,中中可以從任意一個雪坑出發,並且任務完成後要回到出發點(起點的雪坑和終點的雪坑也需要消耗體力),而且中中只記得他選擇的道路 中中每到一個雪坑$i$,都會消耗一定的體力值$t_i$,即使這個雪坑的$OIER$已被救出。第$j$條道路連線$x,y$兩個雪坑,而從$x$到達$y$也需要消耗體力值$energy$ 由於時間緊迫,中中請你這名$OIER$幫助他計算下救出這$N$名$OIER$所消耗的最小體力值是多少 ### 輸入格式 輸入第一行兩個整數$N$和$M$,表示有$N$名$OIER$,$M$條連線的道路 接下來$N$行,每行一個整數$t_i$,表示第$i$個雪坑需要消耗中中的體力值 然後$M$行,每行三個整數$x,y,energy$,表示從$x$坑滑到$y$坑需要消耗的體力值為$energy$ ### 輸出格式 第$1$行,一個整數,為中中消耗的最小體力值 ### 樣例 #### 樣例輸入 >5 7 6 5 13 8 18 4 1 7 5 2 5 1 5 16 2 3 20 3 1 18 4 3 12 2 4 15 #### 樣例輸出 >154 ### 資料範圍與提示 對於$30\%$的資料 $5\le N\le 100,N-1\le M\le 500 1\le t_i\le 100 x\le N,y\le N,1\le energy\le 100$ 對於全部資料 $5\le N\le 10000,N-1\le M\le 100000 1\le t_i\le 1000 x\le N,y\le N,1\le energy\le 1000$ 結果保證不超過$2^{31}-1$ ### 分析 看到題目中只保留$N-1$條邊,且要求消耗能量最小即路徑長最小,那麼就是一個最小生成樹板子題了,但是處理邊權需要考慮一下。 因為每條路徑一定要經過兩遍,所以首先需要讓路徑長乘以$2$,又因為每個點的點權也需要計算,所以我們還需要加上路徑兩端的點權,然後跑最小生成樹就行了。 因為起點和終點也要計算,而除了終點都是經過兩次,又因為處理的時候沒有把每個起點和重點的值計算兩邊,所以需要找出最小的點權,並且從那裡作為起點,最後就是最小生成樹的值加上這個最小的點權。 ### 程式碼 ```cpp #include
using namespace std; const int maxn = 1e4+10; struct Node{ int x,y,val; }e[maxn*10]; int n,m; int mn=0x7f7f7f7f; int t[maxn],fa[maxn]; bool cmp(Node a,Node b){ return a.valt[i])mn=t[i]; } int ans = mn+kruscal();//計算答案 printf("%d\n",ans); return 0; } ``` ## NO.2 家務活 ### 題目描述 農場主約翰家人有$N (3\le N\le 10,000)$件家務活需要完成,完成第$i$件家務活需要$T_i(1\le T_i\le 100)$的時間,在做第$i$件家務活之前約翰必須完成若干個家務活,我們稱這些家務為$i$的必備家務。至少有一個家務沒有必備家務,第一件家務沒有必備家務。 約翰已經安排好完成家務活的順序,家務活$k$的必備家務活只會出現在區間$[1,k-1]$之間。沒有依賴關係的家務活可以同時進行。 現在請你計算約翰家人完成所有家務的最短時間。 ### 輸入格式 第一行為一個整數$N$,表示有$N$件家務活。 接下來$2~n+1$行,第$i+1$行前兩個數分別為$T_i$和$k_i$,表示完成第$i$件家務需要$T_i$的時間,有$k_i$個必備家務,接著$k$個數表示第$i$件家務的必備家務。 ### 輸出格式 只有一行,約翰完成所有家務的最短時間。 ### 樣例 #### 樣例輸入 >
7 5 0 1 1 1 3 1 2 6 1 1 1 2 2 4 8 2 2 4 4 3 3 5 6 #### 樣例輸出 >23 ### 資料範圍與提示 $1$: $0$ 時刻開始,$5$ 時刻結束 $2$: $5$ 時刻開始,$6$ 時刻結束 $3$: $6$ 時刻開始,$9$ 時刻結束 $4$: $5$ 時刻開始,$11$時刻結束 $5$: $11$時刻開始,$12$時刻結束 $6$: $11$時刻開始,$19$時刻結束 $7$: $19$時刻開始,$23$時刻結束 ### 分析 看到題目中有約束條件,也就是先到某個點才能到下一個點,所以我們就可以用拓撲排序來排出來工作的先後順序,每一項從這個工作拓展出來的工作是可以同時進行的,所以我們記錄一下同時工作的話花費時間的最大值,雖然讓求的是最小值,但是如果沒做完這個工作是不行的。這樣就處理出來了每個工作到什麼時刻結束。最後當沒有出度,也就是全部工作都應該做完了,那麼就統計答案。 有一個需要注意的地方,因為可能有很多不需要必備家務活的家務,而一開始我們就需要從這些點開始進行拓撲排序,所以我們建一個超級源點,連線入度為$0$的點,然後從這個點開始拓撲排序就好了。 ### 程式碼 ```cpp #include
using namespace std; const int maxn = 1e5+10; struct Node{ int v,next; }e[5000005]; int n; int t[maxn],tot,val[maxn]; int head[maxn],ans; queueq; int rd[maxn],cd[maxn]; void Add(int x,int y){ e[++tot].v = y; e[tot].next = head[x]; head[x] = tot; } int main(){ scanf("%d",&n); int gs; for(int i=1;i<=n;++i){ scanf("%d%d",&t[i],&gs); rd[i] = gs; if(!gs)Add(n+1,i);//沒有入度就從超級源點建邊 val[i] = t[i];//記錄下來每個的時間 for(int k=1;k<=gs;++k){ int x; scanf("%d",&x); Add(x,i); cd[x]++;//計算出度 } } q.push(n+1);//超級源點開始 while(!q.empty()){ int u = q.front(); q.pop(); for(int i=head[u];i;i=e[i].next){ int v = e[i].v; if(rd[v])rd[v]--;//上個工作做了,入度減一 val[v] = max(val[v],val[u]+t[v]);//把能夠同時做的工作全做了,找到做完後的時間點 if(!cd[v])ans = max(ans,val[v]);//沒有出度就統計答案 if(!rd[v])q.push(v);//沒有入度了說明可以做這個家務,入隊 } } printf("%d\n",ans); return 0; } ``` ## NO.3 傳紙條 原型:方格取數 ### 題目描述 小淵和小軒是好朋友也是同班同學,他們在一起總有談不完的話題。一次素質拓展活動中,班上同學被安排坐成一個$m$行、$n$列的矩陣,而小淵和小軒被安排坐在矩陣對角線的兩端,因此,他們就無法直接交談了。幸運的是,他們可以通過傳紙條來進行交流。紙條要經由許多同學傳到對方手裡,小淵坐在矩陣的左上角,座標$(1,1)$,小軒坐在矩陣的右下角,座標$(m,n)$。從小淵傳給小軒的紙條只可以向下或者向右傳遞,從小軒傳給小淵的紙條只可以向上或者向左傳遞。 在活動進行中,小淵希望給小軒傳一張紙條,同時希望小軒給他回覆。班裡的每個同學都可以幫他們傳遞,但只會幫他們一次,也就是說如果此人在小淵遞給小軒紙條的時候幫忙,那麼在小軒遞給小淵的時候就不會再幫忙。反之亦然。 還有一件事情需要注意,全班每個同學願意幫忙的好心程度有高有低(注意:小淵和小軒的好心程度沒有定義,輸入時用$0$表示),可以用一個 $0~100$的自然數來表示,數越大表示越好心。小淵和小軒希望儘可能找好心程度高的同學來幫忙傳紙條,即找到來回兩條傳遞路徑,使得這兩條路徑上同學的好心程度之和最大。現在,請你幫助小淵和小軒找到這樣兩條路徑。 ### 輸入格式 第一行有$2$個用空格隔開的整數$m$和$n$,表示班裡有$m$行$n$列 $(1\le m,n\le 50)$。 接下來的$m$行是一個$m\times n$的矩陣,矩陣中第$i$行$j$列的整數表示坐在第$i$行$j$列的學生的好心程度。每行的$n$個整數之間用空格隔開。 ### 輸出格式 共一行,包含一個整數,表示來回兩條路上參與傳紙條的同學的好心程度之和的最大值。 ### 樣例 #### 樣例輸入 >3 3 0 3 9 2 8 5 5 7 0 #### 樣例輸出 >34 ### 資料範圍與提示 $30\%$的資料滿足:$l\le m,n\le 10$ $100\%$的資料滿足:$1\le m,n\le 50$ ### 分析 因為從左上到右下要找到兩條路徑,還不能重合,所以我們直接從左上到右下,開四維陣列,前兩個和後兩個分別從上一行、上一列進行轉移,然後取最大值並且加上到的那個點的值,如果算重了就減去,最後輸出就行。下邊說一下具體的狀態: 定義$f[i][j][k][l]$為一條路徑到$i$行$j$列,另一條到$k$行$l$列得到的最大值,狀態轉移如下: $$f[i][j][k][l] = max(max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]),max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]))+a[i][j]+a[k][l];$$ 如果算重了,減去就行。 ### 程式碼 ```cpp #include using namespace std; const int maxn = 55; int f[maxn][maxn][maxn][maxn]; int n,m; int a[maxn][maxn]; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ scanf("%d",&a[i][j]); } } for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ for(int k=1;k<=n;++k){ for(int l=1;l<=m;++l){ f[i][j][k][l] = max(max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]),max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]))+a[i][j]+a[k][l]; if(i == k && j == l)f[i][j][k][l] -= a[i][j];//算重了就減去 } } } } printf("%d\n",f[n][m][n][m]); return 0; } ``` ## NO.4 殺人遊戲 ### 題目描述 一位冷血的殺手潛入 $Na-wiat$,並假裝成平民。警察希望能在 $N$ 個人裡面查出誰是殺手。 警察能夠對每一個人進行查證,假如查證的物件是平民,他會告訴警察,他認識的人,誰是殺手,誰是平民。假如查證的物件是殺手,殺手將會把警察幹掉。 現在警察掌握了每一個人認識誰。每一個人都有可能是殺手,可看作他們是殺手的概率是相同的。 問:根據最優的情況,保證警察自身安全並知道誰是殺手的概率最大是多少? ### 輸入格式 第一行有兩個整數 $N,M$。 接下來有 $M$ 行,每行兩個整數 $x,y$,表示 $x$ 認識 $y$($y$ 不一定認識 $x$,例如$HJT$同志) 。 ### 輸出格式 僅包含一行一個實數,保留小數點後面 $6$ 位,表示最大概率。 ### 樣例 #### 樣例輸入#1 >5 4 1 2 1 3 1 4 1 5 #### 樣例輸出#1 >0.800000 #### 樣例輸入#2 >4 2 1 2 2 3 #### 樣例輸出#2 >0.750000 #### 樣例輸入#3 >7 6 4 1 5 4 2 1 7 3 1 6 2 5 #### 樣例輸出#3 >0.714286 ### 資料範圍與提示 警察只需要查證 $1$。假如$1$是殺手,警察就會被殺。假如 $1$不是殺手,他會告訴警察 $2,3,4,5$ 誰是殺手。而 $1$ 是殺手的概率是 $0.2$,所以能知道誰是殺手但沒被殺的概率是$0.8$。 對於 $100\%$的資料有 $1\le N\le 100000$,$0\le M\le 300000$ ### 分析 看到題想到縮點,因為縮完點後,問一個人就能知道一堆人的的資訊,並且每個縮完後的點又相互之間有邊,那麼我們需要找一個入度為$0$的點,每有一個就讓$ans++$,但是縮點後肯定不只是有環,還會有單個的點,像這樣的入度為$0$,單個點,且他連線的那個點入度不為$1$,也就是說可以通過其他路徑找出這個人的身份,那麼就標記,讓$ans--$。但是這樣的點只允許有一個,因為如果多了,在問完並知道所有人的身份後,還需要問這些孤立的點,那麼不被殺的機率就小了一點,所以只讓最後剩下一個這樣的點,或者沒有。而我們需要求的就是縮點後入度為$0$的點的個數。 ### 程式碼 ```cpp #include using namespace std; const int maxn = 1e5+10; struct Node{ int v,next; }e[300010<<1],ec[300010<<1]; int head[maxn],tot; int dfn[maxn],vis[maxn],low[maxn],c[maxn]; int hc[maxn],tc; int visx[maxn]; int sta[maxn],top,cnt,rd[maxn],siz[maxn]; int num; int ans; void Add(int x,int y){ e[++tot].v = y; e[tot].next = head[x]; head[x] = tot; } void Add2(int x,int y){ ec[++tc].v = y; ec[tc].next = hc[x]; hc[x] = tc; } void Tarjan(int x){//縮點 dfn[x] = low[x] = ++num; vis[x] = 1; sta[++top] = x; for(int i=head[x];i;i=e[i].next){ int v = e[i].v; if(!dfn[v]){ Tarjan(v); low[x] = min(low[x],low[v]); } else if(vis[v]){ low[x] = min(low[x],dfn[v]); } } if(dfn[x] == low[x]){ cnt++; int y; while(1){ y=sta[top--]; c[y]=cnt; siz[cnt]++; vis[y] = 0; if(x==y)break; } } } bool judge(int x){//判斷是不是他的出邊能夠通過其他點進來而得到他的身份,如果不行就是false for(int i=hc[x];i;i=ec[i].next){ int v = ec[i].v; if(rd[v] == 1)return false; } return true; } int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;++i){ int x,y; scanf("%d%d",&x,&y); Add(x,y); } for(int i=1;i<=n;++i){//Tarjan縮點 if(!dfn[i])Tarjan(i); } for(int x=1;x<=n;++x){ for(int i=head[x];i;i=e[i].next){//縮點後新建圖 int v = e[i].v; if(c[x] != c[v]){ rd[c[v]]++; Add2(c[x],c[v]); } } } int ans = 0; bool flag = 0; for(int i=1;i<=cnt;++i){ if(!flag && siz[i] == 1 && rd[i] == 0 && judge(i)){//標記孤立點 flag = 1; } if(rd[i] == 0){//記錄入度為0的點個數 ans++; } } if(flag)ans--;//如果有孤立的點就減一 printf("%.6lf",1.0-(double)ans/(double)n); } ```