当前位置 : 主页 > 编程语言 > 其它开发 >

2022 Usaco US Open - 银组题解

来源:互联网 收集:自由互联 发布时间:2022-05-30
T1 题意简述 有 \(n\) 只奶牛,第 \(i\) 只奶牛希望拜访第 \(a_i\) 只奶牛。 给定一个 \(1\) . . . \(N\) 的排列 \((p_1, p_2, ..., p_n)\) ,对于从 \(1\) 到 \(n\) 的每一个 \(i\) : 如果奶牛 \(a_{p_i}\) 已经
T1

题意简述

  • \(n\) 只奶牛,第 \(i\) 只奶牛希望拜访第 \(a_i\) 只奶牛。
  • 给定一个 \(1\) . . . \(N\) 的排列 \((p_1, p_2, ..., p_n)\),对于从 \(1\)\(n\) 的每一个 \(i\)
    • 如果奶牛 \(a_{p_i}\) 已经离开了她的农场,那么奶牛 \(p_i\) 仍然留在她的农场。
    • 否则,奶牛 \(p_i\) 就会离开她的农场去拜访第 \(a_{p_i}\) 只奶牛,并产生 \(v_{p_i}\) 次快乐的哞叫。
  • 给出序列 \(a_n\)\(v_n\),对于所有可能的排列 \(p\),计算可能得到的快乐哞叫的次数的最大值。
  • \(2 \leq n \leq 10^5\)\(0 \leq v_i \leq 10^9\)

解题思路

十分有趣的图论题。

我们建立一个 \(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

题意简述

  • 给定两个字符串 \(s\)\(t\),仅由小写字母 'a' 到 'r' 组成。
  • \(q\) 次询问,每次给出一个小写字母 'a' 到 'r' 的子集,你需要判断 \(s\)\(t\) 在删去了不包含在子集内的字符后是否相等,相等输出 'Y',不相等输出 'N'。每次询问独立。
  • \(\mid s \mid, \mid t \mid \leq 10^5\)\(1 \leq q \leq 10^5\)

解题思路

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

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

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

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

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

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

  • 如果 \(k = 1\),比如 'a'、'b',我们直接统计这个字符在 \(s\)\(t\) 中出现的次数即可。
  • 如果 \(k = 2\),比如 'ab'、'ac',我们用暴力方法判断是否相等。
  • 如果 \(k \geq 3\),比如 'abc'、'defg'、'acdfh',我们发现当且仅当询问子集的所有子集的答案都是 'Y' 的时候询问子集的答案才是 'Y'。为了简化,我们只用判断询问子集的所有元素个数等于 \(k-1\) 的子集即可。

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

  • 我们观察到两个字符串中 'a'、'b'、'c'、'd' 的数量都相同,所以:
    \(dp[\)'a'\(] = dp[\)'b'\(] = dp[\)'c'\(] = dp[\)'d'\(] = true\)
  • 对于子集 'ab'、'ac'、'ad'、'bc'、'bd'、'cd',我们暴力判断,得出:
    \(dp[\)'ab'\(] = dp[\)'ad'\(] = dp[\)'bd'\(] = dp[\)'cd'\(] = true\)\(dp[\)'ac'\(] = dp[\)'bc'\(] = true\)
  • 对于子集 'abc',我们发现 \(dp[\)'ab'\(] = true\)\(dp[\)'ac'\(] = dp[\)'bc'\(] = false\),不满足它所有子集答案都是 'Y',所以:
    \(dp[\)'abc'\(] = false\)
  • 而对于子集 'abd',我们发现 \(dp[\)'ab'\(] = dp[\)'ad'\(] = dp[\)'bd'\(] = true\),所以 \(dp[\)'abd'\(] = true\)
  • 子集 'acd'、'bcd'、'abcd' 同理。

具体如何实现呢?我们考虑使用二进制的思想将所有 '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

题意简述

  • 给定一个仅包含字符 'C','O' 和 'W' 的字符串 \(s\),有两种操作:
    1.选择两个相邻的字符并将其删除。
    2.选择一个字母,将其替换为另外两个字母的任一排列。
  • \(q\) 次询问,每次询问给定 \(l, r\),问能否通过上述两种操作将 \(s\) 的子串 \([l, r]\) 变为单个字母 'C',能则输出 'Y',不能则输出 'N'。
  • \(1 \leq l \leq r \leq \mid s \mid \leq 2 \cdot 10^5\)\(1 \leq q \leq 2 \cdot 10^5\)

解题思路

比较简单的思维题。

首先思考在变化的字符串 \(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;
}
上一篇:python实现Mysql数据库批量新增数据
下一篇:没有了
网友评论