T1:词典

题意:

给定 n 个长度为 m 的字符串 w1,w2,,wn
对于每个 i=1,2,,n 询问是否存在 w1,w2,,wn 使得对于每个 j=1,2,,nwj 都可以由 wj 交换字符得到,且对于 ji 都有 wi 的字典序小于 wj

数据范围:

1n3000, 1m3000, 所有字符串均由小写字母构成

算法分析

做法1:

对于每个 i, 如果存在方案的话必然可以让 wi 字典序最小,而其他的 jiwj 字典序最大。也就是让 wi 的字符从小到大排序得到 wi,让 wj 的字符从大到小排序得到 wj

直接模拟这个过程的时间复杂度为 O(n2m),期望得分 80 分。
但实际上可以拿 100 分。

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
freopen("dict.in", "r", stdin);
freopen("dict.out", "w", stdout);
int n, m;
cin >> n >> m;
vector<string> w(n);
rep(i, n) cin >> w[i];
vector<string> mn(n);
rep(i, n) {
mn[i] = w[i];
sort(mn[i].begin(), mn[i].end());
}
vector<string> mx(n);
rep(i, n) {
mx[i] = w[i];
sort(mx[i].rbegin(), mx[i].rend());
}
string ans;
rep(i, n) {
bool ok = true;
rep(j, n) if (j != i) {
if (mn[i] > mx[j]) {
ok = false;
break;
}
}
ans += ok ? '1' : '0';
}
cout << ans << '\n';
return 0;
}

做法2:

实际上我们发现没有必要模拟上述过程,因为得到的 wi 以及 wj 一定是一段一段的,最多 σ=26 段,使用桶存下每个字符的出现次数,之后一段一段比较即可在 O(nm+σn2) 的复杂度内解决。期望得到 80100 分。

做法3:

不妨再深入一步,设 fi 表示 wi 中出现的最小字母, gi 表示 wi 中出现的最大字母。
如果 fi<gi,那么显然 wi 的字典序小于 wj,只需要将 fi 作为 wi 的第一个字母,gj 作为 wj 的第一个字母即可。
如果 fi>gj,那么显然 wi 的字典序大于 wj
剩下最后一种情况 fi=gj,想要 wi 的字典序最小,需要将 fi 放在最前面,此时越往后 wi 的字母越大。想要 wj 的字典序最大,需要将 gj 放在最前面,此时越往后 wj 的字母越小。因此此时必然有 wi 的字典序大于 wj
综上,如果 i 可能,当且仅当 fi<minjigj。因此可以 O(n) 完成这个过程,总的时间复杂度为 O(nm+n2)
期望得分:100分。

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
freopen("dict.in", "r", stdin);
freopen("dict.out", "w", stdout);
int n, m;
cin >> n >> m;
vector<int> f(n, 26), g(n, -1);
rep(i, n) {
string w;
cin >> w;
rep(j, m) {
f[i] = min(f[i], w[j]-'a');
g[i] = max(g[i], w[j]-'a');
}
}
string ans;
rep(i, n) {
bool ok = true;
rep(j, n) if (j != i) {
if (f[i] >= g[j]) {
ok = false;
break;
}
}
ans += ok ? '1' : '0';
}
cout << ans << '\n';
return 0;
}

T2:三值逻辑

题意:

有三种变量:T,F,U 以及一种运算 ¬。且满足运算法则:¬T=F¬F=T¬U=U
已知现在有 n 个变量 x1,x2,,xn,有 m 条赋值语句,每条语句为以下三种之一:

  1. xiv,其中 vT,F,U 三者之一
  2. xixj
  3. xi¬xj

在执行 m 条语句之前,会先对变量赋初始值。求所有使得执行 m 条语句之后,每个变量初始值与最终值相等的初始赋值中,U 变量的最小值。数据保证有解。

数据范围:

1t61n,m105。多组数据,t 表示数据组数。

算法分析

做法1:

直接 O(3n) 枚举每种可能的初始值,之后再使用 O(m) 的时间判断,可以获得 20 分。

做法2:(v 可能取值 TFU
此时可以发现,一个变量需要赋值为 U 当且仅当 m 条语句结束之后变量的值为 U,因此我们可以记录每个变量的状态:未赋值,赋值为 T,赋值为 F,赋值为 U。这个可以 O(m) 得到。
期望得分:20 分。

做法3:(v 可能取值为 U+
此时与上一种情况略有区别,一个变量需要赋初值为 U 除了最终值为 U 之外,还多了某个变量最终值为 U,而 xi 的最终值为这个变量。例如 m=2

  1. x1x2
  2. x2U

此时由于 x2 的值为 U,导致 x1 的值也必须为 U
这就启发我们要记录 xi 的最终值是哪里来的。而这是好记录的,初始时每个 xi 的来源都是本身,当执行 xixj 时,记录 xi 的来源为 xj 的来源即可,而对于 xiU,则直接标记来源为 U,如果某个值最终来源为 U,则说明其最终值为 U
最终可以使用并查集(如果 xi 的来源是 xj,说明 xi 的最终值等于 xj 的初始值,进一步 xi 的初始值等于 xj 的初始值,因此将 xixj 放入一个集合),如果集合内有一个变量初始值为 U,则集合内所有变量的初始值必须为 U
时间复杂度为 O(n+m)
期望得分:20 分。

做法4:

最终我们可以沿用上述思路,记录每个变量的来源(注意 xi¬xji 可能等于 j)以及是否被取反(也就是逻辑非运算)。
之后我们可以使用并查集维护命题,具体来说对于每个 xi 建立两个点,pi 表示命题 xi=Tpi 表命题 xi=F。显然正常情况 pipi 是不会在同一个集合中的,而一旦它们在同一个集合中,就只能说明 xi=¬xi,即 xi=U
之后对于每个 i 考虑 xi 的来源

  1. U,说明 xi 的最终值为 U,将 pipi 并入同一集合
  2. T 或者 F,由于题目保证有解,而 xi 的最终值为 T 或者 F,说明不可能为 U,此时我们不需要做任何处理。
  3. xj,说明 xi 的值与 xj 的值一致,将 pipj 并入同一集合,将 pipj 并入同一集合。
  4. ¬xj,说明 xi 的值与 xj 的值相反,将 pipj 并入同一集合,将 pipj 并入同一集合。

最终 pipi 在同一集合的变量 i 的个数就是所求答案。
时间复杂度为 O(n+m)
期望得分:100

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
const int N = 2e5+5;
const int U = N-1;
const int T = N-2;
int val[N], p[N];
int root(int x) {
if (p[x] == x) return x;
return p[x] = root(p[x]);
}
void merge(int x, int y) {
p[root(x)] = root(y);
}
bool same(int x, int y) {
return root(x) == root(y);
}
void solve() {
int n, m;
cin >> n >> m;
rep(i, n) val[i] = i;
rep(mi, m) {
char v; int i, j;
cin >> v >> i;
if (v == '+' or v == '-') {
cin >> j;
int x = val[j];
val[i] = v == '-' ? -x : x;
}
else {
val[i] = v == 'U' ? U : T;
}
}
rep(i, n*2) p[i] = i;
rep(i, n) {
if (abs(val[i]) == U) merge(i, i+n);
else if (abs(val[i]) == T) continue;
else if (val[i] > 0) {
merge(val[i], i);
merge(val[i]+n, i+n);
}
else if (val[i] < 0) {
merge(-val[i], i+n);
merge(-val[i]+n, i);
}
}
int ans = 0;
rep(i, n) if (same(i, i+n)) ans++;
cout << ans << '\n';
}
int main() {
freopen("tribool.in", "r", stdin);
freopen("tribool.out", "w", stdout);
int c, t;
cin >> c >> t;
while (t--) solve();
return 0;
}

T3:双序列拓展

题意:

B={b1,b2,,bn}A={a1,a2,,am} 的扩展,当且仅当存在 L={l1,l2,,lm} 使得 B 是将 A 中的 ai 替换为 liai 得到的。
给出长度为 n 的序列 X 以及长度为 m 的序列 Y,问是否存在 X 的拓展 F,以及 Y 的拓展 G,满足 FG 的长度均为 10100,且对于任意的 1i,j10100 都有 (figi)(fjgj)>0
q 组数据,每组数据都是在原序列的基础上修改若干个元素得到的。

数据范围:

  • 1n,m5×105
  • 1q60
  • 1ai,bi109
  • 修改的数量之和不超过 5×105