题解:AT_agc077_b [AGC077B] Long Increasing Walk
更好的阅读体验
这应是我第一次独立做出黑题!这是一个思路和实现都比其他题解简单的做法。
我们需要解决这样一个问题:给一个左右各
首先如果见过 arc220d 的 trick 就会知道,完全图上的一条边可以看作网格图上的一个格子;完全二分图也可以。考虑一条完全二分图上的路径在网格上表现为什么:假设边
那么我们先考虑判断无解的问题。先考虑答案下界,我们声称,对于左右各
考虑假如给定了二分图上每条边的边权数组
a ,我们应该如何求出这个二分图的最长上升路径?假设
f_{i, j, 0/1} 表示以(i, j) 结束的路径,最后一步是横向/纵向移动,的最长上升路径。那么我们先将所有
f 赋值为-\infty 。然后按照a_{x, y} 从小到大遍历每个格子(x, y) 。
注意到
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 ,下文的构造将会给出验证。
接下来考虑上界。对于上界,我们可以先取出图中最长的一条路径(假设长度为
那么当
因此有解条件是,
接下来考虑构造。
首先我们考虑将
考虑得到一个具体的方案。首先当
再考虑
那么对于一般的
这么做需要满足一个条件,就是剩下的
那么这道题就做完了!时间复杂度
#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;
}