AtCoder Beginner Contest 391


A - Lucky Direction

点击查看代码
void solve() {
    std::vector<std::string> a{"N","S", "W", "E", "NE", "SW", "NW", "SE"};
    std::string s;
    std::cin >> s;
    int p = std::find(a.begin(), a.end(), s) - a.begin();
    std::cout << a[p ^ 1] << "\n";
}

B - Seek Grid

点击查看代码
void solve() {
	int n, m;
	std::cin >> n >> m;
	std::vector<std::string> s(n), t(m);
	for (int i = 0; i < n; ++ i) {
		std::cin >> s[i];
	}   

	for (int i = 0; i < m; ++ i) {
		std::cin >> t[i];
	}

	auto check = [&](int a, int b) -> bool {
		for (int i = 0; i < m; ++ i) {
			for (int j = 0; j < m; ++ j) {
				if (s[a + i][b + j] != t[i][j]) {
					return false;
				}
			}
		}

		return true;
	};

	for (int i = 0; i + m - 1 < n; ++ i) {
		for (int j = 0; j + m - 1 < n; ++ j) {
			if (check(i, j)) {
				std::cout << i + 1 << " " << j + 1 << "\n";
				return;
			}
		}
	}
}

C - Pigeonhole Query

题意:\(n\)个鸽子开始各在第\(i\)个笼子里,每次会移动一只鸽子到另一个笼子,或者问你有多少个笼子有两个以上的鸽子。

记录每个笼子的鸽子数和每个鸽子在哪个笼子即可。

点击查看代码
void solve() {
    int n, q;
    std::cin >> n >> q;
    std::vector<int> cnt(n, 1), p(n);
    std::iota(p.begin(), p.end(), 0);
    int ans = 0;
    while (q -- ) {
    	int op;
    	std::cin >> op;
    	if (op == 1) {
    		int x, y;
    		std::cin >> x >> y;
    		-- x, -- y;
    		if (cnt[p[x]] == 2) {
    			-- ans;
    		}

    		-- cnt[p[x]];

    		if (cnt[y] == 1){
    			++ ans;
    		}

    		++ cnt[y];

    		p[x] = y;
    	} else {
    		std::cout << ans << "\n";
    	}
    }
}

D - Gravity

题意:\(n\)个方块向下落,每一时刻,如果最底层填满了方块,就消除这些方块。然后如果有方块不是在最底层并且下面没有方块就会下落。\(q\)次询问问你第\(t + 0.5\)时刻第\(i\)个方块有没有被消除。

模拟消除即可。每次模拟一行的消除,每次找每行高度最低的方块里高度最高的。最多模拟\(n\)次。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> ans(n, 2e9);
    std::vector<std::vector<std::pair<int, int> > > a(m);
    for (int i = 0; i < n; ++ i) {
    	int x, y;
    	std::cin >> x >> y;
    	-- x;
    	a[x].push_back({y, i});
    }

    for (int i = 0; i < m; ++ i) {
    	std::sort(a[i].begin(), a[i].end(), std::greater<>());
    }

    int time = 0;
    auto move = [&]() -> bool {
    	int max = 0;
    	for (int i = 0; i < m; ++ i) {
    		if (a[i].empty()) {
    			return false;
    		}

    		max = std::max({max, a[i].back().first - time, 1});
    	}

    	int t = max - 1;
    	for (int i = 0; i < m; ++ i) {
    		ans[a[i].back().second] = time + t;
    		a[i].pop_back();
    	}

    	time += t;
    	return true;
    };

    while (move());

    int q;
    std::cin >> q;
    while (q -- ) {
    	int t, x;
    	std::cin >> t >> x;
    	-- x;
    	if (ans[x] >= t) {
    		std::cout << "Yes\n";
    	} else {
    		std::cout << "No\n";
    	}
    }
}

E - Hierarchical Majority Vote

题意:一个\(3^n\)长度的字符串\(s\)每次会缩短三倍变成\(s'\),如果\(s_{3i}, s_{3i + 1}, s_{3i + 2}\)里有两个以上的1,\(s'_{i}\)就是1,否则是0。最终会变成一个0或者1,现在要你修改最少的字符使得最终结果改变。

可以用递归求出最终结果,每次分成三份一直递归到长度为1就行。然后得到我们需要变成的结果v(v=0或者v=1),最后依然可以递归求,求出每个部分变成v需要几次操作,取最小的两个部分即可。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    n = std::pow(3, n);
    std::string s;
    std::cin >> s;

    auto dfs = [&](auto self, int l, int r) -> int {
    	if (l == r) {
    		return s[l - 1] - '0';
    	}

    	int len = (r - l + 1) / 3;
    	return self(self, l, l + len - 1) + self(self, l + len, l + 2 * len - 1) + self(self, l + 2 * len, r) >= 2;
    };


    int v = dfs(dfs, 1, n);

    auto dfs1 = [&](auto self, int l, int r) -> int {
    	if (l == r) {
    		return s[l - 1] - '0' == v;
    	}

    	int len = (r - l + 1) / 3;
    	int a = self(self, l, l + len - 1);
    	int b = self(self, l + len, l + 2 * len - 1);
    	int c = self(self, l + 2 * len, r);
    	std::vector<int> d{a, b, c};
    	std::sort(d.begin(), d.end());
    	return d[0] + d[1];
    };

    // std::cout << v << "\n";
    std::cout << dfs1(dfs1, 1, n) << "\n";
}

F - K-th Largest Triplet

题意:给你三个数组,求所有\(i, j, k\)中,\(a_i b_j + b_j c_k + a_i c_k\)中第\(m\)大的。

三个数组从大到小排序后用优先级队列模拟即可,一开始把\(\{val(1, 1, 1), 1, 1, 1\}\)入队,分别代表\(i,j, k\)得到的值以及\(i, j, k\)的值。注意每个\(i, j, k\)可能被多个状态转移,用set记录一下。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<i64> a(n), b(n), c(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    for (int i = 0; i < n; ++ i) {
    	std::cin >> b[i];
    }

    for (int i = 0; i < n; ++ i) {
    	std::cin >> c[i];
    }

    std::sort(a.begin(), a.end(), std::greater<i64>());
    std::sort(b.begin(), b.end(), std::greater<i64>());
    std::sort(c.begin(), c.end(), std::greater<i64>());

    auto get = [&](int i, int j, int k) -> i64 {
    	return a[i] * b[j] + b[j] * c[k] + a[i] * c[k];
    };

    std::priority_queue<std::array<i64, 4>> heap;
    heap.push({get(0, 0, 0), 0, 0, 0});
    std::set<std::array<int, 3> > s;
    s.insert({0, 0, 0});
    while ( -- m) {
    	auto [val, i, j, k] = heap.top(); heap.pop();
    	if (i + 1 < n && !s.count({i + 1, j, k})) {
    		s.insert({i + 1, j, k});
			heap.push({get(i + 1, j, k), i + 1, j, k});
    	}

    	if (j + 1 < n && !s.count({i, j + 1, k})) {
    		s.insert({i, j + 1, k});
    		heap.push({get(i, j + 1, k), i, j + 1, k});
    	}

    	if (k + 1 < n && !s.count({i, j, k + 1})) {
    		s.insert({i, j, k + 1});
    		heap.push({get(i, j, k + 1), i, j, k + 1});
    	}
    }

    std::cout << heap.top()[0] << "\n";
}

G - Many LCS

题意:给你一个长度为\(n\)的字符串\(s\),求有多少个长度为\(m\)的字符串\(t\)\(s\)的最长公共子序列长度为\(k\),求出0到n的每个k。

听说是经典dp,但我是第一次做。
回忆求最长公共子序列的dp方程,发现如果\(f[i][j] > f[i][j - 1]\),则表明\(s_i和t_j\)匹配了。有多少个这样的大于,就代表最长公共子序列长度是多少,于是我们可以按匹配到\(t\)的第几个来\(dp\)
\(F(f_{1,j}, f_{2,j}, ... , f_{n-1,j}, f_{n, j})\)为匹配到\(t_j\)时,最长上升子序列的dp状态为\(f_{1,j}, f_{2,j}, ... , f_{n-1,j}, f_{n, j}\)的方案数。但这样的dp状态非常多,我们不可能存的下。这时就要利用我们之前观察到的性质,我们可以记一个类似于差分数组的东西\(f'\),如果\(f_{i, j} > f_{i - 1, j}\)\(f'_{i, j} = 1\)否则\(f'_{i, j} = 0\), 发现它是一个01串,我们可以利用状压来表示!
dp状态定义只要满足对于每种情况都恰好有一个状态可以表示它,就是合理的。那么我们这样定义的dp状态,是正确的。
于是记\(F_{j, s}\)表示匹配到\(t_j\)\(f'\)状压表示为\(s\)的方案数。那么我们可以枚举\(t_j\),对每个\(s\)和每个\(t_j\)进行正常的最长公共子序列的状态转移即可。先把\(s\)转换为\(dp\)数组,然后进行\(dp\),最后得到的结果转换回状压表示就行。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::string str;
    std::cin >> str;

    auto s_to_dp = [&](int x) -> std::vector<int> {
        std::vector<int> f(n + 1);
        for (int i = 0; i < n; ++ i) {
            if (x >> i & 1) {
                f[i + 1] = f[i] + 1;
            } else {
                f[i + 1] = f[i];
            }
        }

        return f;
    };

    auto dp_to_s = [&](std::vector<int> f) -> int {
        int res = 0;
        for (int i = 0; i < n; ++ i) {
            if (f[i + 1] > f[i]) {
                res += 1 << i;
            }
        }

        return res;
    };

    std::vector<Z> f(1 << n);
    f[0] = 1;
    for (int j = 1; j <= m; ++ j) {
        std::vector<Z> g(1 << n);
        for (int s = 0; s < 1 << n; ++ s) {
            if (f[s] == 0) {
                continue;
            }

            auto tmp = s_to_dp(s); //dp[1~n][j - 1]
            for (char c = 'a'; c <= 'z'; ++ c) {
                std::vector<int> dp(n + 1); //dp[1~n][j]
                for (int i = 1; i <= n; ++ i) { 
                    //dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
                    dp[i] = std::max(dp[i - 1], tmp[i]);
                    //dp[i][j] = dp[i - 1][j - 1] + 1
                    if (c == str[i - 1]) {
                        dp[i] = std::max(dp[i], tmp[i - 1] + 1);
                    }
                }

                int ns = dp_to_s(dp);
                g[ns] += f[s];
            }
        }

        f = g;
    }

    std::vector<Z> ans(n + 1);
    for (int i = 0; i < 1 << n; ++ i) {
        ans[__builtin_popcount(i)] += f[i];
    }

    for (int i = 0; i <= n; ++ i) {
        std::cout << ans[i] << " \n"[i == n];
    }
}
posted @ 2025-02-01 21:45  maburb  阅读(62)  评论(0)    收藏  举报