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);
}
posted @ 2025-04-12 11:21  _lull  阅读(8)  评论(0)    收藏  举报