题解:AT_agc077_b [AGC077B] Long Increasing Walk

更差的阅读体验


这应是我第一次独立做出黑题!这应该是一个思路和实现都比其他题解简单的做法。


我们需要解决这样一个问题:给一个左右各 \(n\) 个点的完全二分图上的 \(n^2\) 条边分别确定边权,使最长上升路径的长度恰好为 \(k\),或报告无解。

首先如果见过 arc220d 的 trick 就会知道,完全图上的一条边可以看作网格图上的一个格子;完全二分图也可以。考虑一条完全二分图上的路径在网格上表现为什么:假设边 \((l_i, r_j)\) 代表格子 \((i, j)\),格子的权值就是 \((l_i, r_j)\) 这条边的边权,那么一条二分图上的路径,一定可以看作网格图上的一条“路径”,使相继经过的两个格子在同一行或同一列,且横着走和竖着走交替进行,且所经过的格子权值递增。

那么我们先考虑判断无解的问题。先考虑答案下界,我们声称,对于左右各 \(n\) 个点的完全二分图,其最长上升路径的长度,至少为 \(n\)

考虑假如给定了二分图上每条边的边权数组 \(a\),我们应该如何求出这个二分图的最长上升路径?

假设 \(f_{i, j, 0/1}\) 表示以 \((i, j)\) 结束的路径,最后一步是横向/纵向移动,的最长上升路径。

那么我们先将所有 \(f\) 赋值为 \(-\infty\)。然后按照 \(a_{x, y}\) 从小到大遍历每个格子 \((x, y)\)

  • \(f_{x, y, 0} = f_{x, y, 1} = 1\),表示以这个格子为起点。
  • \(f_{x, y, 0} \leftarrow \max\limits _i\left({f_{x, i,1} + 1}\right)\),从同行的一个格子转移。
  • \(f_{x, y, 1} \leftarrow \max\limits _i\left({f_{i, y,0} + 1}\right)\),从同列的一个格子转移。

注意到 \(f_{x, y, 0}\) 的转移实际上与 \(x\) 无关,因此考虑直接记为 \(f_{y, 0}\)\(f_{x, y, 1}\) 的转移与 \(y\) 无关,因此直接记为 \(f_{x, 1}\)

接下来考虑 \(f_{y, 0} + f_{x, 1}\) 这个东西。容易发现,

\[f_{y, 0} + f_{x, 1} = \max\limits _i\left({f_{i,1} + 1}\right) + \max\limits _i\left({f_{i,0} + 1}\right) = \max_{i, j} \left(f_{i, 0} + f_{j, 1} + 2\right) \]

因此对于最后一个格子 \((x, y)\) 所在行和所在列的 \(2n\) 个格子(\((x, y)\) 要计算两次),以它们为结尾的路径长度之和为 \(2n^2\),也就是说,一定存在一个格子,存在以它结尾、长度 \(\ge n\) 的路径。

至于为什么最小值恰好为 \(n\),下文的构造将会给出验证。

接下来考虑上界。对于上界,我们可以先取出图中最长的一条路径(假设长度为 \(L\)),然后在这条路径上依次填上 \(1 \sim L\) 即可。

那么当 \(n\) 是偶数,图中存在欧拉回路,因此答案上界为 \(n^2\);当 \(n\) 是奇数,要删除一些边使原图存在欧拉路。容易计算删除的边数为 \(n-1\)

因此有解条件是,\(k \ge n\),且 \(k \le n^2\)\(n\) 为偶数),或 \(k \le n^2 - (n-1)\)\(n\) 为奇数)。


接下来考虑构造。

首先我们考虑将 \(n^2\) 个格子分成 \(k\) 个等价类,每个等价类内部两两不能到达,而第 \(i\) 个等价类能够到达第 \(i+1\) 个等价类。那么我们只需要保证第 \(i+1\) 个等价类的最小值 \(>\)\(i\) 个等价类的最大值,就可以保证我们从第 \(i\) 个等价类进入第 \(i+1\) 个等价类后,再也不会回到编号更小的等价类,这样就可以保证最长上升路径的长度恰好是 \(k\)。这便是本题中我们构造的总体思路。

考虑得到一个具体的方案。首先当 \(k=n\),那就是说我们要将 \(n^2\) 个格子分成 \(n\) 组且两两不能到达。那么容易想到根据对角线来进行构造。更具体地,我们按照 \((i + j) \bmod n\) 来划分等价类,那么显然每个等价类中,每行、每列各有一个格子,是符合要求的。

再考虑 \(k = n^2\)(偶数)的情况。则此时一个格子就是一个等价类,可以从 \((1, 1)\) 出发,如图构造。图中只展示了这条路径的前半部分。

那么对于一般的 \(k\) 要如何构造呢?仍然考虑找到一条长度为 \(k\) 的路径,并且我们希望这条路径是最长的。那么我们可以先取出路径上的 \(k-n\) 个格子,让它们每个格子分别形成一个等价类。剩下的 \(n\) 个格子,按照 \(k = n\) 的方法划分等价类,以覆盖整个网格。

这么做需要满足一个条件,就是剩下的 \(n\) 个格子所属的等价类必须各不相同。因此我们从 \((1, 1)\) 出发,最开始走到的 \(n\) 个格子一定满足这些格子所对应得对角线能够覆盖整个网格!如图进行构造即可。红色的格子表示单独成为一个等价类的格子;不为红色的格子中,颜色相同的表示位于同一个等价类中。


那么这道题就做完了!时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
#define endl '\n'
#define N 706
using namespace std;
int n,k,ans[N][N],vis[N][N];
vector<int> col;
vector<pair<int,int> > g[N];
void solve()
{
  scanf("%d%d",&n,&k);
  if(k<n||n&1&&k>n*n-n+1)
    return printf("No\n"),(void)0;
  if(n==1)
    return printf("Yes\n1\n"),(void)0;
  for(int i=0;i<=n;i++)g[i].clear();
  for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)g[(i+j)%n].push_back({i,j}),ans[i][j]=vis[i][j]=0;
  int i=1,j=1,lst=-1,st=1,tot=0,c=k-n;
  for(col.clear();tot<k;tot++)
  {
    if(j==1&&lst==0) {
      i=st=(st+1)%n+1,lst=1;
      while(vis[i][j])i=i%n+1;
    } else if(lst==0)i=i%n+1,lst=1;
    else if(lst==1)j=j%n+1,lst=0;
    else lst=1;
    if(tot<n)col.push_back((i+j)%n);
    else ans[i][j]=c--;
    vis[i][j]=1;
  }
  reverse(col.begin(),col.end()),tot=k-n;
  for(int i:col)for(auto j:g[i])
    if(!ans[j.first][j.second])ans[j.first][j.second]=++tot;
  printf("Yes\n");
  for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)printf("%d%c",ans[i][j]," \n"[j==n]);
}
main()
{
  int T; scanf("%d",&T);
  while(T--)solve();
  return 0;
}
posted @ 2026-05-27 18:13  dyc2022  阅读(14)  评论(0)    收藏  举报
/* 设置动态特效 */ /* 设置文章评论功能 */ 返回顶端 levels of contents