1. 程式人生 > 其它 >【LGR-109】洛谷 5 月月賽 II & Windy Round 6

【LGR-109】洛谷 5 月月賽 II & Windy Round 6

比賽連結

排名


(見證歷史)

A P8344 「Wdoi-6」走在夜晚的蓮臺野

顯然有解要滿足 \(x \leq z\) 否則無解.
再考慮最多有多少個銀色木板能被放入:
顯然我們一定要先放銀色, 再放金色(否則先放的金色會被浪費, 沒有利用).
於是我們可以貪心地每次都放滿銀色, 再預留一個位置給即將放的金色. 具體地:

\(1\) 次操作: 可以放入 \(z - 1\) 個銀色, 再放入一個金色, 取出 \(z - 1\) 個銀色 (最後 \(1\) 個位置留給金色)
\(2\) 次操作: 可以放入 \(z - 2\) 個銀色, 再放入一個金色, 取出 \(z - 2\) 個銀色 (前面已經有了 \(1\)

個金色, 再預留 \(1\) 個位置)
\(3\) 次操作: 可以放入 \(z - 3\) 個銀色, 再放入一個金色, 取出 \(z - 3\) 個銀色 (前面已經有了 \(2\) 個金色, 再預留 \(1\) 個位置)
......
\(x\) 次操作: 可以放入 \(z - x\) 個銀色, 再放入一個金色, 取出 \(z - x\) 個銀色 (前面已經有了 \(z - x - 1\) 個金色, 再預留 \(1\) 個位置)

最後一個操作後還剩下 \(z - x\) 個位置, 此時金色已經用完, 可以全部放銀色.
故對於 \(y\) 需要滿足 \(y \leq (z - 1) + (z - 2) + .. + (z - x) + (z - x)\)


\(y \leq \frac{(2z - x - 1)x}{2} + z - x\) (等差數列求和都會吧)

注意 \(1 \leq y \leq 5 \times 10^{17}\) , 所以要開 long long.
時間複雜度: \(O(1)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;

inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

int t, x, y, z;

signed main() {
    t = read();
    while(t--) {
        x = read(); y = read(); z = read();
        if(x <= z && y <= (2 * z - x - 1) * x / 2 + z - x) puts("Renko");
        else puts("Merry");
    }

    return 0;
}

B P8345 「Wdoi-6」華胥之夢

假設當前我們第 \(i\) 個走過的點為 \(p_i\) 那麼據題意, 有:
\(length = \sum _{i = 1} ^{|s| - 1} (a[p_i] - 2a[p_{i + 1}] + c)\)
\(\ \ \ \ \ \ \ \ \ \ \ = (|s| - 1)c + \sum _{i = 1} ^{|s - 1|} a[p_i] - 2 \sum _{i = 1} ^{|s - 1|} a[p_{i + 1}]\)
\(\ \ \ \ \ \ \ \ \ \ \ = (|s| - 1)c + \sum _{i = 1} ^{|s - 1|} a[p_i] - 2 \sum _{i = 2} ^{|s|} a[p_{i}]\)
\(\ \ \ \ \ \ \ \ \ \ \ = (|s| - 1)c + (\sum _{i = 2} ^{|s - 1|} a[p_i] + a[p_i]) - (2 \sum _{i = 2} ^{|s - 1|} a[p_{i}] + 2a[p_{|s|}])\)
\(\ \ \ \ \ \ \ \ \ \ \ = (|s| - 1)c + a[p_i] - 2a[p_{|s|}] + \sum _{i = 2} ^{|s - 1|} a[p_i] - 2 \sum _{i = 2} ^{|s - 1|} a[p_{i}]\)
\(\ \ \ \ \ \ \ \ \ \ \ = (|s| - 1)c + a[p_i] - 2a[p_{|s|}] - \sum _{i = 2} ^{|s - 1|} a[p_i]\)
\(\ \ \ \ \ \ \ \ \ \ \ = (|s| - 1)c + 2a[p_i] - a[p_{|s|}] - \sum _{i = 1} ^{|s|} a[p_i]\)
推到這裡, 我們發現, 要使 \(length\) 最小, 只需要使 \(a[p_i]\) 最小, \(2a[p_{|s|}]\) 最大即可.

注意, 最終結果有可能爆 int , 保險起見要開 long long .
剩下的套公式就好了:
時間複雜度: \(O(n)\)

#include <bits/stdc++.h>
using namespace std;

inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

const int N = 1e6 + 10;
int n, c, q, l, minv, maxv, a[N], s[N];
long long sum;

inline bool cmp(int &x, int &y) { return a[x] < a[y]; }

int main() {
	n = read(); c = read(); q = read();
	for(int i = 1; i <= n; ++i) a[i] = read();

	while(q--) {
		l = read(); 
		sum = 0; minv = 0x3f3f3f3f; maxv = -0x3f3f3f3f;
		for(int i = 1; i <= l; ++i) { s[i] = read(); sum += a[s[i]]; minv = min(minv, a[s[i]]); maxv = max(maxv, a[s[i]]); }
		printf("%lld\n", ((long long)l - 1) * c + 2 * minv - maxv - sum);
	}

    return 0;
}

C P8346 「Wdoi-6」最澄澈的空與海

顯然有一個結論: 如果一個點只有一條邊與之相連, 那麼它只能和這條邊上另一點相連.
於是我們可以迴圈找這樣的點, 然後讓它與這條邊上另一點配對.
還有一種情況就是, 雖然這個點有很多邊, 但是其它的都已經配對過, 只剩下一個點沒有配對, 那麼也可以直接相連.
最後看所有點是否配對即可.

個人認為這題甚至比上一題簡單.
時間複雜度: \(O(n + m)\) (瞎推的, 有可能不對, 反正能過)

#include <bits/stdc++.h>
using namespace std;

inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

const int N = 1e7 + 10;
int t, n, m, u, v, res, tmp;//res:目前配對個數 tmp:上一次配對個數
bool vis[N];//vis[i]:i是否配對

int main() {
	t = read();
	while(t--) {
		n = read(); m = read(); 
		
		res = 0;
		vector<int> mp[N];
		for(int i = 1; i <= (n << 1); ++i) vis[i] = 0;

		for(int i = 1; i <= m; ++i) {
			u = read(); v = read() + n;
			mp[u].push_back(v);
			mp[v].push_back(u);
		}

        do {
            tmp = res;

            for(int i = 1; i <= (n << 1); ++i) {
                if(vis[i]) continue;
                int pos = -1;
                for(int j = 0; j < mp[i].size(); ++j) {
                    if(vis[mp[i][j]]) continue;
                    if(pos == -1) pos = j;
                    else { pos = -1; break; }
                }

                if(pos != -1) {//配對點唯一
                    vis[i] = vis[mp[i][pos]] = 1;//將這兩個配對
                    res += 2;
                }
            }
        } while(tmp != res);//沒有新的配對了

		if(res == (n << 1)) puts("Renko");
		else puts("Merry");
	}

    return 0;
}

D P8347 「Wdoi-6」另一側的月

我們有一個非常簡單的結論, 當出現這種情況時, 顯然先手必勝:

Hifuu 可以取掉 \(1\) , 之後 Luna 怎麼取也會剩下一個單獨的節點.

我們可以發現 \(4\) 的度數為 \(2\) , \(7\) 的度數為 \(1\) .
於是我們發現 如果一個節點 \(u\) , 和它的兒子 \(v\) 滿足 \(in_u = 2, in_v = 1\) 則說明先手必勝.

再考慮擴廣: 如果一個節點 \(u\) , 和它的兒子 \(v\) 滿足 \(in_u = 2k, in_v = 1\) 則說明先手必勝.
想一下這麼一種情況:

如果 Hifuu 先手取 \(-1\) , 然後指定 \(0\) 這個連通塊:
顯然任何一個人都不會取掉 \(0\) (這樣直接就輸掉了), 於是他們只能交替選 \(1\) (Luna) \(2\) (Hifuu), Luna 最後只有 \(0, 3\) 可選, 但她無論選什麼都會輸, 所以可以簡單證明結論正確.


而如果你這麼寫的話會掛在 subtask 3 , 為什麼呢?
我們知道, 在這個子任務中他給定樹是完全二叉樹, 並且節點數等於 \(2^k - 1\) , 其實就是一個滿二叉樹最後一層少一個節點, 大概長這樣:

而對於這種情況, 顯然選擇 \(0\) 這個節點, 再選擇 \(2-5\) 這個連通塊, 則先手 Hifuu 必勝.

程式碼的話用 dfs 找 節點 \(u\) , 和它的兒子 \(v\) 滿足 \(in_u = 2, in_v = 1\) 即可.
時間複雜度: \(O(n)\)

#include <bits/stdc++.h>
using namespace std;

inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

const int N = 1e6 + 10;
int t, n, u, v, in[N];
vector<int> mp[N];
bool res;

inline void dfs(int u, int fa) {
	for(auto v : mp[u]) {
		if(v == fa) continue;
		if(!(mp[u].size() & 1) && mp[v].size() == 1) res = 1;
		dfs(v, u);
	}
}

int main() {
	t = read();
	while(t--) {
		n = read(); res = 0;

		for(int i = 1; i <= n; ++i) { in[i] = 0; mp[i].clear(); }

		for(int i = 1; i < n; ++i) {
			u = read(); v = read();
			mp[u].push_back(v);
			mp[v].push_back(u);
		}

		dfs(1, 0);
		if(res || !((n + 1) & n)) puts("Hifuu");//或者滿二叉樹在最後一層少一個節點也是先手勝
		else puts("Luna");

		end: ;
	}

    return 0;
}

E P8348 「Wdoi-6」未知之花魅知之旅

這應該是一道結論題, 可惜我不會, 本來能拿 30 pts 的, 晚了 2s 交題. /ll

30 pts 爆搜卡常程式碼:
時間複雜度: \(O(3^n)\)

#include <bits/stdc++.h>
using namespace std;

inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

int t, a1, a2, x, y, k, l1, l2;
bool res, vis[1500][1500];

int main() {
	t = read();
	while(t--) {
		a1 = read(); a2 = read(); x = read(); y = read(); k = read();
        memset(vis, 0, sizeof(vis));
		res = 0;
        
        queue<pair<int, int>> q;
        if(a1 >= k && a2 >= k) q.push({a1, a2});

        while(q.size()) {
            l1 = q.front().first; l2 = q.front().second; q.pop();
            if(l1 >= 994 || l2 >= 994) continue;
            vis[l1][l2] = 1;

            if(l1 == x && l2 == y) { res = 1; break; }

	        if(l1 + l2 >= k && !vis[l2][l1 + l2]) { q.push({l2, l1 + l2}); }
	        if(l1 - l2 >= k && !vis[l2][l1 - l2]) { q.push({l2, l1 - l2}); }
	        if(l2 - l1 >= k && !vis[l2][l2 - l1]) { q.push({l2, l2 - l1}); }
        }

		if(res) puts("yes");
		else puts("no");
	}

    return 0;
}