其他分享
首页 > 其他分享> > 2022 Usaco US Open - 银组题解

2022 Usaco US Open - 银组题解

作者:互联网

T1

题意简述

解题思路

十分有趣的图论题。

我们建立一个 \(n\) 条边 \(n\) 个点的有向图,第 \(i\) 条边从 \(i\) 连向 \(a_i\)。注意到图不一定连通。

首先考虑对于 \(a_i \neq a_j\) 的部分分如何解决。这种情况下,每个连通块都形成一个简简单单的环。

容易发现最优情况下,应当是先由一头起始奶牛 \(i\) 拜访 \(a_i\),然后 \(a_i\) 拜访 \(a_{a_i}\),然后 \(a_{a_i}\) 拜访 \(a_{a_{a_i}}\),以此类推。

图片 1

所以这种情况下,每个连通块的答案就是 \(v_i\) 的和减去这个连通块内 \(v_i\) 的最小值。

那么对于非特殊情况,我们如何解决呢?先随意画出一张可能的有向图观察一下吧。

图片 2

我们发现每一个连通块内有且仅有一个环,那么我们就可以把点分成两类:

图片 3

问题也就迎刃而解了。我们对于每个连通块,统计所有 \(v_i\) 的和,再减去环上的点的 \(v_i\) 的最小值,就是答案。

代码

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

inline int read() {
    int x = 0; char ch = getchar();
    while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = (x<<3) + (x<<1) + ch - '0', ch = getchar();
    return x;
}

const int L = 1e5 + 5;
int n, ans, minn, a[L], v[L], f[L], in[L];
vector<int> vec, son[L];
bool vis[L];

void get_point(int x) { // 获取每一个连通块中的点
    vis[x] = true, vec.push_back(x);
    for (int i = 0; i < son[x].size(); i++)
	if (!vis[son[x][i]]) get_point(son[x][i]);
    if (!vis[a[x]]) get_point(a[x]);
}

void get_circle(int x) { // dfs 找环,同时直接更新最小值
    f[x]++;
    if (f[x] == 2) minn = min(minn, v[x]);
    if (f[a[x]] != 2) get_circle(a[x]);
}

signed main() {
    n = read();
    for (int i = 1; i <= n; i++)
	a[i] = read(), v[i] = read(), son[a[i]].push_back(i), in[a[i]]++;
		
    for (int i = 1; i <= n; i++) {
	if (vis[i]) continue;
	minn = L*L;
	get_point(i);
	get_circle(i);
	for (int j = 0; j < vec.size(); j++)
    	    ans += v[vec[j]];
	ans -= minn;
	vec.clear();
    }
	
    printf("%lld", ans);
	
    return 0;
}

T2

题意简述

解题思路

首先思考暴力做法:对于每次询问,对两个字符串暴力地扫一遍,剔除不包含在自己内的字符,然后 \(O(n)\) 判断两个字符串是否相等。

时间复杂度达到了 \(O(nq)\),显然过不去。怎么办呢?

观察题面,注意到本题有一个不同寻常的限制:

仅由小写字母 'a' 到 'r' 组成。

'r' 是字母表里的第 \(18\) 个字母,而所有 'a' 到 'r' 的子集个数只有 \(2^{18} = 262144\),似乎......不是很大?

考虑使用状压 dp 预处理出答案,我们设询问子集的元素个数为 \(k\),分类如下:

比如,对于样例中的情况,即 $s = $ "\(\texttt{aabcd}\)",$t = $ "\(\texttt{caabd}\)":

具体如何实现呢?我们考虑使用二进制的思想将所有 'a' 到 'r' 的子集变为一个数。比如将 'a' 转化为 \(1\)、将 'b' 转化为 \(2\)、将 'c' 转化为 \(4\)、将 'abc' 转化为 \(7\)。

同时,我们预处理出元素个数等于 \(1\) 的子集与元素个数等于 \(2\) 的子集,进行特殊处理。

代码

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

inline int read() {
    int x = 0; char ch = getchar();
    while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = (x<<3) + (x<<1) + ch - '0', ch = getchar();
    return x;
}

const int L = (2<<18);
int q, x, cnt, f1[L], f2[L], tmp1[L], tmp2[L], cnt1[20], cnt2[20];
string s, t, ask;
bool dp[L];

int main() {
    cin >> s >> t;
    for (int i = 0; i < s.size(); i++)
	cnt1[s[i]-'a']++; // 预处理出 s 中每个字符出现的个数
    for (int i = 0; i < t.size(); i++)
	cnt2[t[i]-'a']++; // 预处理出 t 中每个字符出现的个数
	
    for (int i = 0; i <= 17; i++) { // 预处理出元素个数等于 1 的子集与元素个数等于 2 的子集
	f1[(1<<i)] = i+1;
	for (int j = i+1; j <= 17; j++)
    	    f1[(1<<i)+(1<<j)] = i+1, f2[(1<<i)+(1<<j)] = j+1;
    }
	
    for (int i = 1; i <= L-1; i++) {
	if (f1[i] && !f2[i]) { // 元素个数等于 1 的子集
	    if (cnt1[f1[i]-1] == cnt2[f1[i]-1])	
		dp[i] = true;
	    else dp[i] = false;
	} else if (f1[i]) { // 元素个数等于 2 的子集
	    if (cnt1[f1[i]-1] != cnt2[f1[i]-1] || cnt1[f2[i]-1] != cnt2[f2[i]-1])
		dp[i] = false;
	    else {
                // 暴力判断
		cnt = 0;
		for (int j = 0; j < s.size(); j++)
		    if (s[j]-'a' == f1[i]-1 || s[j]-'a' == f2[i]-1)
			tmp1[++cnt] = s[j]-'a';
		cnt = 0;
		for (int j = 0; j < t.size(); j++)	
		    if (t[j]-'a' == f1[i]-1 || t[j]-'a' == f2[i]-1)
			tmp2[++cnt] = t[j]-'a';
				
		dp[i] = true;
		for (int j = 1; j <= cnt; j++)
		    if (tmp1[j] != tmp2[j])
			dp[i] = false;
	    }
	} else {
	    dp[i] = true, x = i;
	    for (int j = 17; j >= 0; j--) {
		if (x < (1<<j)) continue;
		x -= (1<<j);
		if (!dp[i-(1<<j)]) dp[i] = false;
	    }
	}
    }
	
    q = read();
    while (q--) {
	cin >> ask, x = 0;
	for (int i = 0; i < ask.size(); i++)
	    x += (1<<(ask[i]-'a'));
	if (dp[x]) printf("Y");
	else printf("N");
    }
	
    return 0;
}

T3

题意简述

解题思路

比较简单的思维题。

首先思考在变化的字符串 \(s\) 中有什么是不变的。

这种题目的一个常见套路就是分析奇偶性。我们记字符 'C','O','W' 的个数为 \(c, o, w\),列出下表观察奇偶性:

\(c\) \(o\) \(w\) \(c+o\) \(o+w\) \(w+c\) \(c+o+w\)
操作 \(1\) 奇偶性不变 奇偶性不变 奇偶性不变 奇偶性不变 奇偶性不变 奇偶性不变 奇偶性不变
操作 \(2\) 奇偶性变 奇偶性变 奇偶性变 奇偶性不变 奇偶性不变 奇偶性不变 奇偶性变

我们惊奇地发现:\(c+o, o+w, w+c\) 的奇偶性自始至终都不变!

既然我们需要把字符串变成单个字符 'C',那么 \(c+o\) 就 \(w+c\) 就应该是奇数,\(o+w\) 就应该是偶数。

那么解法也呼之欲出了。我们使用前缀和维护 'c','o','w' 出现的次数,对于每一次询问,求出该子串里 \(c+o, o+w, w+c\) 的值,再判断奇偶性是否符合要求即可。

代码

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

inline int read() {
	int x = 0; char ch = getchar();
	while (!isdigit(ch)) ch = getchar();
	while (isdigit(ch)) x = (x<<3) + (x<<1) + ch - '0', ch = getchar();
	return x;
}

const int L = 2e5 + 5;
int q, l, r, c, o, w, pre[L][3];
string s;

int main() {
    cin >> s;
    for (int i = 0; i < s.size(); i++) {
	if (i) pre[i][0] = pre[i-1][0], pre[i][1] = pre[i-1][1], pre[i][2] = pre[i-1][2];
	if (s[i] == 'C') pre[i][0]++;
	else if (s[i] == 'O') pre[i][1]++;
	else pre[i][2]++;
    }
	
    q = read();
    while (q--) {
	l = read()-1, r = read()-1;
	if (l) c = pre[r][0]-pre[l-1][0], o = pre[r][1]-pre[l-1][1], w = pre[r][2]-pre[l-1][2];
	else c = pre[r][0], o = pre[r][1], w = pre[r][2];
	if (((c+o)&1) && ((c+w)&1) && !((o+w)&1)) printf("Y");
	else printf("N");
    }
	
    return 0;
}

标签:pre,银组,int,题解,奇偶性,US,leq,子集,dp
来源: https://www.cnblogs.com/SparkleZH-Blog/p/16074374.html