AtCoder Beginner Contest 447 解题报告
手速场。看有一哥们切了 A~F perf2400,我手速快点就上蓝了(。
AT_abc447_c
卡了十分钟,实现有点细节。
考虑到不是 A 的字符会把 \(s\) 和 \(t\) 分成很多个由 A 组成的字串。那么如果有解,那么二者不是 A 的字符组成的序列肯定一样。那考虑对于一组对应的 A 字串,贡献的操作就是两个字串长度之差。
实现就是假想字符串头上和尾部都加一个非 A 字符,然后一段区间其实从当前的非 A 字符到下一个非 A 字符。如果存在两个相邻的非 A 字符,中间的空串也算,不然会错位。
//to kill a living book
#include<bits/stdc++.h>
#define int long long
using namespace std;
string s,t;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>s>>t;
string h1,h2;
for(char ch:s) if(ch!='A') h1+=ch;
for(char ch:t) if(ch!='A') h2+=ch;
if(h1!=h2) return cout<<"-1\n",0;
vector<int> cnt1,cnt2;
cnt1.push_back(0);
cnt2.push_back(0);
for(char ch:s){
if(ch!='A') cnt1.push_back(0);
else cnt1.back()++;
}
for(char ch:t){
if(ch!='A') cnt2.push_back(0);
else cnt2.back()++;
}
int ans=0;
for(int i=0;i<cnt1.size();i++) ans+=abs(cnt1[i]-cnt2[i]);
cout<<ans<<"\n";
return 0;
}
AT_abc447_d
首先是显然的是尽可能消前面的,给后面的留尽量多的机会。那么肯定是找到一个消一个。直接把三种字母的下标分别塞进三个数组。注意到一组 ABC 下标可以被消除当且仅当 \(i<j<k\),那么直接跑一个类似于双指针的操作就行了。看代码。
//to kill a living book
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
string s; cin>>s;
vector<int> a,b,c;
for(int i=0;i<s.size();i++){
if(s[i]=='A') a.push_back(i);
else if(s[i]=='B') b.push_back(i);
else c.push_back(i);
}
int ans=0;
for(int i=0,j=0,k=0;i<a.size()&&j<b.size()&&k<c.size();i++){
while(j<b.size()&&b[j]<a[i]) j++;
while(k<c.size()&&c[k]<b[j]) k++;
if(j<b.size()&&k<c.size()) ans++;
j++,k++;
}
cout<<ans<<"\n";
return 0;
}
AT_abc447_e
并查集都不会写了。考虑倒序遍历所有边,如果合并完之后图仍然不连通,那就选择这条边,否则答案加上这条边的贡献。正确性是因为删去当前的边的代价是大于你还没有遍历的所有边的代价之和。所以哪怕你只选这条边更小的全部不选了也比牺牲当前边来合并一条小的边更优。
//to kill a living book
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+7,mod=998244353;
vector<pair<int,int>> e;
int n,m,ans;
struct DSU{
int f[N],cnt;
void init(){
for(int i=1;i<=n;i++) cnt++,f[i]=i;
}
int find(int x){
return x==f[x]?x:f[x]=find(f[x]);
}
bool merge(int x,int y){
x=find(x),y=find(y);
if(x==y) return true;
if(cnt<=2) return false;
if(x!=y) return f[x]=y,cnt--,true;
}
} Misaka;
int qpow(int x,int y){
int res=1;
for(int i=y;i;i>>=1){
if(i&1) (res*=x)%=mod;
(x*=x)%=mod;
} return res;
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v; cin>>u>>v;
e.push_back({u,v});
}
reverse(e.begin(),e.end());
Misaka.init();
for(int i=0;i<m;i++){
auto [u,v]=e[i];
if(!Misaka.merge(u,v)) ans+=qpow(2,m-i),ans%=mod;
}
cout<<ans<<"\n";
return 0;
}
AT_abc447_f
F<D。
直接树形 DP,设计状态 \(f_i\) 代表以 \(i\) 为起点,在 \(i\) 的子树内可以达到的蜈蚣状图形最长长度。转移受很多度数的限制,可以看看代码,找每一种转移的时候记得有条理地找,统计答案可以由两个子树的 \(f\) 拼起来,还有其它方式,也是有条理地找就行。看代码。
//to kill a living book
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+7;
int n,a[N],f[N],ans;
vector<int> g[N];
void dfs(int u,int pre){
f[u]=0;
int son=0,mx=-1,sn=-1;
for(int v:g[u]){
if(v==pre) continue;
dfs(v,u),son++;
if(f[v]>mx) sn=mx,mx=f[v];
else if(f[v]>sn) sn=f[v];
}
int deg=son+(pre!=0);
if(deg>=4) ans=max(ans,mx+sn+1);
if(deg>=3) ans=max(ans,mx+1);
if(deg>=2) ans=max(ans,1ll);
if(son>=3) f[u]=max(f[u],mx+1);
if(son>=2) f[u]=max(f[u],1ll);
ans=max(ans,f[u]);
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++) g[i].clear(),g[i].shrink_to_fit();
for(int i=1;i<n;i++){
int u,v; cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
ans=0,dfs(1,0),cout<<ans<<"\n";
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t; cin>>t;
while(t--) solve();
return 0;
}
还有一个挺聪明的解法是把度不小于 \(4\) 的点的连通块的及边界上的度等于 \(3\) 的点拿出来跑直径,被小 Y 爆杀了。
AT_abc447_g
看 LA 群友说是乱搞,然后同学又说好像可以分块,我也感觉根号算法是可行的hh。
本文来自博客园,作者:GE9x,转载请注明原文链接:https://www.cnblogs.com/GE9X/p/19654148

浙公网安备 33010602011771号