ARC171 - sol

感觉难度并不大,但是就是做不起,呜呜呜。

中等题练少了是这样的。


AtCoder Regular Contest 171 - AtCoder

A - No Attacking

There is a chessboard with \(N\) rows and \(N\) columns. Let \((i, j)\) denote the square at the \(i\)-th row from the top and the \(j\)-th column from the left.
You will now place pieces on the board. There are two types of pieces, called rooks and pawns.
A placement of pieces is called a good arrangement when it satisfies the following conditions:

  • Each square has zero or one piece placed on it.
  • If there is a rook at \((i, j)\), there is no piece at \((i, k)\) for all \(k\) \((1 \leq k \leq N)\) where \(k \neq j\).
  • If there is a rook at \((i, j)\), there is no piece at \((k, j)\) for all \(k\) \((1 \leq k \leq N)\) where \(k \neq i\).
  • If there is a pawn at \((i, j)\) and \(i \geq 2\), there is no piece at \((i-1, j)\).

Is it possible to place all \(A\) rooks and \(B\) pawns on the board in a good arrangement?

You are given \(T\) test cases; solve each of them.

\(1 \le N \le 10^4,1 \le T \le 10^5\)

简单题,但是这里注意兵是可以放在第一排的啊。


感觉确实没什么说的,自己推一下就可以了,直接放上代码。

#include <bits/stdc++.h>
using namespace std;

int n,a,b,T;

bool sol(){
  if(a>n) return false;
  n-=a;
  if(n<=a) return b<=n*n;
  return b<=a*n+((n-a+1)/2)*n;
}

int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>T;
  while(T--){
    cin>>n>>a>>b;
    sol()?cout<<"Yes\n":cout<<"No\n";
  }
  return 0;
}

B - Chmax

For a permutation \(P = (P_1, P_2, \dots, P_N)\) of \((1, 2, \dots, N)\), we define \(F(P)\) by the following procedure:

  • There is a sequence \(B = (1, 2, \dots, N)\).
    As long as there is an integer \(i\) such that \(B_i \lt P_{B_i}\), perform the following operation:

    • Let \(j\) be the smallest integer \(i\) that satisfies \(B_i \lt P_{B_i}\). Then, replace \(B_j\) with \(P_{B_j}\).

    Define \(F(P)\) as the \(B\) at the end of this process. (It can be proved that the process terminates after a finite number of steps.)

You are given a sequence \(A = (A_1, A_2, \dots, A_N)\) of length \(N\). How many permutations \(P\) of \((1,2,\dots,N)\) satisfy \(F(P) = A\)? Find the count modulo \(998244353\).

\(1 \le n \le 2 \times 10^5\)

推一下就可以发现这里面的性质,每一次一定是往大走而不是往小走,

所以这样会构成一些链,而每一条链的结尾都是 \(a_i=i\) 的节点(这需要判,否则就会像我这样调半天)。


容易发现这也反应了一个性质:\(a_i \ge i\),可以直接判掉了。

而构成了许多链之后,发现可以单独排序的数其实只有链头了,

而可以随便选 \(\le i\) 的数也是只有每一条链的链尾了,

于是这就直接做就可以了,时间复杂度是线性的。


其实模拟一下样例就都会了。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back

const int N=2e5+5;
const ll mod=998244353;
int n,a[N],cnt=0,p[N],nw=0;
ll ans=1;
vector<int> G[N];

bool chk(){
  for(int i=1;i<=n;i++) if(a[i]<i) return true;
  return false;
}

int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n;
  for(int i=1;i<=n;i++) cin>>a[i];
  if(chk()) cout<<0,exit(0);
  for(int i=n;i>=1;i--) G[a[i]].pb(i);
  for(int i=1;i<=n;i++) if(!G[i].empty()) p[++cnt]=G[i].back();
  sort(p+1,p+cnt+1);
  for(int i=1,j=0;i<=n;i++){
    if(G[i].empty()) continue;
    while(j<cnt&&p[j+1]<=i) ++j;
    if(G[i][0]!=i) cout<<0,exit(0);
    ans=1ll*(j-nw)*ans%mod;++nw; 
  }
  cout<<ans;
  return 0;
}

C - Swap on Tree

There is a tree with \(N\) vertices numbered \(1\) to \(N\). The \(i\)-th edge connects vertices \(u_i\) and \(v_i\).
Additionally, there are \(N\) pieces numbered \(1\) to \(N\). Initially, piece \(i\) is placed on vertex \(i\).
You can perform the following operation any number of times, possibly zero:

  • Choose one edge. Let vertices \(u\) and \(v\) be the endpoints of the edge, and swap the pieces on vertices \(u\) and \(v\). Then, delete the chosen edge.

Let \(a_i\) be the piece on vertex \(i\). How many different possible sequences \((a_1, a_2, \dots, a_N)\) exist when you finish performing the operation? Find the count modulo \(998244353\).

\(1 \le n \le 3000\)

乍一看非常像直接 Gym 中的题目,想到了是一个 dp,但后面逐渐想偏了,就不知道什么状态了。/kk


容易发现一个子树内只会有一个数不是内部的,但是那个数是什么并不重要!!!

所以我们没必要去记录那个数是什么,只需要知道 断了多少条边 了,到父亲的边有没有断 即可!!!

于是设 \(f_{u,i,0/1}\) 表示以 \(u\) 为根的子树中,与 \(u\) 相连的边断了 \(i\) 条(到父亲的也算),到父亲的边是否断了。

那么初始状态就是:\(f_{u,0,0}=1,f_{u,1,1}=[u\neq 1]\)


接着,会发现转移是非常好写的,这里我们令 \(g_{u,c}=\sum f_{u,i,c}\),那么我们有:

\[\begin{align} f_{u,i,0} & = f_{u,i,0} \times g_{v,0} + f_{u,i-1,0} \times g_{v,1} \times i \\ f_{u,i,1} & = f_{u,i,1} \times g_{v,0} + f_{u,i-1,1} \times g_{v,1} \times i \end{align} \]

这里最后的 \(\times i\) 表示当前加入的 \(v\) 可以差到原序列的任意一个位置,而这就是它的贡献(感觉比较好懂)。


于是直接这样转移就可以了,最后的答案非常显然。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back

const int N=3e3+5;
const ll mod=998244353;
int n;
ll g[N][2],f[N][N][2],dp[N][2];
vector<int> G[N];

ll adc(ll a,ll b){return (a+b>=mod)?a+b-mod:a+b;}
void add(ll &a,ll b){a=adc(a,b);}

void dfs(int u,int fa){
  f[u][0][0]=1,f[u][1][1]=(u!=1);
  for(auto v:G[u]) if(v!=fa){
    dfs(v,u);
    for(int i=0;i<=(int)G[u].size();i++)
      dp[i][0]=adc(f[u][i][0]*g[v][0]%mod,i?1ll*f[u][i-1][0]*i%mod*g[v][1]%mod:0),
      dp[i][1]=adc(f[u][i][1]*g[v][0]%mod,i?1ll*f[u][i-1][1]*i%mod*g[v][1]%mod:0);
    for(int i=0;i<=(int)G[u].size();i++)
      f[u][i][0]=dp[i][0],f[u][i][1]=dp[i][1];
  }
  g[u][0]=g[u][1]=0;
  for(int i=0;i<=(int)G[u].size();i++)
    add(g[u][0],f[u][i][0]),add(g[u][1],f[u][i][1]);
}

int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n;
  for(int i=1,u,v;i<n;i++) cin>>u>>v,G[u].pb(v),G[v].pb(u);
  dfs(1,0);
  cout<<g[1][0];
  return 0;
}

D - Rolling Hash

You are given non-negative integers \(P\) and \(B\). Here, \(P\) is prime, and \(1 \leq B \leq P-1\).
For a sequence of non-negative integers \(X=(x_1,x_2,\dots,x_n)\), the hash value \(\mathrm{hash}(X)\) is defined as follows.

\(\displaystyle \mathrm{hash}(X) = \left(\sum_{i=1}^n x_i B^{n-i}\right) \bmod P\)

You are given \(M\) pairs of integers \((L_1, R_1), (L_2, R_2), \dots, (L_M, R_M)\).
Is there a sequence of non-negative integers \(A=(A_1, A_2, \dots, A_N)\) of length \(N\) that satisfies the condition below?

  • For all \(i\) \((1 \leq i \leq M)\), the following condition holds:
    • Let \(s\) be the sequence \((A_{L_i}, A_{L_i + 1}, \dots, A_{R_i})\) obtained by taking the \(L_i\)-th to the \(R_i\)-th elements of \(A\). Then, \(\mathrm{hash}(s) \neq 0\).

\(1 \le n \le 16,1 \le P \le 10^9\)

很典的思路,但是我不知道。(WC2024 的 T3 暴力也是这个思路,但是我也不知道)/fn/fn


对于这种区间问题,我们尝试把它转化到点上面的关系!

于是我们记 \(s_i\) 表示 \(i \sim n\) 的哈希值,那么对于一段区间 \([l,r]\),它的哈希值可以用两个点表示出来:

\[B^{r-n}(s_l-s_{r+1}) \pmod p \]

如果这个东西 \(=0\),而 \(B^{r-n}\) 一定不为零,所以 \(s_l \equiv s_{r+1} \pmod p\)

为了满足条件,我们就需要 \(s_l \neq s_{r+1}\) 而容易发现这里的 \(s_i\) 的取值其实只有 \(p\) 种。


于是这里我们就可以抽象出图论模型了,每次将 \(l\)\(r+1\) 中建出一条边,

那么我们的目的就是去染色,使得每一条边的 两个端点的颜色不同


而去判断可不可以,我们不如算所需要的最小颜色数,设状态 \(f_{S}\) 表示只考虑 \(S\) 中的点是的最小染色次数,

那么最后判断 \(f_U \le P\) 即可,而这里的 \(U\) 就是全集。


考虑转移,发现其实我们想把一个同色连动块找出来,而这个集合 \(T\) 一定是一个独立集,并且是 \(S\) 的子集。

而一个集合是不是独立集是可以先用 \(\mathcal O(2^nn^2)\) 的时间预处理出来的,

于是对于满足条件的集合 \(T\),直接转移就是 \(f_{S}=\min f_{S \oplus T}+1\)

这样的时间复杂度就是 \(\mathcal O(3^n)\) 了,就做完了。

#include <bits/stdc++.h>
using namespace std;

const int N=17,M=(1<<17)+1;
int n,m,P,B,f[M];
bool G[N][N],g[M];

bool val(int s,int i){return s&(1<<i);}

int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>P>>B>>n>>m;
  for(int i=1,l,r;i<=m;i++) cin>>l>>r,G[l-1][r]=G[r][l-1]=1;

  if(P>n) cout<<"Yes",exit(0);

  const int U=(1<<(n+1))-1;
  for(int s=0;s<=U;s++){
    g[s]=1;
    for(int u=0;u<=n;u++) if(val(s,u))
      for(int v=u+1;v<=n;v++) if(val(s,v))
        if(G[u][v]){g[s]=0;break;}
  }
  memset(f,0x3f,sizeof(f));
  f[0]=0;
  for(int s=1;s<=U;s++)
    for(int t=s;t;t=(t-1)&s) if(g[t]) f[s]=min(f[s],f[s^t]+1);
  cout<<(f[U]<=P?"Yes":"No");
  return 0;
}

E - Rookhopper's Tour

Problem Statement

There is a grid with \(N\) rows and \(N\) columns. Let \((i, j)\) denote the cell at the \(i\)-th row from the top and the \(j\)-th column from the left. Additionally, there is one black stone and \(M\) white stones.
You will play a single-player game using these items.

Here are the rules. Initially, you place the black stone at \((A, B)\). Then, you place each of the \(M\) white stones on some cell of the grid. Here:

  • You cannot place a white stone at \((A, B)\).
  • You can place at most one white stone per row.
  • You can place at most one white stone per column.

Then, you will perform the following operation until you cannot do so:

  • Assume the black stone is at \((i, j)\). Perform one of the four operations below:
    • If there is a white stone at \((i, k)\) where \((j \lt k)\), remove that white stone and move the black stone to \((i, k + 1)\).
    • If there is a white stone at \((i, k)\) where \((j \gt k)\), remove that white stone and move the black stone to \((i, k - 1)\).
    • If there is a white stone at \((k, j)\) where \((i \lt k)\), remove that white stone and move the black stone to \((k + 1, j)\).
    • If there is a white stone at \((k, j)\) where \((i \gt k)\), remove that white stone and move the black stone to \((k - 1, j)\).
      • Here, if the cell to which the black stone is to be moved does not exist, such a move cannot be made.

The following figure illustrates an example. Here, B reprfesents the black stone, W represents a white stone, . represents an empty cell, and O represents a cell to which the black stone can be moved.

..O...
..W...
......
......
..B.WO
......

You win the game if all of the following conditions are satisfied when you finish performing the operation. Otherwise, you lose.

  • All white stones have been removed from the grid.
  • The black stone is placed at \((A, B)\).

In how many initial configurations of the \(M\) white stones can you win the game by optimally performing the operation? Find the count modulo \(998244353\).

\(1 \le n,m \le 2\times 10^5\)

感觉非常有意思的题目,一眼会感觉到有很多性质,但是又不太能具体出来。/kk


首先容易发现走的路线一定只有两种:

  • 沿着行 \(\to\) 沿着列 \(\to\) 沿着行 \(\to \dots \to\) 沿着列
  • 沿着列 \(\to\) 沿着行 \(\to\) 沿着列 \(\to \dots \to\) 沿着行

于是容易发现,这里我们要求 \(m\) 一定是偶数,反之无解。

而会发现,我们的这两种路线不会同时成立,于是我们可以分开来讨论。

证明感觉可以感性理解,因为如果同时成立,那么我们每一次跳一定只跳两格,这与定义中的每行每列只能一个不符合,

而又一定不会存在一种路径可以正反各走一次。

所以我们只需要先考虑第一种情况,第二种是同理的(最终答案其实乘个 \(2\) 就可以了)。


这时候我们考虑把每一次变化用数学语言表示出来了,

我们假设他先往下走,那么原来在 \((A,B)\) 跳过一个 \((x,B)\) 的石头,到了 \((x+1,B)\)

这时我们再假设它往右边跳,要去跳 \((x+1,y)\) 的石头,到了 \((x+1,y+1)\)

于是就有了一个性质:发现这里的两个石头是分别在第 \(x\) 行和第 \(x+1\) 行,而先是跳 \(x\) 行的还是 \(x+1\) 行的取决于你是从上面跳下来还是从下面跳上去。

而类似分析一下,发现列也是同样有这样的性质,就是所有石子一定两两相邻!!!


有了这个性质似乎就可以直接用组合数计算了,枚举一下你是从左边来的还是从右边来的就可以了,

而行列其实本质相同,互相独立且互不干扰,

由于我们最后要回到 \((A,B)\),所以有些东西其实是必定的,比如在这种情况中,最后一个石头必须在 \(B\) 这一列这样的。

于是其实排除掉分类讨论,其实随便排的只有 \(m-2\) 组(这里我们将原来的 \(m\) 除以 \(2\) 来计算)。


所以具体实现中,我们枚举一下当前这种情况它左边有多少组,右边有多少组,直接组合计数就可以了。

这样就做完了,实现出来是 \(\mathcal O(n)\) 的,具体还是可以参考代码。

#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int N=2e5+5,mod=998244353;
int n,m,A,B,fac[N],inv[N];

int qpow(int a,int b){
  int res=1;
  while(b){if(b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
  return res;
}

void init(){
  fac[0]=1;
  for(int i=1;i<N;i++) fac[i]=1ll*fac[i-1]*i%mod;
  inv[N-1]=qpow(fac[N-1],mod-2);
  for(int i=N-1;i>=1;i--) inv[i-1]=1ll*inv[i]*i%mod;
}

int binom(int n,int m){
  if(n<m||m<0) return 0;
  return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
}

int sol(int L,int R){
  int res=0;
  for(int l=0,r;l<=m;l++){
    r=m-l;
    if(2*l>L||2*r>R) continue;
    if(l&&2*l+1<=L) res=(res+1ll*binom(L-l-1,l)*binom(R-r,r)%mod*l%mod)%mod;
    if(r&&2*r+1<=R) res=(res+1ll*binom(L-l,l)*binom(R-r-1,r)%mod*r%mod)%mod;
  }
  return 1ll*res*fac[m-1]%mod;
}

int main(){
  cin>>n>>m>>A>>B;init();
  if((m&1)||m==2) cout<<0,exit(0);
  m=(m>>1)-1;
  cout<<(1ll*sol(A-1,n-A)*sol(B-1,n-B)%mod*2ll%mod);
  return 0;
}

F 题相当抽象,先咕了。


Conclusion

  1. 做题时注意无解情况的判断,WA 部分点很有可能就是这种情况。(B)
  2. dp 题目一定要考虑那些东西是 重要的,分清主次才能更好地写出 dp 状态。(C)
  3. 有关一个 区间 的关系我们可以通过类似于差分的方法转化成 点与点 之间的关系,从而能够更好的建图。(D)
  4. 判断是否在一定限制内完成,我们可以转化成求 完成的最小次数。(D)
  5. 问方案数的题目要尝试把抽象的性质通过一步一步的数学语言表示转化成可以简单易懂的性质,这样就可以用组合数解决。(E)
posted @ 2024-02-08 15:00  H_W_Y  阅读(46)  评论(0编辑  收藏  举报