Educational Codeforces Round 105 (Rated for Div. 2) 题解
A.ABC string
【题面】
给定一个字符串 \(s\),构造一个括号字符串 \(t\) 满足:
1.如果任意 \(s_i=s_j\),那么 \(t_i=t_j\)。
2.\(t\) 满足身为一个括号字符串的要求。
【思路】
1.首先可以发现应为 \(s\) 中的元素数量只有 \(3\) 个,所以说可以直接枚举一下每一种不同的 \(s_i\) 可以对应哪些括号即可。
2.时间复杂度就是枚举每一种括号的情况,和判断是否符合一个括号序列。
【实现】
#include <bits/stdc++.h>
using namespace std;
const int N=5e1+10;
char s[N];
int dis[3];
void solve(){
memset(dis,0,sizeof(dis));
cin>>s;
if(strlen(s)%2) { puts("NO"); return; }
if(s[0]-'A'==s[strlen(s)-1]-'A') { puts("NO"); return; }
dis[s[0]-'A']=1;
dis[s[strlen(s)-1]-'A']=-1;
bool flag;
int zero,sum;
for(register int i=0;i<=2;i++) if(!dis[i]) zero=i;
dis[zero]=1;
sum=0,flag=true;
for(register int i=0;i<strlen(s);i++){
sum+=dis[s[i]-'A'];
if(sum<0) { flag=false; break; }
}
if(flag&&!sum) { puts("YES"); return; }
dis[zero]=-1;
sum=0,flag=true;
for(register int i=0;i<strlen(s);i++){
sum+=dis[s[i]-'A'];
if(sum<0) { flag=false; break; }
}
if(flag&&!sum) { puts("YES"); return; }
puts("NO");
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
solve();
}
return 0;
}
B. Berland Crossword
【题面】
给定一个 \(n\times n\) 的矩阵,再给出第一行,最后一行、第一列、和最后一列有多少个黑色的点。判断是否存在。
【思路】
1.首先可以发现对于非第一行,最后一行、第一列、和最后一列的格点是无效的。
2.角落的格子会有重复,所以只有这些格点是需要被特殊考虑的。再发现因为只有4个格子我们可以直接去枚举 \(2^4=16\) 次,去判断是否满足要求。
【实现】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
const int N = 1e5 + 5;
int n,u,d,l,r;
inline bool check(int ul, int ur, int dl, int dr){
int um = u - ul - ur;
int dm = d - dl - dr;
int lm = l - ul - dl;
int rm = r - ur - dr;
if(um < 0 || um > n - 2) return false;
if(dm < 0 || dm > n - 2) return false;
if(lm < 0 || lm > n - 2) return false;
if(rm < 0 || rm > n - 2) return false;
return true;
}
int a[4];
bool flag;
void dfs(int step){
if(step == 4){
flag |= check(a[0], a[1], a[2], a[3]);
return;
}
a[step] = 0;
dfs(step + 1);
a[step] = 1;
dfs(step + 1);
}
int main(){
ios::sync_with_stdio (false);
cin.tie(nullptr);
int T;cin>>T;
while(T --){
cin>>n>>u>>r>>d>>l;
flag = false;
dfs(0);
cout<<(flag ? "YES" : "NO")<<endl;
}
return 0;
}
C.1D Sokoban
【题面】
有 \(n\) 个箱子在一个数轴上,你可以从原点开始走去推箱子(不能拉),箱子可以推动箱子。
【思路】
1.因为不能拉,所以原点左侧和右侧的点不能到另一侧,所以可以分开考虑。
2.人只能推最左侧的箱子(我们简称可推箱子),可推箱子将左侧的若干个箱子压缩在一起,而右侧的箱子保持原状。因此,我们可以将答案的计算分成两个部分:压缩在一起的箱子占据的特殊位置数量,以及保持原状的箱子占据的特殊位置数量
3.因此,我们得到了一种不是很有效率的做法:枚举可推箱子的所在位置,计算有多少个箱子被压缩在一起,进而分别计算这两个部分的箱子占据的特殊位置数量。
4.可以用 lower_bound
去二分,时间复杂度是 \(O(n\log m)\)
【实现】
#include<bits/stdc++.h>
using namespace std;
constexpr size_t N = 2e5 + 5;
int a[N], b[N];
int sum[N], in[N];
void solve(){
int n, m;
cin >> n >> m;
unordered_set<int> s;
for (int i = 0; i < m; i++) in[i] = sum[i] = 0;
for (int i = 0; i < n; i++)cin >> a[i], s.insert(a[i]);
for (int i = 0; i < m; i++)cin >> b[i], in[i] = (s.count(b[i]) >= 1);
for (int i = 1; i < m; i++) in[i] += in[i - 1];
int ta = lower_bound(a, a + n, 0) - a - 1;
int tb = lower_bound(b, b + m, 0) - b - 1;
while (tb >= 0) {
while (ta >= 0 && a[ta] >= b[tb]) sum[tb]++, ta--;
tb--;
}
ta = lower_bound(a, a + n, 0) - a;
tb = lower_bound(b, b + m, 0) - b;
while (tb < m) {
while (a[ta] <= b[tb] && ta < n) sum[tb]++, ta++;
tb++;
}
int posl = lower_bound(b, b + m, 0) - b - 1, posr = posl + 1;
for (int i = posl - 1; i >= 0; i--) sum[i] += sum[i + 1];
for (int i = posr + 1; i < m; i++) sum[i] += sum[i - 1];
int ansl = 0, ansr = 0;
for (int i = posl, mx = posl + 1; i >= 0; i--) {
while (mx - 1 >= 0 && b[i] - b[mx - 1] + 1 <= sum[mx - 1]) mx--;
if (mx >= 1)
ansl = max(ansl, i - mx + 1 + in[mx - 1]);
else
ansl = max(ansl, i - mx + 1);
}
for (int i = posr, mx = posr - 1; i < m; i++) {
while (mx + 1 < m && b[mx + 1] - b[i] + 1 <= sum[mx + 1]) mx++;
ansr = max(ansr, mx - i + 1 + in[m - 1] - in[mx]);
}
int ans = ansl + ansr;
cout << ans << '\n';
}
signed main(){
int T;
cin >> T;
while(T--){
solve();
}
return 0;
}
D.DogeForces
【题面】
一个公司里有上下级关系,每个下级有恰好一个直属上司,立刻想到树形结构
用树形结构翻译题目:每个节点有权值,父节点的权值严格大于子节点,每个父节点有 \(\geq 2\) 个孩子。二维数组 \(a\) 给出了每对节点的最近公共祖先(lca)的权值。
【思路】
1.容易想到最大的权值肯定属于根(记为 \(max\))
2.如果 \(a_i = max\),说明 \(i,j\) 属于不同的树中。
3.因此,我们可以通过 \(a_{i,j}\) 是否等于 \(max\) 来确认有多少个子树,以及每个子树中有哪些节点(最直观的方法是使用并查集,代码中有更为方便快速的解法)。
4.用递归还原每一个树的结构
【实现】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
const int N = 1e3 + 5;
int c[N];
int a[N][N];
int n,k;
vector<pii> edges;
void solve(vi vs, int fa){
int u = vs[0];
if(vs.size() == 1){
c[u] = a[u][u];
if(fa != -1) edges.pb(mkp(u, fa));
return;
}
int maxa = 0;
for(auto v : vs){
maxa = max(maxa, a[u][v]);
}
vector<vi> subs;
for(auto v : vs){
bool smallest = true;
for(auto w : vs){
if(a[v][w] != maxa){
if(w < v) smallest = false;
}
}
if(smallest){//确保v是所在子树里编号最小的节点,这样能够避免重复计算子树
vi sub;
for(auto w : vs){
if(a[v][w] != maxa) sub.pb(w);
}
subs.pb(sub);
}
}
k++;
int curU = k - 1;
c[curU] = maxa;
if(fa != -1) edges.pb(mkp(curU, fa));
for(auto sub : subs){
solve(sub, curU);
}
}
int main(){
ios::sync_with_stdio (false);
cin>>n;
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++)
cin>>a[i][j];
}
k = n;
vi vs;
for(int i = 0; i < n; i ++) vs.pb(i);
solve(vs, -1);
cout<<k<<endl;
for(int i = 0; i < k; i ++) cout<<c[i]<<" ";
cout<<endl<<n + 1<<endl;
for(auto e : edges){
cout<<e.first + 1<<" "<<e.second + 1<<endl;
}
return 0;
}
E.A-Z Graph
【题面】
维护一个有边权的有向图,支持加边、删边,询问是否存在一个包含 \(k\) 个点的节点序列,正向走和反向走得到的字符串相同。
【思路】
1.观察样例路径 \(1 \to 2 \to 3 \to 2 \to 1\),发现这条路径是合法的,但与回文串没有直接关系。路径 \(1, 2, 3, 2, 1\) 本身是一个回文结构。
2.这个结构还可以进一步简化:\(1 \to 2 \to 1 \to 2 \to 1\) 也是一个合法路径!完全不需要 3 号点的参与。
3. 只要存在 \(u \to v\),\(v \to u\) 这样的双向边,就可以构造 \(u \to v \to u \to v \to u \ldots\) 这样的路径,这可以解决所有的奇数长度路径问题。
4. 假如有路径 \(1 \to 2 \to 3 \to 4\) 和 \(4 \to 3 \to 2 \to 1\) 能够得到相同的字符串,可以发现 \(2 \to 3\) 与 \(3 \to 2\) 必然有一样的字符。\(2 \to 3 \to 2 \to 3 \to \ldots\) 能解决所有偶数长度的路径,但相比奇数路径有额外要求:\(u \to v\) 和 \(v \to u\) 需要具有相同的边权。
【实现】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
const int N = 2e5 + 5;
int n,m;
map<pii,char> mp;
int cntO,cntE;
int main(){
ios::sync_with_stdio (false);
cin>>n>>m;
while(m--){
char op;int u, v, k;char c;
cin>>op;
switch(op){
case '+':
cin>>u>>v>>c;
if(mp.find(mkp(v,u)) != mp.end()){
cntO++;
if(mp[mkp(v,u)] == c) cntE++;
}
mp[mkp(u,v)] = c;
break;
case '-':
cin>>u>>v;
if(mp.find(mkp(v,u)) != mp.end()){
cntO--;
if(mp[mkp(v,u)] == mp[mkp(u,v)]) cntE--;
}
mp.erase(mkp(u,v));
break;
case '?':
cin>>k;
if(k % 2 == 1) cout<<(cntO > 0 ? "YES" : "NO")<<endl;
else cout<<(cntE > 0 ? "YES" : "NO")<<endl;
break;
default:
break;
}
}
return 0;
}s