1. 程式人生 > 其它 >【賽後小結】2022.03.30 隊內訓練(使用2018年第15屆浙江省省賽試題)

【賽後小結】2022.03.30 隊內訓練(使用2018年第15屆浙江省省賽試題)

比賽相關資訊

比賽資訊

比賽名稱: The 15th Zhejiang Provincial Collegiate Programming Contest
比賽地址: Vjudge

全部參賽隊伍: 280 + 5*
金: 9題,1000m
銀: 7題,582m
銅: 6題,363m

比賽過程回顧

A B C D E F G H I J K L M
提交次數 1 1 1 5 1 1
首次提交時間 0:15:15 0:18:49 0:56:17 2:59:21 0:32:25 0:11:07
首A時間 0:15:15 0:18:49 0:56:17 0:32:25 0:11:17
狀態 ⚪補
知識點 組合數學 / 概率 DP 圖論 / 差分約束 / 貪心 數論 / 二分 數學規律 模擬

✔:比賽時通過;⚪:比賽時嘗試過;補:已補題。


部分題解與小結

A - Peak

小評

\(\mathcal{Consider\ by\ Hwh\ \&\ Wcj}\)

\(\mathcal{Solved\ by\ Wida}\)

打卡題(1 / 5),隊友在跟我說題意的時候聽錯了,直到第一遍碼完後發現過不了樣例才意識到理解錯題意,於是決定先把更簡單的 \(\tt{}M\) 做了再重構程式碼,導致時間上略慢。

題意

給出長度為 \(N\) 的序列,詢問其是否為凸序列。

思路

賽時思路

先找到序列最大值,再分別向左右遍歷。

AC程式碼

點選檢視程式碼
//====================
#define int LL
const int N = 1e6 + 7;
int a[N];
//====================
void Solve() {
    int n; cin >> n;
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    int m = max_element(a + 1, a + 1 + n) - a;
    int flag = 0;
    for (int i = m - 1; i >= 1; -- i)
        if (a[i - 1] >= a[i]) 
            flag = 1;
    for (int i = m + 1; i <= n; ++ i)
        if (a[i - 1] <= a[i])
            flag = 1;
    if (flag == 1 || m == 1 || m == n) cout << "No" << endl;
    else cout << "Yes" << endl;
}


B - King of Karaoke

小評

\(\mathcal{Consider\ by\ Wcj}\)

\(\mathcal{Solved\ by\ Wida}\)

打卡題(2 / 5),倒數第二簡單,不解釋。

題意

思路

AC程式碼

點選檢視程式碼
const int N = 1e5 + 7;
int a[N], b[N];

void solve() {
    int n; cin >> n;
    map<int, int> mp; int m = 0;
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    for (int i = 1; i <= n; ++ i) cin >> b[i];
    for (int i = 1; i <= n; ++ i) mp[a[i] - b[i]] ++ ;
    for (auto [i, j] : mp) m = max(m, j);
    cout << m << endl;
}


C - Magic 12 Months

https://blog.csdn.net/V5ZSQ/article/details/80205105



I - Magic Points

\(\mathcal{Solved\ by\ Wida(After)}\)

小評

這是一道和 \(\tt{}K\) 一樣處境的題目,但比 \(\tt{}K\) 題慘多了。在比賽全程幾乎都無人問津這道題,於是我也就象徵性的看了一下題目的意思,以為需要涉及到斜率等複雜計算,於是便沒有細想。

賽後覆盤的時候發現這居然也是一道規律+模擬的打卡題(?),只要想到了幾乎是秒做的程度,程式碼量也非常小。

題意

在二維座標軸上有 \(4*N-4\) 個不同的點,排布如下圖。

眾所周知,兩點確定一條直線。現在你需要連出 \(N\) 根不平行於座標軸的直線,使得這些直線相交點的數量最多,輸出任意一種方案即可。可以證明至少存在一種方案。

思路

貪心可得,要使得直線相交點的數量最多,那麼每一條直線與別的直線相交的點的數量都應當最多,即:後畫上去的直線應與所有已有的直線相交。相交點的數量最大值即為 \(\sum_{i=1}^{n-1} i\) 。容易得到 \(N-1\) 條直線的構造方式,如下。

現在的問題是,能否將剩下的一條直線放入圖中,使得其與已有的 \(N-1\) 條直線交 \(N-1\) 個點。從數學方法上論證,只需要這條直線的斜率的絕對值與此前所有直線斜率的絕對值均不相等即可。此前的直線斜率為 \(\frac{1}{N-1},\frac{2}{N-2},\frac{3}{N-3},…,\frac{N-1}{1}\) ,只要找到一個 \(X\) ,使得 \(X \neq i\) 並且 \(gcd(X,N-i)=1\) 即可。幸運的是,恆存在這樣的直線:斜率為 \(\frac{N-2}{1}\)\(\frac{1}{N-2}\)\(\frac{N-2}{N-1}\)\(\frac{N-1}{N-2}\) ,但是需要特判 \(N = 2\) 的情況。

AC程式碼

點選檢視程式碼
void Solve() {
    int n; cin >> n;
    if (n == 2) {
        cout << "0 2 1 3" << endl;
        return;
    }
    for (int i = 0; i < n - 1; ++ i) cout << i << " " << n + i << " ";
    cout << n - 1 << " " << 3 * (n - 1) + 1 << endl;
    // cout << 3 * n - 4 << " " << 4 * n - 5 << endl;
}


J - CONTINUE...?

\(\mathcal{Consider\ by\ Hwh\ \&\ Wcj}\)

\(\mathcal{Solved\ by\ Hwh}\)

小評

打卡題(3 / 5),初見兩個隊友想了有一會兒,我瞭解題意之後感覺不是自己擅長的,就交給兩個隊友了。沒過一會兒隊友討論完了,碼完直接一發過,非常順滑(Jls:還是隊友牛逼呀)。

題意

\(N\) 個學生從 \(1\)\(N\) 編號,每個人都帶有等同自己編號數量的寶石,要求將學生分成 \(G_1,G_2,G_3,G_4\) 四組,規定:

  • 每位學生都要被分到一個組;
  • 女生只能被分到 \(G_1,G_2\) 組,男生同理;
  • 要求 \(G_1,G_3\) 組的寶石數量等於 \(G_2,G_4\) 組的寶石數量。

輸出任意一種情況,不然則輸出 \(-1\)

思路

賽時思路(數論 + 暴力)

將整數 \(1\)\(N\) 分成兩組的情況為: \(\sum_{i=1}^n i \mid 2\)

本題的難點在於要將學生分為四組,而分析後我們可以發現,首先我們可以先不考慮四組的情形,只考慮將所有人分成兩組,使得兩組的寶石數相等,再去區分男女即可。

而分成兩組的方式也可以非常暴力,在保證能夠分成兩組的情況下,只需要倒序遍歷陣列即可。

AC程式碼

點選檢視程式碼
//====================
const int N = 1e6 + 7;
int a[N], v[N];
//====================
void Solve() {
    int n; cin >> n;
    for (int i = 1; i <= n; ++ i) {
        v[i] = 0;
        char x; cin >> x;
        a[i] = x - '0';
    }
    
    int add = (1 + n) * n / 2, num = 0;
    if (add % 2 == 1) {
        cout << "-1" << endl;
        return;
    }
    for (int i = n; i >= 1; -- i) {
        if (num + i <= add / 2) {
            num += i;
            v[i] = 1;
        }
    }
    for (int i = 1; i <= n; ++ i) {
        if (a[i] % 2 == 0) cout << 1 + v[i];
        else if (a[i] % 2 == 1) cout << 3 + v[i];
    }
    cout << endl;
}


K - Mahjong Sorting

\(\mathcal{Consider\ by\ Hwh\ \&\ Wida}\)

\(\mathcal{Solved\ by\ Wida(After)}\)

小評

在比賽的前半段,因為榜單裡做這道題的人極少,便遲遲沒開這題,中途隊友 \(\mathcal Hwh\) 也嘗試解這道題,但是限於過長的題幹以及較少的通過人數也沒有細看。到比賽後半段時解這一題的人逐漸多了起來,才正式開了這道題。題幹非常長,題意也不是特別清晰,連猜帶蒙的理解完題意才意識到這是一道模擬大水題,應該是能切掉的,於是開始模擬。但是由於時間較晚,隊員狀態也不是特別好,在連WA五次後還是沒能解出來。

賽後查閱題解,發現全網關於這道題的題解只有一篇,但在參考AC程式碼後發現這道題應該算是打卡題之一,可能是因為過長的題幹導致大家都以為這道題難度極高,這一點需要反思。

題意

背景知識如下:

本題的麻將一共有 \(3*M+1\) 塊,分別是 \(M\) 塊條子, \(M\) 塊竹, \(M\) 塊點,這 \(3*M\) 塊普通麻將上面都有一個數字代表它的大小,以及 \(1\) 塊特殊的白龍。 \(3*M\) 塊普通麻將按照一般順序排序從前往後依次為:條子,竹,點。

現在,從 \(3*M\) 塊普通麻將中選出一個“幸運塊”,然後再從全部 \(3*M+1\) 塊麻將中選出 \(N\) 塊麻將進行排序。規定新的排序方式如下:

  • 如果這 \(N\) 塊麻將中有“幸運塊”,那麼需要放在最左邊;
  • 如果這 \(N\) 塊麻將中有白龍,那麼需要放在幸運塊原來的位置。

給出按新排序方式排序過後的 \(N\) 塊麻將的順序,求解“幸運塊”可能的數量。保證至少有一種解。

思路

弄懂題目之後很顯然的想到了模擬。可以分成以下幾種情況:

  • 白龍在第一個時,答案與第二個有關;
  • 白龍在最後一個時,答案與倒數第二個有關;
  • 白龍在中間時,與前後兩個都有關;
  • 不存在白龍時,答案與 \(N\) 有關。

注意以上各情況均為理想情形,要加上一些判斷:

  • \(N\)\(1\) 時,所有塊都能為“幸運塊”,直接輸出 \(3*M\)
  • 白龍在第二個時,第一個塊也有可能是“幸運塊”,答案加一;
  • 第一個和第二個均不為白龍,且第一個大於第二個,說明第一個是被移動過來的“幸運塊”,直接輸出 \(1\)

各種情況的先後順序要仔細考慮,答案極多,滿足所有條件即可。

AC程式碼

點選檢視程式碼
//====================
const int N = 1e6 + 7;
int num[N];
//====================
void Solve() {
    int n, m, s = 0; cin >> n >> m;
    for (int i = 1; i <= n; ++ i) {
        char x; cin >> x;
        int w = 0;
        if (x != 'W') cin >> w;
        if (x == 'C') num[i] = w;
        else if (x == 'B') num[i] = m + w;
        else if (x == 'D') num[i] = m + m + w;
        else num[i] = 0, s = i;
    }
    
    int ans = 0;
    if (n == 1) ans = 3 * m;
    else if (num[1] > num[2] && s != 1 && s != 2) ans = 1;
    else if (s == 0) ans = 3 * m - n + 1;
    else if (s == 1) ans = num[2] - 1;
    else {
        if (s == 2) ans = 1;
        if (s == n) ans += 3 * m - num[n - 1];
        else ans += num[s + 1] - num[s - 1] - 1;
    }
    cout << ans << endl;
}


L - Doki Doki Literature Club

小評

\(\mathcal{Consider\ by\ Hwh}\)

\(\mathcal{Solved\ by\ Wida\ \&\ Wcj}\)

打卡題(4 / 5),剛看到這題的時候以為是字典樹,就交給隊友讀了。這道題題目較長,但是讀懂了之後發現就是一個自定義排序,寫起來不難。

題意

給定 \(N\) 個單詞及其權值,你需要按照規定順序排序:

  • 權值大的在前;
  • 權值相同時,字典序小的在前。

要求選出前 \(M\) 個,輸出總值( \(\sum_{i=1}^m (m - i + 1) * w_i\) ,其中 \(w_i\) 是第 \(i\) 個單詞的權值)與連成的句子。

思路

自定義排序,暴力計算總值。

AC程式碼

點選檢視程式碼
struct Node{
    int x;
    string y;
};
bool cmp(Node x, Node y) {
    if (x.x == y.x) return x.y < y.y;
    return x.x > y.x;
}
void solve() {
    vector<Node> ver;
    string ans;
    int cnt = 0;
    int n, m; cin >> n >> m;
    for (int i = 1; i <= n; ++ i) {
        string s; int num;
        cin >> s >> num;
        ver.push_back({num, s});
    }
    sort(ver.begin(), ver.end(), cmp);
    for (int i = 0; i < m; ++ i) {
        auto [x, y] = ver[i];
        ans += " " + y;
        cnt += (m - i) * x;
    }
    cout << cnt << ans << endl;
}


M - Lucky 7

小評

\(\mathcal{Consider\ by\ Wcj}\)

\(\mathcal{Solved\ by\ Wida}\)

打卡題(5 / 5),最簡單的一題,不解釋了。

題意

思路

AC程式碼

點選檢視程式碼
const int N = 1e5 + 7;
int a[N];

void solve() {
    int n, k; cin >> n >> k;
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    for (int i = 1; i <= n; ++ i) {
        if ((a[i] + k) % 7 == 0) {
            cout << "Yes" << endl;
            return;
        }
    }
    cout << "No" << endl;
}

文 / WIDA
2022.03.31 成文
首發於WIDA個人部落格,僅供學習討論