前言
今天除了改成\(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\),又因為每個點的點權也需要計算,所以我們還需要加上路徑兩端的點權,然後跑最小生成樹就行了。
因為起點和終點也要計算,而除了終點都是經過兩次,又因為處理的時候沒有把每個起點和重點的值計算兩邊,所以需要找出最小的點權,並且從那裡作為起點,最後就是最小生成樹的值加上這個最小的點權。
程式碼
#include<bits/stdc++.h>
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.val<b.val;
}
int Find(int x){//並查集
return x==fa[x]?x:(fa[x]=Find(fa[x]));
}
int kruscal(){//最小生成樹板子……
int ans = 0;
sort(e,e+m,cmp);
for(int i=0;i<m;++i){
int x = Find(e[i].x);
int y = Find(e[i].y);
if(x != y){
fa[x] = y;
ans += e[i].val;
}
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)fa[i]=i;
for(int i=0;i<n;++i){
scanf("%d",&t[i]);
}
for(int i=0;i<m;++i){
scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].val);
e[i].val = 2*e[i].val+t[e[i].x-1]+t[e[i].y-1];//處理邊權
}
for(int i=0;i<n;++i){//找到最小點權
if(mn>t[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\)的點,然後從這個點開始拓撲排序就好了。
程式碼
#include<bits/stdc++.h>
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;
queue<int>q;
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\)列得到的最大值,狀態轉移如下:
如果算重了,減去就行。
程式碼
#include<bits/stdc++.h>
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\)的點的個數。
程式碼
#include<bits/stdc++.h>
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);
}