部分题解
A. Déjà Vu
题意: 给出一个字符串,你可以选择一个位置插入一个字符a,能否找到一个位置插入后使得插入字符a之后新的字符串不是回文串?
做法::乱搞,这里采取的办法是分别尝试在原字符串的最前面,原字符串第一个字符的后面,以及原字符串的最后面插入,如果有一种能成功,便输出,否则,输出NO。(其实后面想了以下应该只用试最前面和最后面即可,但没有确切的证明。)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
char s[N];
string ans;
int f, n;
int main()
{
ios::sync_with_stdio(false);
int T;
cin >> T;
while(T--)
{
cin >> s + 1;
f = 0;
int p = 1, ao = 1;
n = strlen(s + 1);
ans = "a" + string(s + 1);
for(int i = 0; i < ans.size(); i++)
{
if(ans[i] != ans[n - i]) p = 0;
}
if(!p)
{
cout << "YES" << endl << ans << endl;
continue;
}
ans = s[1];
ans += "a" + string(s + 2);
p = 1;
for(int i = 0; i < ans.size(); i++)
{
if(ans[i] != ans[n - i]) p = 0;
}
if(!p)
{
cout << "YES" << endl << ans << endl;
continue;
}
ans = string(s + 1) + "a";
for(int i = 0; i < ans.size(); i++)
{
if(ans[i] != ans[n - i]) p = 0;
}
if(!p)
{
cout << "YES" << endl << ans << endl;
continue;
}
cout << "NO" << endl;
}
return 0;
}
B. Flip the Bits
题意: 定义了一个操作,操作规则如下:选择一个满足其中0和1的数量相等的前缀,将该前缀内的所有01翻转(0变1,1变0)。现给出一个字符串
a
a
a,
b
b
b,问能否通过有限次的操作将字符串
a
a
a变为
b
b
b。
做法: 设字符串长度为
n
n
n,记包含前
i
i
i个字符的前缀为
p
r
e
i
pre_i
prei,那么,想要翻转第
j
j
j位的字符,就只能通过对
p
r
e
k
,
k
≥
j
pre_k,k\ge j
prek,k≥j来完成翻转操作。第
n
n
n位字符如果需要翻转只能通过对
p
r
e
n
pre_n
pren操作来完成,第
n
−
1
n-1
n−1位要翻转只能通过对
p
r
e
n
pre_n
pren或者
p
r
e
n
−
1
pre_{n-1}
pren−1操作来完成,依次类推。因此,我们倒着枚举即可。遇到
a
i
≠
b
i
a_i\neq b_i
ai=bi的情况就尝试实施操作,如果不能操作,就输出NO,如果没有出现这种情况,那么就是YES。注意实施翻转后,第
i
i
i位的字符不一定是原来的字符,因此我们需要知道在枚举到第
i
i
i位时,
a
i
a_i
ai究竟是0还是1。翻转操作有个特性,即翻转奇数次和翻转1次效果一样,翻转偶数次和不翻转效果一样。因此,我们只需要记录枚举到第
i
i
i位时,翻转次数的奇偶性即可。如果是奇数且
a
i
=
b
i
a_i=b_i
ai=bi,或者是偶数且
a
i
≠
b
i
a_i \neq b_i
ai=bi,那么第
i
i
i位需要翻转,否则不需要。遇到某一位需要翻转并尝试翻转的时候注意实施翻转的条件(该前缀内0和1的数量必须相同)即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
char a[N], b[N];
int n, cnt[N][2], f, ok;
int main()
{
ios::sync_with_stdio(false);
int T;
cin >> T;
while(T--)
{
cin >> n >> a + 1 >> b + 1;
for(int i = 1; i <= n; i++)
{
cnt[i][1] = cnt[i - 1][1];
cnt[i][0] = cnt[i - 1][0];
cnt[i][a[i] - '0']++;
}
ok = 1, f = 0;
for(int i = n; i >= 1; i--)
{
if(f and a[i] == b[i])
{
if(cnt[i][0] != cnt[i][1])
{
ok = 0;
break;
}
f = !f;
}
else if(!f and a[i] != b[i])
{
if(cnt[i][0] != cnt[i][1])
{
ok = 0;
break;
}
f = !f;
}
}
if(ok) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
C. Balance the Bits
题意: 给出一个01字符串
s
s
s,要求构造两个合法的括号序列
a
,
b
a,b
a,b使得,
s
i
=
0
s_i=0
si=0时,
a
i
≠
b
i
a_i \neq b_i
ai=bi,
s
i
=
1
s_i=1
si=1时,
a
i
=
b
i
a_i=b_i
ai=bi。
做法: 我们不妨将左括号看成1,右括号看成-1,一个括号序列便变成了一个数字序列,我们考察它的前缀和。记1的个数为
s
u
m
sum
sum,由于
a
,
b
a,b
a,b均是合法括号序列,可以让前一半的1对应左括号,后一半的1对应右括号,这样1的位置上的对
a
,
b
a,b
a,b的前缀和的总贡献就是0。接下来我们来处理0的位置。要构造的
a
,
b
a,b
a,b是合法的括号序列,因此我们可以知道到第
n
n
n位的前缀和
s
u
m
a
,
i
=
s
u
m
b
i
=
0
sum_{a,i}=sum_{b_i}=0
suma,i=sumbi=0,而
s
i
=
0
s_i=0
si=0的位置上,
a
,
b
a,b
a,b的其中一个的在该位的前缀和会+1,而另一个会-1,这样就产生了差距,我们必须要在后面弥补上这个差距否则最后它们的前缀和不会相同,显然,在遇到下一个0时,我们将左括号分配给另一个字符串即可(上一次给了
a
a
a左括号,这次就给
b
b
b),也即,遇到0交替给
a
,
b
a,b
a,b左括号即可满足前缀和的要求。
但是前缀和为0只是个必要条件而非充要条件,因此,最后构造出来的
a
,
b
a,b
a,b进行检查,如果均为合法序列,输出YES,否则输出NO。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n, f, cnt, sum;
char a[N], b[N], s[N];
bool check(char* s)
{
stack<char> S;
for(int i = 1; i <= n; i++)
{
if(s[i] == '(') S.push(s[i]);
else if(S.empty()) return false;
else S.pop();
}
return S.empty();
}
int main()
{
ios::sync_with_stdio(false);
int T;
cin >> T;
while(T--)
{
cin >> n >> s + 1;
f = 1;
sum = cnt = 0;
for(int i = 1; i <= n; i++) sum += s[i] - '0';
for(int i = 1; i <= n; i++)
{
if(s[i] == '1')
{
if(cnt < sum / 2)
{
a[i] = b[i] = '(';
cnt++;
}
else
{
a[i] = b[i] = ')';
}
}
else if(s[i] == '0')
{
if(f)
{
a[i] = '(';
b[i] = ')';
}
else
{
a[i] = ')';
b[i] = '(';
}
f = !f;
}
}
a[n + 1] = b[n + 1] = 0;
if(check(a) and check(b)) cout << "YES" << endl << a + 1 << endl << b + 1 << endl;
else cout << "NO" << endl;
}
return 0;
}
D. 3-Coloring
题意: 交互题。给出三种颜色1,2,3。每次禁用一种颜色,要求将一个n*n的棋盘染色,并且相邻的格子颜色不同。
做法(官方题解): 如果不带禁用颜色,那么用两种颜色就可以了。直接染成黑白棋盘即可。
现在带了禁手,其实我们的策略大致相同,我们假定颜色1染黑色,2染白色,并将原来的棋盘按黑白染色的规则划分成应该染黑色的格子和应该染白色的格子。如果禁用的颜色是黑色1,那么是可以正常染的,那么就去给应该染白色的格子染颜色2,如果白色格子染完了,那么就让颜色3替代颜色1给黑色格子染,同样,如果白色格子的颜色2被禁用,那么1可以正常给黑色格子染,黑色格子被染完就用3代替2给白色染,如果3被禁用,那不影响。
注意: 3作为替代颜色不能乱替代,否则有可能出现连续用3替代的情况导致相邻格子具有相同的颜色3。一种典型错解:从第一行到枚举到最后一行,从第一列枚举到最后一列,该染黑色的时候被禁手就染3,该染白色的时候被禁手就染3。官方题解的做法会保证了不会出现这样的情况。假设有这样的情况发生,假设是在染完一个白色格子后出现的3颜色相邻的情况。那么说明之前染黑色格子的时候被迫用3替代了,但是被迫替代的条件是染的时候白色格子已经染色完毕了,这就和我们染完一个白色格子才出现这样的冲突情况的假设矛盾。因此假设不成立,不会出现这样的情况,
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
int a, mp[120][120], n;
int pre;
vector<pii> w, b;
void ans(int c, int x, int y)
{
cout << c << ' ' << x << ' ' << y << endl;
cout.flush();
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
if((i + j) % 2 == 0) b.push_back(make_pair(i, j));
else w.push_back(make_pair(i, j));
}
}
for(int i = 1; i <= n * n; i++)
{
cin >> a;
if(a == 1)
{
if(w.size())
{
pii p = w.back();
ans(2, p.first, p.second);
w.pop_back();
}
else
{
pii p = b.back();
ans(3, p.first, p.second);
b.pop_back();
}
}
else if(a == 2)
{
if(b.size())
{
pii p = b.back();
ans(1, p.first, p.second);
b.pop_back();
}
else
{
pii p = w.back();
ans(3, p.first, p.second);
w.pop_back();
}
}
else
{
if(w.size())
{
pii p = w.back();
ans(2, p.first, p.second);
w.pop_back();
}
else
{
pii p = b.back();
ans(1, p.first, p.second);
b.pop_back();
}
}
}
return 0;
}