A. ABC String
给你一个由A,B,C构成的字符串,判断他们所形成的括号式是否合法
首先,我们能确定首位的括号,( 设为1, )设为-1,用d[] 来存储。如果首尾字符相同,直接不合法。
可以发现合法的式子( )数各占一半,所以可以用vis[]来存储ABC是否出现,判断首或尾字符出现的数量是否为总数的一半
即可得到未出现的字符代表的括号。
我们知道出现多少个右括号,必须出现小于等于右括号数量的左括号这个式子才可能合法,需要遍历d[],计算前辍和,如果出现小于0的情况,则不合法。因为我们之前并未判断右括号或左括号是否为总数的一半,所以最后要判断总和是否为0.
这道题我比赛的时候没有A掉,我的思路大致正确,那时我想到了以前做过的一道逆波兰表达式的题,所以我当时采用的是栈模拟的办法,只要中途采取删除操作时,栈出现空则不合法,但是我判断符号代表的括号是sort一下,所以第一个就可能直接进行删除操作,直接不合法。
反思:多做题,观察题目的性质。
#include <bits/stdc++.h>
using namespace std;
int t;
string s;
int main()
{
cin >> t;
while (t -- )
{
int d[3], vis[3];
memset(d, 0, sizeof(d));
memset(vis, 0, sizeof(vis));
cin >> s;
int ed = s.size();
if (s[0] == s[ed - 1])
{
cout << "NO" << endl;
continue;
}
int x = s[0] - 'A', y = s[ed - 1] - 'A';
d[x] = 1, d[y] = -1;
vis[x] = 1, vis[y] = 1;
int z;
for (int i = 0; i < 3; i ++ )
if (vis[i] == 0) z = i;
int cnt = 0;
for (int i = 0; i < ed; i ++ )
{
if (s[i] == s[0]) cnt ++;
}
if (cnt == ed/2) d[z] = -1;
else d[z] = 1;
int pre = 0;
int f = 1;
for (int i = 0; i < ed; i ++ )
{
pre += d[s[i] - 'A'];
if (pre < 0)
{
f = 0;
break;
}
}
if (f == 1 && pre == 0) cout << "YES" << endl;
else cout << "NO" << endl;
}
}
B. Berland Crossword
在正方形的边上进行涂色操作,我们知道正方形的四个角因为有重合,是特殊的,每个角都有涂色和空白两种情况。暴力枚举16种情况(写个dfs可能代码会好看点)。
#include <bits/stdc++.h>
using namespace std;
int t;
int n, u, r, l, d;
int Check(int x, int y)
{
if (x >= 0 && x <= y - 2) return 1;
else return 0;
}
int main()
{
cin >> t;
while (t -- )
{
cin >> n >> u >> r >> d >> l;
// a,b,c,D四个角
int f = 0;
for (int a = 0; a <= 1; a ++)
for (int b = 0; b <= 1; b ++ )
for (int c = 0; c <= 1; c ++ )
for (int D = 0; D <= 1; D ++ )
{
int uu = u, rr = r, dd = d, ll = l;
if (a) uu --, ll --;
if (b) uu --, rr --;
if (c) rr --, dd --;
if (D) ll --, dd --;
if (Check(uu, n) && Check(rr, n) && Check(dd, n) && Check(ll, n)) f = 1;
}
if (f) cout << "YES" << endl;
else cout << "NO" << endl;
}
}
C. 1D Sokoban
看了好久题解,头秃
先复习下双指针的模板
for (i = 0, j = 0; i< n; i ++ )
{
while(j < i && check(i, j)) j ++; // j的范围和j需要满足的条件
// 这道题的具体逻辑
}
我们首先可以发现负数的箱子只能到达负数的特殊点,正数的箱子只能到达正数的特殊点,所以我们只用考虑正数的做法,负数改成正数就能套用
最暴力的做法就是O(nmmaxc)maxc是最远能到达的右边位置,n * m是遍历箱子所处的位子和特殊点的重合数量,可以发现,因为我们只能往右走,所以j指针是具有单调性的显然n*m能用双指针优化为n+m,伪代码如下
for (i = 0, j = 0; i < n; i ++ )
{
while (j < m && b[j] < a[i]) j ++;
if (a[i] == b[j]) cnt ++;
}
明显maxc是可以进行优化的,我们很容易知道最优的位置只可能是左边第一个盒子被推到特殊点的情况,因为此时要么特殊点位子上有箱子,那么覆盖点不变,如果无箱子,覆盖点加一,当然左边一堆箱子的最右端箱子到达特殊点也可能是最优解,但这样由于箱子在变,会更复杂。且只考虑左边第一个箱子也能达到相同的目的。这样我们的复杂度优化为
O((n+m)m),但很明显,还是会超。
我们可以用一个后辍数组存每个箱子后有多少箱子在特殊点上我们每次推箱子时只用把左边一堆箱子所覆盖的特殊点加上后缀即可。
看下代码,先用双指针把后缀算出来。用cnt来计算当第一个箱子推到特殊点时,能有几个箱子能成为左边的一堆箱子,用指针r计算长度为cnt的箱子当左边第一个箱子被推到特殊点时能覆盖掉躲少特殊点,最后再加上后缀。同时更新最优解。此时时间复杂度被优化为O(n + m)。
#include <bits/stdc++.h>
using namespace std;
int t, n, m;
int calc(vector<int> &a, vector<int> &b)
{
int n = a.size(), m = b.size();
// 求后辍和
vector<int> suf(n + 1);
for (int i = n - 1, r = m - 1; i >= 0; i -- )
{
suf[i] = suf[i + 1];
while (r >= 0 && b[r] > a[i]) r --;
if (r >=0 && b[r] == a[i]) suf[i] ++;
}
// 求最大覆盖特殊点数
int ans = 0;
int r = 0, cnt = 0;
for (int l = 0; l < m; l ++ )
{
while (cnt < n && a[cnt] <= b[l] + cnt) cnt ++;
while (r < m && b[r] - b[l] + 1 <= cnt) r ++;
ans = max(ans, r - l + suf[cnt]);
}
return ans;
}
int main()
{
cin >> t;
while (t -- )
{
cin >> n >> m;
vector<int> a(n), b(m);
vector<int> al, bl, ar, br;
for (int i = 0; i < n; i ++ )
{
cin >> a[i];
if (a[i] >= 0) ar.push_back(a[i]);
else al.push_back(- a[i]);
}
for (int i = 0; i < m; i ++ )
{
cin >> b[i];
if (b[i] >= 0) br.push_back(b[i]);
else bl.push_back(- b[i]);
}
reverse(al.begin(), al.end());
reverse(bl.begin(), bl.end());
cout << calc(al, bl) + calc(ar, br) << endl;
}
return 0;
}