ABC398
UNIQUE VISION Programming Contest 2025 Spring (AtCoder Beginner Contest 398)
D - Bonfire
First
发现\(t+0.5\)烟雾能到当且仅当 前面以前面\(t\)个时间为起点进行移动 , 至少一个能到该位置.直接枚举是\(O(N^2)\)的.
第一眼感觉很难 , 因为路径数量太多了 , 情况很复杂.但是给定始末位置后判断能否到达其实只需要处理在\(x\)轴上的合运动和\(y\)轴上的合运动 , 用前缀和可以快速判断. 具体实现见代码
void solve() {
int n,r,c;cin>>n>>r>>c;
set<pair<int,int>> st;
string s;cin>>s;
int dx=0,dy=0;
for (auto ch: s) {
st.insert({dx,dy});
if (ch=='N') {
dx--;
}else if (ch=='S') {
dx++;
}else if (ch=='W') {
dy--;
}else {
dy++;
}
if (st.count({dx-r,dy-c})) {
cout<<1;
}else {
cout<<0;
}
}
}
Second
直接模拟 , 每次要维护的东西太多 . 但我们发现烟雾的运动是以火和人为参考系的. 如果我们更换参考系 , 以烟雾为参考系, 就只用维护2个东西了.由于只是转化了参考系,并不影响我们生成烟雾的过程.
void solve() {
pair<int,int> p,f{0,0};
int n,r,c;cin>>n>>p.first>>p.second;
set<pair<int,int>> st;
string s;cin>>s;
for (auto ch: s) {
st.insert(f);
if (ch=='N') {
f.first++,p.first++;
}else if (ch=='S') {
f.first--,p.first--;
}else if (ch=='W') {
f.second++,p.second++;
}else {
f.second--,p.second--;
}
if (st.count(p)){
cout<<1;
}else {
cout<<0;
}
}
}
E - Tree Game
不妨先考虑在原树上加一条边.
我们添加一条边一定会形成一个环 (这两点找LCA的路径) . 下面考察奇环有什么性质 , 路径为奇数 , 故层数同奇偶.
但这里有一个问题 , 就是已经加了几条边 . 导致环改变了.
什么边会影响环\(v_0,v_1,\cdots,v_k\),?
- 两个点都不在环内;
- 恰好一个点在环内;
- 两个点都在环内.
(1) 先看第三种 , 说明该环内有个小环. 由于小环一定是偶环 , 故不会影响新环的奇偶.
(2) 而前两种边可能会导致这个环在某个大环里 . 若新环为偶环 , 因为大环也为偶环 ,故另外一个环也是偶环.
因此: 猜想操作集是固定的,互不干扰的 . 即可以连的边是固定的 , 且连一条恰好少一条.
我们说我们一定可以且最多只能将所有层数奇偶不同的点连接起来. 下面给出较为严谨的证明:
引理1:任何时候都不能连接层数同奇偶的点.
由(1) 显然成立
引理2:已经形成的奇环数量不会减少 .
若环\(v_0,v_1,\cdots,v_k\)为一个奇环 , 我们想改变其奇偶 ,只能在其环内连边 . 由(1),只能连奇环.故奇环数不变.
引理3:将所有层数奇偶不同的点连接起来的过程中不会形成奇环.
由引理2 , 显然只需要看最后有无奇环即可. 不妨对点染色(奇数层染1, 偶数层染0), 显然连完边后还是一个二分图.若存在一个奇数环一定会存在同色的两个点相连 , 矛盾.
由这三个引理 , 显然一定可以且最多只能将所有层数奇偶不同的点连接起来.
上面的思路比较繁琐.
但如果先想到没有奇环的图是二分图 , 然后在二分图上考虑就会简单很多.
于是根据奇偶选择先手 一个一个连即可. 实现见代码.
int dep[110];
bool a[110][110];
int n;
void dfs(int x, int fa) {
dep[x]=dep[fa]+1;
for (int i=1;i<=n;++i) {
if (!a[x][i] or i==fa) continue;
dfs(i,x);
}
}
void solve() {
cin>>n;
for (int i=1;i<n;++i) {
int x,y;cin>>x>>y;
a[x][y]=a[y][x]=1;
}
dfs(1,0);
set<pair<int,int>> op;
for (int i=1;i<=n;++i) {
for (int j=i+1;j<=n;++j) {
if (abs(dep[i]-dep[j])%2==0 or a[i][j]) continue;
op.insert({i,j});
}
}
if (op.size()&1) {
cout<<"First"<<endl;
}else {
cout<<"Second"<<endl;
int x,y;cin>>x>>y;
if (x==-1) return ;
if (x>y)swap(x,y);
op.erase({x,y});
}
while (op.size()) {
auto [x,y]=*op.begin();
cout<<x<<" "<<y<<endl;
op.erase(op.begin());
cin>>x>>y;
if (x==-1) return ;
if (x>y)swap(x,y);
op.erase({x,y});
}
}
F - ABCBA
First(字符串哈希)
等价于求最长回文后缀 . 考虑字符串哈希.
const int N=1<<21;
static const int mod1=1e9+7,base1=127;
static const int mod2=1e9+9,base2=131;
vector<int> val1,val2;
void init(int n=N) {
val1.resize(n+1),val2.resize(n+2);
val1[0]=1,val2[0]=1;
for (int i=1;i<=n;++i) {
val1[i]=val1[i-1]*base1%mod1;
val2[i]=val2[i-1]*base2%mod2;
}
}
struct String {
vector<int> hash1,hash2;
string s;
String(string s_) : s(s_),hash1{1},hash2{1} {
for (auto it: s) {
hash1.push_back((hash1.back()*base1%mod1+it)%mod1);
hash2.push_back((hash2.back()*base2%mod2+it)%mod2);
}
}
pair<int,int> get() {
return {hash1.back(),hash2.back()};
}
pair<int,int> substring(int l,int r) {
if (l>r) swap(l,r);
int ans1=((hash1[r+1]-hash1[l]*val1[r-l+1]%mod1)%mod1+mod1)%mod1;
int ans2=((hash2[r+1]-hash2[l]*val2[r-l+1]%mod2)%mod2+mod2)%mod2;
// cout<<ans1<<" "<<ans2<<endl;
return {ans1,ans2};
}
};
void solve() {
string s;cin>>s;
int n=s.size();
String Str(s);
string rs=s;
reverse(rs.begin(),rs.end());
String RStr(rs);
int p=0;
for (;p<n;++p) {
if (Str.substring(p,n-1)==RStr.substring(n-1-p,0)) {
break;
}
}
cout<<s<<rs.substr(n-p);
}
Second(KMP)
如果我们把S的反串接在前面 , 那么最长回文后缀就变成了新串的前缀函数.
void solve() {
string s;cin>>s;
string r=s;
reverse(r.begin(),r.end());
r=r+"@#"+s;
int n=r.size();
vector<int> kmp(n+1);
r="@"+r;
for (int i=2,j=0;i<=n;++i) {
while (j and r[i]!=r[j+1]) j=kmp[j];
j+=r[i]==r[j+1];
kmp[i]=j;
}
cout<<s<<r.substr(kmp[n]+1,s.size()-kmp[n]);
}
Third(manacher)
直接用manacher求出最长回文后缀 , 即可
void solve() {
string s;cin>>s;
int n=s.length();
string t="-#";
for (auto c:s) {
t+=c;
t+="#";
}
int m=t.length();
t+="+";
int mid=0,r=0;
vector<int> p(m);
for (int i=1;i<m;++i) {
p[i]=i<r ? min(p[2*mid-i],r-i):1;
while (t[i-p[i]]==t[i+p[i]]) p[i]++;
if (i+p[i]>r) {
r=i+p[i];
mid=i;
}
}
int len=0;
for (int i=1;i<m;++i) {
if (i+p[i]>2*n) {
if (i&1) len=p[i]/2*2;
else len=(p[i]+1)/2*2-1;
break;
}
}
// cout<<len<<endl;
cout<<s;
reverse(s.begin(),s.end());
cout<<s.substr(len,n-len);
}

浙公网安备 33010602011771号