牛客多校4 BG解题报告
B
题意:给定一个\(n * m\)的地图,保证首列尾列无障碍物,其余位置存在墙。
从\((1, 1)\)出发,只能向上,向下,向右移动,不能向左回退。终点为\((1, m)\),且每次只能看到自己当前所处位置右侧\(k\)列的地图情况。
问是否存在一个“死胡同”位置,使得该位置可以:
1.从起点可到达。
2.不能到达终点。
3.在到达该点的途中无法判断是否能够到达终点。
解答:刚开始想复杂了,后来发现直接逐个条件判断即可:
对于1,2,分别从起点和终点dfs,记录可以到达的点即可
对于3,考虑某个位置的下一步,是否会到达某个位置,该位置无法到达终点,但是可以到达的最右侧位置大于k(即超过视线范围)。考虑开一个数组记录每个位置向右能到达的最右位置。
接下来只需求出这个数组。一开始想沿用前两个条件的思路,搞个类似记忆化搜索的东西,对于每个位置,直接对上下右三个位置的记录值取max就可以了。但是后来写挂了,用dfs似乎无法控制访问的顺序。于是在我思考如何保证访问顺序时,突然发现直接从右往左遍历一遍就可以了。于是写了个DP一样的东西顺利解决。
但是WA了一发,原因是没有注意一个位置的答案与上下位置都有关,应该从上往下和从下往上分别遍历一次。一开始只写了一半。
另外这题为什么写个n*m的范围逼我开vector不能用静态二维数组(
AC代码:
#include <bits/stdc++.h>
#define MAX 1000005
using namespace std;
int t;
int n, m, k;
char a[MAX];
vector<int> vex[MAX];
vector<int> vis1[MAX];
vector<int> r[MAX];
vector<int> vis2[MAX];
int l, up, down;
void dfs1(int x, int y){
if(x == 0 || x == n + 1 || y == 0 || y == m + 1) return ;
if(vex[x][y] == 1) return ;
if(vis1[x][y]) return ;
vis1[x][y] = 1;
dfs1(x, y + 1);
dfs1(x + 1, y);
dfs1(x - 1, y);
}
void dfs2(int x, int y){
if(x == 0 || x == n + 1 || y == 0 || y == m + 1) return ;
if(vex[x][y] == 1) return ;
if(vis2[x][y]) return ;
vis2[x][y] = 1;
dfs2(x, y - 1);
dfs2(x + 1, y);
dfs2(x - 1, y);
}
int check(){
int cnt = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j < m; j++){
if(vex[i][j] == 1) continue;
if(vis1[i][j + 1] == 1 && vis2[i][j + 1] == 0 && r[i][j + 1] >= j + k){
return 1;
}
if(i < n && vis1[i + 1][j] == 1 && vis2[i + 1][j] == 0 && r[i + 1][j] >= j + k){
return 1;
}
if(i > 1 && vis1[i - 1][j] == 1 && vis2[i - 1][j] == 0 && r[i - 1][j] >= j + k){
return 1;
}
}
}
return 0;
}
int main(){
scanf("%d", &t);
while(t--){
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; i++){
scanf("%s", a + 1);
vex[i].clear();
vis1[i].clear();
vis2[i].clear();
r[i].clear();
vex[i].push_back(0);
vis1[i].push_back(0);
vis2[i].push_back(0);
r[i].push_back(0);
for(int j = 1; j <= m; j++){
vex[i].push_back(a[j] - '0');
vis1[i].push_back(0);
vis2[i].push_back(0);
r[i].push_back(0);
}
}
dfs1(1, 1);
dfs2(1, m);
for(int i = 1; i <= n; i++)
r[i][m] = m;
for(int j = m - 1; j > 0; j--){
for(int i = 1; i <= n; i++){
if(vex[i][j] == 1) continue;
if(vex[i][j + 1] == 0){
r[i][j] = max(r[i][j], r[i][j + 1]);
}
if(i != 1 && vex[i - 1][j] == 0){
r[i][j] = max(r[i][j], r[i - 1][j]);
}
r[i][j] = max(r[i][j], j);
}
for(int i = n - 1; i > 0; i--){
if(vex[i][j] == 1) continue;
if(vex[i + 1][j] == 0){
r[i][j] = max(r[i][j], r[i + 1][j]);
}
}
}
printf(check() ? "Yes\n" : "No\n");
}
return 0;
}
G题赛后补题
题意:给定一个小括号组成的括号序列,现在每个位置的小括号都有\(\frac{1}{2}\)的概率变成问号?,试问最后的序列可以唯一还原的概率(模998244353)
解答:分析可知,一个括号序列无法被还原,是由于某对括号序列可以发生交换,交换前后均合法
一开始想用概率搞无果,看了题解发现是计数问题:
考虑一个带有问号的括号序列。由于括号序列的所有还原方案的左右括号数分别相等,因此不同方案的差别可以视为将某些括号进行交换。
如果一个序列满足:对于序列中任意一对被问号化的括号,它们两个交换后将无法使得原序列仍然合法。
那么这个序列就是可以被还原的。(原因:存在唯一的一种括号序列,这个序列的所有问号处的括号都无法发生交换)
否则一定不能被还原。(原因:这对括号交换后仍合法,至少存在交换前后两个序列)
那么决定这个序列是否可以还原的是什么?
将左括号视为+1,右括号视为-1,则序列合法的条件可以转化为:任意位置处的前缀和始终非负。
这样可以发现,交换两个括号,相当于把左括号的位置的+1变成-1,从左括号到右括号的前一个位置这一整段的前缀和都会-2。
容易发现必须选择左括号在左,右括号在右的一对括号,才可能出现前缀和减小。
我们的目标是判断一个被问号化的序列是否能还原,因此我们要找到一个条件,使得每一对括号都不能发生交换。
因此,若我们可以选择最右端的左括号和最左端的右括号,若它们无法交换,就可以判断这整个序列无法发生任何交换了。(因为在左括号到右括号前一个位置这一段的前缀和一定存在一个前缀和会在交换后变成负数)
所以可以构想这样一个过程:从左到右枚举每一个i,然后对于每个i,找到最近的j,满足前缀和数组中存在一个\(k \in [i, j - 1]\) 使得 \(sum[k] <= 1\)。这个过程中j是单调增加的,可以用双指针\(O(n)\)解决
此时我们找到了一对不能发生交换的括号,那么我们可以一次性求出所有包含左括号i的所有答案了:只需统计左括号i之前的左括号数量x,然后再统计右括号j之后的右括号数量y(可以预处理)。除了左括号i必须固定外,这且统计出来的位置都是可以问号化的,且没被统计出来的位置都是不能问号化的。因此,直接一个快速幂求\(2^{x + y}\)就是答案数。
最后还要注意没有左括号的情况,显然也是直接2的右括号数次方。
把这些答案加起来,除以总情况数\(2^n\)就是答案了。
由于有个快速幂,复杂度应该是\(O(nlogn)\)
代码:
#include <bits/stdc++.h>
#define MAX 1000005
#define ll long long
#define mod 998244353
using namespace std;
int n;
char s[MAX];
int pre[MAX], suf[MAX];
int sum[MAX];
ll mul(ll x, ll y){
ll res = 1;
x %= mod;
while(y){
if(y & 1){
res *= x;
res %= mod;
}
x *= x;
x %= mod;
y >>= 1;
}
return res;
}
ll tra(ll x, ll y){
return x * mul(y, mod - 2) % mod;
}
int main(){
scanf("%s", s + 1);
n = strlen(s + 1);
for(int i = 1; i <= n; i++){
if(s[i] == '('){
sum[i] = 1;
pre[i] = 1;
}
else{
sum[i] = -1;
suf[i] = 1;
}
}
for(int i = 2; i <= n; i++){
sum[i] += sum[i - 1];
pre[i] += pre[i - 1];
}
for(int i = n - 1; i > 0; i--){
suf[i] += suf[i + 1];
}
int i = 1, j = 1;
ll cnt = 0;
int flag = 0;
while(i < n){
while(i < n && s[i] == ')'){
i++;
if(sum[i - 1] <= 1) flag--;
}
while(j <= i){
j++;
if(sum[j - 1] <= 1) flag++;
}
while(j <= n && (s[j] == '(' || flag == 0)){
j++;
if(sum[j - 1] <= 1) flag++;
}
if(j <= n) cnt += mul(2, pre[i - 1]) * mul(2, suf[j] % mod) % mod;
cnt %= mod;
i++;
if(sum[i - 1] <= 1) flag--;
}
cnt += mul(2, suf[1]);
cnt %= mod;
printf("%lld", tra(cnt, mul(2, n)) % mod);
return 0;
}

浙公网安备 33010602011771号