AcWing第85場周賽

勇敢龍龍發表於2023-01-08

這場周賽是手速局hh

死或生

某國正在以投票的方式決定 2 名死刑犯(編號 1∼2)的生死。

共有 n 組人員(編號 1∼n)參與投票,每組 10 人。

每組成員只參與一名死刑犯的投票,其中第 i 組人員的投票物件是死刑犯 ti,其中 xi 人認為他無罪,yi 人認為他有罪。

在所有人員投票結束後,將對投票結果進行統計。

對於每名死刑犯,如果投他無罪的總票數大於或等於投他有罪的總票數,則他得以生還,否則他將被處死。

請你判斷每名死刑犯的生死。

輸入格式
第一行包含一個整數 n。

接下來 n 行,每行包含三個整數 ti,xi,yi。

保證兩名犯人都會被投票。

輸出格式
如果第一位死刑犯生還,則在第一行輸出 LIVE,否則在第一行輸出 DEAD。

如果第二位死刑犯生還,則在第二行輸出 LIVE,否則在第二行輸出 DEAD。

資料範圍
\(前 3 個測試點滿足 2≤n≤10。\)
\(所有測試點滿足 2≤n≤1000,1≤ti≤2,0≤xi,yi≤10,xi+yi=10。\)

輸入樣例1:

2
1 5 5
2 6 4

輸出樣例1:

LIVE
LIVE

輸入樣例2:

3
1 0 10
2 0 10
1 10 0

輸出樣例2:

LIVE
DEAD

簡單的列舉即可,藉助雜湊表記錄無罪票數和有罪票數

#include <bits/stdc++.h>
using namespace std;
int a[3],b[3];
int n;
int main()
{
    cin>>n;
    while (n--)
    {
        int t,x,y;
        cin>>t>>x>>y;
        a[t]+=x;
        b[t]+=y;
    }
    
    for (int i=1;i<=2;i++)
        if (a[i]>=b[i])
            puts("LIVE");
        else puts("DEAD");
    
    return 0;
}

最大價值

已知,小寫字母 a∼z 的價值分別為$ w_a,w_b,…,w_z$。

對於一個由小寫字母構成的長度為 l 的字串 \(S=s_1,s_2…s_l,其價值為 w_{s1}×1+w_{s2}×2+…+w_{sl}×l\)

現在,給定一個由小寫字母構成的字串 S,請你在這個字串中插入 k 個小寫字母,要求最終得到的字串的價值儘可能大。

注意:

  • 插入的位置可以隨意選。
  • 插入的字母也可以隨意選,可以插入不同字母。

輸出最大可能價值。

輸入格式
第一行包含一個字串 S。

第二行包含一個整數 k。

第三行包含 26 個整數 \(w_a,w_b,…,w_z\)

輸出格式
一個整數,表示最大可能價值。

資料範圍
前 3 個測試點滿足,S 的長度範圍 [1,5]。
所有測試點滿足,S 的長度範圍 [1,1000],\(0≤k≤10^3\)\(w_a∼w_z\) 的取值範圍 [0,1000]。

輸入樣例:

abc
3
1 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

輸出樣例:

41

貪心即可,經過證明(很好證)要得到最大價值,插入方法即在尾部插入k個單個價值的最大的字母

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
string s;
int w[N];
int k;
typedef long long LL;
int main()
{
    cin>>s>>k;
    int res=0;
    for (int i=0;i<26;i++)
        cin>>w[i],res=max(res,w[i]);
    
    LL ans=0;
    
    for (int i=0;i<s.size();i++)
        ans+=(w[s[i]-'a']*(i+1));  // 這裡一定要記著-'a'啊!血的教訓,這細節要注意!
    
    int len=s.size();
    
    for (int i=len+1;i<=k+len;i++)
    {
        ans+=res*i;
    }
    cout<<ans<<endl;
    return 0;
}

危險程度

有 n 種化學物質,編號 1∼n。

其中,有 m 對物質之間會發生反應。

現在,要將這些化學物質逐個倒入同一個試管之中,具體倒入順序不限。

我們需要計算一下試管的危險值。

已知,空試管的危險值為 1,每倒入一種化學物質,如果該物質能夠與之前倒入試管中的一種或多種物質發生反應,則試管的危險值將乘以 2。

請你計算並輸出,透過合理安排所有化學物質的倒入順序,能夠得到的試管的最大危險值。

輸入格式
第一行包含兩個整數 n,m。

接下來 m 行,每行包含兩個整數 x,y,表示化學物質 x 和化學物質 y 之間會發生反應。保證同一對化學物質在輸入中最多出現一次。

輸出格式
一個整數,表示最大危險值。

資料範圍
前 4 個測試點滿足 \(1≤n≤10。\)
所有測試點滿足 $ 1≤n≤50,0≤m≤n(n−1)2,1≤x<y≤n。$

輸入樣例1:

1 0

輸出樣例1:

1

輸入樣例2:

2 1
1 2

輸出樣例2:

2

輸入樣例3:

3 2
1 2
2 3

輸出樣例3:

4

題意中幾個很重要的性質抓出來

  • 第一個放入的物品無法和其他物質反映,因為此時試管中沒有其他物品
  • 不能相互反應的物品一定嚴格獨立,沒有交集
  • 假設同一個反應體系中的物品數為k個,則該反應體系對危險程度的貢獻度為\(2^{k-1}\),因此我們可以看出,每一個反應體系實際就是一個連通塊,即每一個連通塊中的物品數量為\(k_i\),則該連通塊的作用即可為\(2^{k_i}-1\)
    現在共有t個獨立的連通塊,則總的貢獻度為\(2^{k_1}-1\) * \(2^{k_2}-1\) * ... * \(2^{k_i}-1\) = \(2^{k_1+k_2+...+k_i-t}\)
    我們注意到共有n件物品,因此結果為\(2^{n-t}\),題目瞬間轉化為求解獨立的連通塊的數量

1. 法一:並查集求解獨立連通塊數量

#include <bits/stdc++.h>
using namespace std;
const int  N = 55;
int p[N];
int n,m;
typedef long long LL;
int find(int x)
{
    if (p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin>>n>>m;
    for (int i=1;i<=n;i++) p[i]=i;
    
    int cnt=n; // cnt為獨立集合的數量,即連通塊的數量
    while (m--)
    {
        int a,b;
        cin>>a>>b;
        int pa = find(a);
        int pb = find(b);
        if (pa!=pb)
        {
            cnt--;
            p[pa]=pb;
        }
    }
    
    printf("%lld\n",1ll<<n-cnt); 
    return 0;
    

}

2. 建圖,圖的遍歷求解連通塊的數量

dfs寫法一

#include <bits/stdc++.h>
using namespace std;
const int N = 55;
bool st[N],reaction[N][N];
int n,m;
typedef long long LL;
LL ans=1;
void dfs(int x)
{
    for (int i=1;i<=n;i++)
    {
        if (reaction[x][i]&&!st[i]) // 當前物品與1~i(除本身)有反應,即更新ans並順著這個物品遍歷
        {
            ans<<=1;
            st[i]=true;
            dfs(i);
        }
    }
}
int main()
{
    cin>>n>>m;
    while (m--)
    {
        int a,b;
        cin>>a>>b;
        reaction[a][b]=reaction[b][a]=true;
    }
    
    for (int i=1;i<=n;i++)
    {
        if (!st[i])  // 只要當前的物品還沒有用過
        {
            st[i]=true;
            dfs(i);
        }
    }
    
    cout<<ans<<endl;
    return 0;
}

dfs寫法二

#include <bits/stdc++.h>
using namespace std;
const int N = 55 ,M = N*N; // 無向圖注意邊數
int e[M],h[N],ne[M],idx;
int n,m;
typedef long long LL;
bool st[N]; 
void add(int a,int b) // 經典鄰接表建圖add
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int x) // dfs遍歷無向圖
{
    st[x]=true;
    for (int i=h[x];~i;i=ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h); // 不初始化的後果就是TLE
    while (m--)
    {
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a); // 無向圖即為特殊的有向圖
    }
    int cnt=0;  // 求解連通塊的數量
    for (int i=1;i<=n;i++)
    {
        if (!st[i])
        {
            dfs(i);
            cnt++;
        }
    }
    
    cout<<(1ll<<n-cnt)<<endl; // 答案為2^{n-cnt}
    return 0;
}

bfs寫法

#include <bits/stdc++.h>
using namespace std;
const int N = 55 ,M = N*N; // 無向圖注意邊數
int e[M],h[N],ne[M],idx;
int n,m;
typedef long long LL;
bool st[N]; 
queue<int>q;
void add(int a,int b) // 經典鄰接表建圖add
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void bfs(int x) // bfs遍歷無向圖
{
    st[x]=true;
    q.push(x); // 藉助stl
    while (q.size())
    {
        auto t = q.front();
        q.pop();
        for (int i=h[t];~i;i=ne[i])
        {
            int j = e[i];
            if (!st[j])
            {
                st[j]=true;
                q.push(j);
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h); // 不初始化的後果就是TLE
    while (m--)
    {
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a); // 無向圖即為特殊的有向圖
    }
    int cnt=0;  // 求解連通塊的數量
    for (int i=1;i<=n;i++)
    {
        if (!st[i])
        {
            bfs(i);
            cnt++;
        }
    }
    
    cout<<(1ll<<n-cnt)<<endl; // 答案為2^{n-cnt}
    return 0;
}

總結

不論是並查集寫法,還是dfs和bfs寫法,本質都是求解圖中的連通子塊個數。
因此,我們對求解連通塊個數的題型,即可採用並查集和圖的遍歷這兩大類方法,其中圖的遍歷可以用dfs(程式碼簡潔)或者bfs(思路簡單,但藉助佇列實現程式碼量較為冗長)


這次確實不難,還是fw,繼續努力吧~

相關文章