1. 程式人生 > 其它 >2020ICPC臺灣網路賽(A、B、C、H、I)

2020ICPC臺灣網路賽(A、B、C、H、I)

  • A. Pac-Man

  • 題意:給出一張\(10 \times 10\)的地圖和一個起點,輸出一個從起點開始遍歷整張圖的路徑,步數不能超過10000 。

  • 思路:dfs、思維。

  • 解析:

    • 解法一:直接用dfs遍歷整張圖,但是此題需要列印完整路徑,說明當在走到某一個地方無路可走並且仍然未遍歷完整張圖的情況下,需要回溯輸出到起點路徑,這樣才能避免路徑不完整的情況,假如一次能走通,那也要輸出一遍回到起點的路徑,相當於輸出往返的路徑(有點笨)。
    • 解法二:從起點先到達(0,0),然後“蛇形”的遍歷整張圖即可,但是需要特判起點在(0, 0)的情況,並且在蛇形填數時也不能從(0, 0)開始,因為之前已經從起點回到(0, 0),下一步肯定是走向(0, 1),否則就會輸出兩次(0, 0),也就是在(0, 0)停留。
  • 程式碼一:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N = 15;
    int g[N][N], vis[N][N];
    int cnt = 0;
    int dir[4][2] = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
    void dfs(int x, int y)
    {
        cout << x << " " << y << endl;
        vis[x][y] = 1;
        cnt ++;
        if(cnt == 100) return; //總共走完100個點
        for(int i = 0; i < 4; i++)
        {
            int xx = x + dir[i][0];
            int yy = y + dir[i][1];
            if(xx >= 0 && xx < 10 && yy >= 0 && yy < 10 && !vis[xx][yy])
            {
                dfs(xx, yy);
                cout << x << " " << y << endl; //回溯輸出路徑
            }
        }
    }
    
    int main()
    {
        int x, y;
        scanf("%d%d", &x, &y);
        dfs(x, y);
        return 0;
    }
    
    
  • 程式碼二:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    
    int main()
    {
        int x, y;
        cin >> x >> y;
        if(x || y) //起點不為(0, 0)
        {
            for(int i = x; i >= 0; i--) cout << i << " " << y << endl;
            for(int i = y - 1; i >= 0; i--) cout << 0 << " " << i << endl;
        }
        else cout << "0 0" << endl;
        for(int i = 0; i < 10; i++)
        {
            if(i & 1)
            {
                for(int j = 9; j >= 0; j--)
                    cout << i << " " << j << endl;
            }
            else
            {
                for(int j = 0; j < 10; j++)
                {
                    if(i == 0 && j == 0) continue; //不能停留在(0, 0)
                    cout << i << " " << j << endl;
                }
            }
        }
    }
    
    
  • B. Folding

  • 題意:假設有一條透明磁帶(可以假象是有n個小格子隔開,例如[1,2]為一個格子),給出兩個已經被染色的區間\([p_1,q_1],[p_2, q_2]\)\(0 \leq p_1<q_1<p_2<q_2 \leq 10^9\)),有\(q\)\(1 \leq q \leq 10^6\))次詢問,每次給出一個摺疊該磁帶的位置\(x\)\(0 \leq x \leq 10^9\)),磁帶摺疊後可以理解成長度會改變(例如對摺長度減半),並且有顏色的格子會將無顏色的格子染色,問摺疊後磁帶中被染色的格子數量(假如之前兩個格子都有顏色,那麼重疊在一起後實際上只算一個格子被染色)。

  • 思路:分類討論。

  • 解析:首先假定磁帶平放在桌面上,規定順時針(從左到右)摺疊磁帶,摺疊在上方的部分可以覆蓋下方的磁帶(但是顏色是可以穿透的),然後摺疊完畢後磁帶的總長度剔除掉在下方磁帶的長度。接下來進行分類討論(此時可能存在有染色區間覆蓋,最後還要進行一次區間覆蓋的分類討論):

    • \(x\)\(p_1\)左側或者在\(p_2\)右側,則被染色的格子數量不變,結果為一開始被染色的兩個區間長度:\(q_1-p_1+q_2-p_2\);

    • \(x\)\(p_1與q_1\)之間,那麼摺疊後磁帶的中有顏色的起始點必定為\(x\),而由\([p_1,q_1]\)染色的終點將是\(max\{q_1, 2x - p_1\}\)(可能x在這個區間內左半部染色部分長度大於右半部的染色部分,所以要取max),然後由\([p_2, q_2]\)染色的區間不變,之後還會對他們兩者的區間覆蓋進行分類討論,現在僅僅關心會染色的最大區域範圍。即:\(l_1 = x, r_1 = max\{q_1, 2x-p_1\}, l_2 = p_2, r_2 = q_2\);

    • \(x\)\(p_2與q_2\)之間,同理可得摺疊後,\([p_2, q_2]\)能染色的貢獻與第一種情況類似,起點為\(x\), 終點為\(max\{q_2, 2x - p_2\}\),而\([p_1, q_1]\)能覆蓋到的區間為\([2x-q_1, 2x-p_1]\)。即:

      \(l_1 = x, r_1 = max\{q_2, 2x-p_2\}, l_2 = 2x-q_1, r_2 = 2x-p_1\);

    • \(x\)\(q_1與p_2\)之間,則\(l_1 = 2x-q_1, r_1 = 2x-p_1, l_2 = p_2, r_2 = q_2\);

    接著需要對\([l_1, r_1], [l_2, r_2]\)重疊的區間進行剔除,最後算出總的被染色格子數量,首先對這兩個區間根據左端點從小到大排序。

    • 若兩區間無交集,結果則為:\(r_1-l_1+r_2-l_2\);
    • 若兩區間有交集或第二個區間與第一個區間左端點重合(\(l_1 = l_2\)), 結果為:\(r_2 - l_1\);
    • 若第一個區間包圍第二個區間或者第一個區間和第二個區間左端點重合,結果為:\(r_1 - l_1\);
  • 程式碼:

    #include<iostream>
    using namespace std;
    typedef long long ll;
    ll p1, q1, p2, q2;
    int q;
    
    struct Node
    {
        ll l, r;
    }a[5];
    
    
    void swapNode()
    {
        if(a[1].l > a[2].l)
        {
            Node temp;
            temp = a[1];
            a[1] = a[2];
            a[2] = temp;
        }
        return;
    }
    
    int main()
    {
        cin >> p1 >> q1 >> p2 >> q2;
        cin >> q;
        while(q --)
        {
            ll x;
            cin >> x;
            ll sum = q1 - p1 + q2 - p2;
            if(x <= p1 || x >= q2)
            {
                cout << sum << endl;
                continue;
            }
            else if(p1 < x && x < q1) //摺痕在第一個區域中
            {
                a[1].l = x, a[1].r = max(q1, (x << 1) - p1);
                a[2].l = p2, a[2].r = q2;
            }
            else if(p2 < x && x < q2) //摺痕在第二個區域中
            {
                a[1].l = x, a[1].r = max(q2, (x << 1) - p2);
                a[2].l = (x << 1) - q1, a[2].r = (x << 1) - p1;
            }
            else //摺痕在第一個區域和第二個區域之間
            {
                a[1].l = (x << 1) - q1, a[1].r = (x << 1) - p1;
                a[2].l = p2, a[2].r = q2;
            }
            swapNode(); //對兩個區間排序
            if(a[1].r < a[2].l) //兩個區間無交集
                cout << a[1].r - a[1].l + a[2].r - a[2].l << endl;
            else if(a[2].l <= a[1].r && a[2].r > a[1].r) //區間有交集或第二個區間與第一個區間左端點重合(l1 = l2)
                cout << a[2].r - a[1].l << endl;
            else //第一個區間包圍第二個區間或者第一個區間和第二個區間左端點重合
                cout << a[1].r - a[1].l << endl;
        }
    
        return 0;
    }
    
    
  • C. Circles

  • 題意:給出\(n(2 \leq n \leq 2000)\)對座標\((x_i,y_i)\),每個座標代表一個圓心且一定不完全相同,\(-10^9 \leq x_i, y_i, \leq 10^9\),所有圓心以相同的增長速度半徑增大,直到與周圍某個圓相碰時停止增長,要求計算出當所有圓停止增長時的總面積,結果與答案誤差不超過\(10^{-6}\)

  • 思路:模擬、簡單數學。

  • 解析:首先找出一對最先碰撞的圓,記錄其編號為\(p, q\), 並記錄這兩個圓能擴大的最大半徑為\(rm\),每次找到這樣的一對圓後,判斷是否兩者均未被確認能擴大的最大半徑,若均未被確認,則面積為兩倍以\(rm\)為半徑的圓,否則只是以\(rm\)為半徑的圓。

    接著需要將所有未確定最大半徑的圓\(i\)與(\(p, q\))中剛剛確認最大半徑的圓(可能是兩個,也可能只有一個)重新計算\(i\)\(碰撞到(p|q|(p, q二者))\)的半徑記為\(r_{i, p},r_{i, q}\)

    然後再找出下一對最先碰撞的圓,即所有圓中將發生碰撞半徑最小的一個圓或者一對圓,但是這裡需要稍微優化一下,不然時間複雜度會爆掉,在找下一對\((p,q)\)時起始複雜度應該為:\(O(n^2)\),但是我們可以在第一層迴圈時判斷第\(i\)個圓是否已經確認了面積(最大半徑),那麼這時候我們可以直接忽略它,因為就算它相鄰的一些圓會成為這一次最先碰撞的圓,那麼肯定會在第一層迴圈訪問到該圓的,所以這樣可以減少時間複雜度。

    最後這樣一直迴圈到\(n\)個圓都確認面積時即可結束。

  • 程式碼:

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<cmath>
    const int N = 2005;
    const double inf = 1e10;
    using namespace std;
    int n, vis[N];
    double r[N][N]; //i -> j目前最大的半徑
    double res = 0, rm = inf; //半徑最小值
    int p, q; //當前情況下最先發生碰撞的兩個圓的編號
    struct Node
    {
        double x, y;
    }a[N];
    
    double cal(double x1, double y1, double x2, double y2)
    {
        return sqrt( (y1 - y2) * (y1 - y2) + (x1 - x2) * (x1 - x2));
    }
    
    int main()
    {
        cin >> n;
        for(int i = 1; i <= n; i++) cin >> a[i].x >> a[i].y;
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(i == j) continue;
                double dis = cal(a[i].x, a[i].y, a[j].x, a[j].y) / 2.0;
                r[i][j] = dis;
                if(r[i][j] < rm)
                {
                    rm = r[i][j];
                    p = i, q = j;
                }
            }
        }
        int cnt = 0;
        while(cnt < n)
        {
            if(!vis[p] && !vis[q]) //均未確認
            {
                res += acos(-1) * rm * rm * 2.0;
                cnt += 2;
            }
            else //其中一個未確認
            {
                res += acos(-1) * rm * rm;
                cnt ++;
            }
            for(int i = 1; i <= n; i++) //更新所有圓與剛剛那對圓中未確認最大半徑的圓發生碰撞的邊界半徑
            {
                if(i == p || i == q) continue;
                if(!vis[p]) r[i][p] = cal(a[i].x, a[i].y, a[p].x, a[p].y) - rm;
                if(!vis[q]) r[i][q] = cal(a[i].x, a[i].y, a[q].x, a[q].y) - rm;
            }
            vis[p] = vis[q] = 1; //這兩個圓的最大半徑已經確定
            //找出下一對最先碰撞的圓
            rm = inf;
            for(int i = 1; i <= n; i++)
            {
                if(vis[i]) continue; //優化
                for(int j = 1; j <= n; j++)
                {
                    if(i == j) continue; //不可能為同一個點
                    if(rm > r[i][j])
                    {
                        rm = r[i][j];
                        p = i, q = j;
                    }
                }
            }
        }
        printf("%.15lf\n", res);
        return 0;
    }
    
    
  • H. In The Name Of Confusion

  • 題意:給出\(n(1\leq n \leq 10^6)\)個點,每個點的權值為\(a_i(|a_i| \le10^6)\),要求連線\(n-1\)條邊,使得形成一棵樹,每連線一條邊的花費為\(a_i \times a_j\)\(i, j\)為這條邊的兩個點),求出最小花費和最大花費並對最後結果取\(mod = 10^9 + 7\),取模後的結果必須為正數。

  • 思路:最小生成樹、最大生成樹、同餘定理、分類討論。

  • 解析:首先用\(a_i\)記錄權值為正(包括0)的結點,\(cnta\)記錄其個數,同理用\(b_i\)記錄權值為負的結點,\(cntb\)記錄其個數,並將\(a_i,b_i\)均降序排序。

    • 當所有結點均為正值(\(cnta == n\))時:

      • 最大生成樹:所有結點均與最大權值的結點連線,總花費:\(a_1 \times \sum_{i=2}^{cnt_a}a_i\);
      • 最小生成樹:所有結點均與最小權值的結點連線,總花費:\(a_n \times \sum_{i = 1}^{cnt_a-1}a_i\);
    • 當所有結點均為負值(\(cntb == n\))時:

      • 最大生成樹:所有結點均與最小權值的結點連線,總花費:\(b_n \times \sum_{i = 1}^{cnt_b-1}b_i\);
      • 最小生成樹:所有結點均與最大權值的結點連線,總花費:\(b_1 \times \sum_{i=2}^{cnt_b}b_i\);
    • 當正負權值結點均存在時:

      • 最大生成樹:正值所有結點與最大正值結點相連,負值所有結點與最小負值結點相連,最後最大負值結點再與最小正值結點相連,總花費:\(a_1 \times \sum_{i=2}^{cnt_a}a_i + b_n \times \sum_{i = 1}^{cnt_b-1}b_i + a_{cnt_a} \times b_1\);
      • 最小生成樹:正值所有結點與最小負值結點相連,負值所有結點與最大正值結點相連,最後最大正值結點再與最小負值結點相連,總花費:\(b_{cnt_b} \times \sum_{i = 2}^{cnt_a}a_i + a_1 \times \sum_{i=1}^{cnt_b - 1} + a_1 \times b_{cnt_b}\);

    ps:因為最後需要取mod,並且結果必須為正值,因為最小生成樹的值可能為負值,假設總花費為\(x\),則\(x \% mod = (x \% mod + mod) \% mod\)

  • 程式碼:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N = 1e6 + 5;
    const ll mod = 1e9 + 7;
    ll a[N], b[N], res1 = 0, res2 = 0;
    int cnta = 0, cntb = 0;
    int n;
    
    bool cmp(ll x, ll y)
    {
        return x > y;
    }
    
    int main()
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
        {
            ll x;
            scanf("%lld", &x);
            if(x >= 0) a[++cnta] = x;
            else b[++cntb] = x;
        }
        sort(a + 1, a + 1 + cnta, cmp);
        sort(b + 1, b + 1 + cntb, cmp);
        if(cnta == n) //全為正數
        {
            for(int i = 2; i <= cnta; i++)
                res1 += a[1] * a[i];
            for(int i = 1; i < cnta; i++)
                res2 += a[cnta] * a[i];
        }
        else if(cntb == n) //全為負數
        {
            for(int i = 1; i < cntb; i++)
                res1 += b[cntb] * b[i];
            for(int i = 2; i <= cntb; i++)
                res2 += b[1] * b[i];
        }
        else //有正有負
        {
            for(int i = 2; i <= cnta; i++)
                res1 += a[1] * a[i];
            for(int i = 1; i < cntb; i++)
                res1 += b[cntb] * b[i] ;
            res1 += a[cnta] * b[1];
            for(int i = 2; i <= cnta; i++)
                res2 += b[cntb] * a[i];
            for(int i = 1; i < cntb; i++)
                res2 += a[1] * b[i];
            res2 += a[1] * b[cntb];
        }
        res1 = (res1 % mod + mod) % mod;
        res2 = (res2 % mod + mod) % mod;
        printf("%lld %lld\n", res2, res1);
        return 0;
    }
    
    
  • I. Site Score

  • 題意:計算出\(56 \times a + 24 \times b + 14 \times c + 6 \times d\)

  • 思路:簽到。

  • 解析:無。

  • 程式碼:

    #include<bits/stdc++.h>
    using namespace std;
    
    int main()
    {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        cout << 56 * a + 24 * b + 14 * c + 6 * d << endl;
    	return 0;
    }