暑期集訓模擬賽3
阿新 • • 發佈:2020-07-17
#前言
今天除了改成$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);
}
```