[题解]2026杭电DEBUG杯完整题解
2026 DEBUG 杯程序设计大赛题解
1001:友好数
难度:简单 ,出题人:潘俊龙
【思考过程】
题目是解方程 \(y-d(y)=x\)。第一反应可把 \(y\) 挪到右边得到 \(y=x+d(y)\),于是候选 \(y\) 一定在 \(x\) 附近,而不是全范围乱枚举。
【方法一:小区间枚举】
对本题数据,\(y\) 位数最多 10 位,数位和最多 90,所以 \(y\in[x,x+90]\)。实作时枚举 \([x,x+100]\) 更稳妥。
逐个计算 \(d(y)\),若 \(y-d(y)=x\) 则计数。
时间复杂度 \(O(100\times 10)\),可视为单组 \(O(1)\)。
【方法二:按数位和反推】
也可枚举可能的数位和 \(s\),令 \(y=x+s\),再检查 \(d(y)=s\)。
该法本质与方法一等价,但枚举对象从 \(y\) 变成 \(s\),实现上同样简单。
C++ Solution
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int getsum(int x){
int s=0;
while(x){
s+=x%10;
x/=10;
}
return s;
}
void solve(){
int x;
cin>>x;
int ans=0;
for(int y=x;y<=x+100;y++){
if(y-getsum(y)==x) ans++;
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;cin>>T;
while(T--) solve();
return 0;
}
1002:星域导航
难度:简单,出题人:林苏阳
题解
【思考过程】
在树中,任意两个节点之间的路径唯一。
如果一条路径上的所有节点颜色都相同,那么这两个节点一定处在同一个 同色连通块 中。
所谓同色连通块,就是:
- 连通块内所有节点颜色相同;
- 连通块内节点之间通过颜色相同的边连接。
对于一个大小为 s 的同色连通块,其中任意两个不同节点之间的路径都只会经过这个连通块中的节点,因此都满足条件。
所以这个连通块对答案的贡献为:
最终答案就是所有同色连通块贡献之和。
【方法一:并查集按边合并】
遍历每条边 \((u,v)\):若 \(c_u=c_v\) 就合并。
每个连通块大小为 \(s\),贡献 \(\binom{s}{2}=s(s-1)/2\),累加即答案。
时间复杂度 \(O(n\alpha(n))\),空间复杂度 \(O(n)\)。
【方法二:DFS/BFS 连通块统计】
只在“同色边”上建图,再 DFS/BFS 求每个连通块大小。
总复杂度 \(O(n)\),常数略大于并查集,但同样可过。
C++ Solution(并查集)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int N=2e5+10;
int c[N];
int f[N];
int sz[N];
int find(int x){
if(f[x]!=x){
f[x]=find(f[x]);
}return f[x];
}
void unionn(int x,int y){
int fx=find(x),fy=find(y);
if(fx!=fy&&c[fx]==c[fy]){
sz[fy]+=sz[fx];
f[fx]=fy;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>c[i];
f[i]=i;
sz[i]=1;
}
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
unionn(u,v);
}
int ans=0;
for(int i=1;i<=n;i++){
if(f[i]==i){
ans+=sz[i]*(sz[i]-1)/2;
}
}cout<<ans<<endl;
}
1003:洗盘子
难度:中等,出题人:张景杰
【题目大意】
A可以用a分钟洗盘子+装菜,可以用c分钟用干净盘子装菜
B可以用b分钟洗盘子
求n分钟内,A最多装几盘菜
【考点】数学,思维
【性质分析】
- B肯定要一直洗盘子
- A要进行 \(a\) 分钟或者 \(c\) 分钟的决策
- 分三种情况
-
- \(a \le c\),那么 A 肯定选 \(a\) 分钟的独自做就行了,答案是 \(\left\lfloor\frac{n}{a}\right\rfloor\)。
-
- \(b < c < a\),A 肯定优先用 \(c\) 分钟去装菜,而他最早能在第 b 分钟开始装菜,因为洗盘子所需要的时间小于状态,所以他可以一直装菜直到结束,能够装 \(\left\lfloor\frac{n-b}{c}\right\rfloor=num\) 盘,而空闲出来的时间可以用 a 分钟来状态,可以装 \(\left\lfloor\frac{n-num\cdot c}{a}\right\rfloor\) 盘,两者相加就是答案。
-
- \(b < a < c\),因为洗盘子比装菜快,所以最后一个洗出来的盘子是没时间拿来装菜的,那么能够装 \(\left\lfloor\frac{n-c}{b}\right\rfloor=num\) 盘,而空闲出来的时间可以用 a 分钟来状态,可以装 \(\left\lfloor\frac{n-num\cdot c}{a}\right\rfloor\),两者相加就是答案。
【时间复杂度】
- \(b < a < c\),因为洗盘子比装菜快,所以最后一个洗出来的盘子是没时间拿来装菜的,那么能够装 \(\left\lfloor\frac{n-c}{b}\right\rfloor=num\) 盘,而空闲出来的时间可以用 a 分钟来状态,可以装 \(\left\lfloor\frac{n-num\cdot c}{a}\right\rfloor\),两者相加就是答案。
- \(O(1)\)
【代码实现(c++)】
#include<bits/stdc++.h>
using namespace std;
long long a,b,c;
void solve(){
cin>>n>>a>>b>>c;
long long num=min((n-c)/b,(n-b)/c);
cout<<max(num+(n-num*c)/a,n/a);
return ;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
1004:回转01串
难度:难 , 出题人:潘俊龙
【思考过程】
原操作“看两侧是否相等再翻中间”表面看像是在直接翻位,但更关键的是:一次操作只会影响与位置 i 相邻的“相邻关系”,并不会对整串产生任意变化。
因此先找不变量:首尾位永远不会改变;接着把难点从“翻转某一位”改写成“相邻关系怎么变”,这样才可能落到可计算模型上。
于是思路链是:
- 用差分 \(D_i=A_i\oplus A_{i+1}\) 记录相邻是否相同;
- 条件 \(A_{i-1}=A_{i+1}\) 等价于 \(D_{i-1}=D_i\),而翻转 \(A_i\) 会让这两段差分同时取反;
- 再做奇偶变换 \(C_i=D_i\oplus(i\bmod 2)\),把“同时取反”转化为在 \(C\) 上的相邻交换
01 <-> 10; - 相邻交换的最小次数就变成经典问题:
1的相对顺序不变,最优代价由匹配到目标中第 \(j\) 个1的位置差给出。
【方法一:差分 + 奇偶变换 + 相邻交换模型】
- 先定义差分串 \(D_i=A_i\oplus A_{i+1}\);
- 再定义 \(C_i=D_i\oplus(i\bmod 2)\);
- 可证明一次合法操作等价于在 \(C\) 上做一次相邻交换
01 <-> 10。
于是最少操作数转化为:把 \(C_A\) 变成 \(C_B\) 的最小相邻交换次数。
若两串1个数不同则无解;否则答案是对应第 \(j\) 个1的位置差绝对值之和。
时间复杂度 \(O(n)\)。
C++ Solution
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int N=1e6+5;
int posa[N],posb[N];
void solve(){
int n;cin>>n;
string a,b;cin>>a>>b;
if(a[0]!=b[0]||a[n-1]!=b[n-1]){
cout<<-1<<endl;
return;
}
int cnta=0,cntb=0;
for(int i=1;i<n;i++){
int da=(a[i-1]!=a[i]),db=(b[i-1]!=b[i]);
int ca=da^(i&1),cb=db^(i&1);
if(ca) posa[++cnta]=i;
if(cb) posb[++cntb]=i;
}
if(cnta!=cntb){cout<<-1<<endl;return;}
int ans=0;
for(int i=1;i<=cnta;i++) ans+=abs(posa[i]-posb[i]);
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;cin>>t;
while(t--) solve();
return 0;
}
【方法二:最近合法点 + 左推贪心】
这道题除了把操作转成差分串上的相邻交换以外,还可以直接从原操作本身出发做贪心。
一次操作选择位置 i,要求两侧相等,即 A[i-1]=A[i+1],然后翻转 A[i]。
也就是说,如果用一个位置 x=i+1 来表示“中心为 i 的操作是否合法”,那么合法条件就是:
我们从左到右考虑。首尾字符永远不会被操作改变,所以如果首尾不同,一定无解。
之后假设已经把 1..i-1 都修好了,那么后面无论怎么操作,都不能再改这些位置。因此当前第 i 位如果和目标不同,就必须让位置 i 被翻转一次。
关键在于:位置 i 能不能翻,取决于 i 这个中心是否合法,也就是 x=i+1 是否是合法点。
如果 i+1 本来不合法,但更右边有一个最近合法点 x,那么可以从右往左依次操作:
因为中间所有合法点都不存在,所以每向左翻一次,都会把左边相邻的非法状态变成合法状态。
这一串操作的效果就是把区间 [i,x-1] 整体翻转一次,花费 x-i 次操作,并且刚好能把第 i 位修正。
所以每次遇到当前位置不匹配时,都应该找当前最靠左的合法点 x。
如果找不到,就无法再翻转当前位置,答案为 -1;如果找到了,代价就是 x-i,并用差分懒标记记录区间 [i,x-1] 被整体翻转。
- 首先判断首尾是否相同,因为首尾不会被任何操作改变;
- 用集合
s维护当前所有合法点x,其中2<=x<n且A[x]=A[x-2]; - 从左到右扫描每一位
i:- 用懒标记落实当前位置是否被之前的区间翻转影响;
- 如果当前位已经等于目标位,直接跳过;
- 否则必须翻转当前位,找到最小合法点
x>=i+1; - 若不存在,则无解;
- 否则执行“从
x-1左推到i”这一段操作,答案加x-i,并记录区间[i,x-1]翻转。
- 一次区间翻转只会影响右边界附近新的合法性,因此只需要更新
x+1这个合法点。
为什么贪心正确:
当扫描到第 i 位时,前面的位已经固定,后面不能再通过操作影响它。
如果 A[i]!=B[i],那么中心 i 必须被操作一次。要让中心 i 合法,只能从右侧某个已经合法的位置一路向左传递。设当前最近的合法点是 x,那么至少需要经过 x-1,x-2,...,i,代价至少是 x-i;而上述操作恰好能用 x-i 次完成,所以这是当前最优选择。每一步都固定一个位置,因此整体最优。
C++ Solution
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int INF=(1LL<<60);
void solve(){
int n;cin>>n;
string a,b;cin>>a>>b;
if(a[0]!=b[0]||a[n-1]!=b[n-1]){
cout<<-1<<endl;
return;
}
set<int>s;
s.insert(INF);
for(int i=2;i<n;i++){
if(a[i]==a[i-2]) s.insert(i);
}
vector<int>tag(n+2,0);
int ans=0;
for(int i=1;i<n;i++){
while(*s.begin()<i+1) s.erase(s.begin());
tag[i]^=tag[i-1];
if(tag[i]) a[i]^=1;
if(a[i]==b[i]) continue;
int x=*s.begin();
if(x==INF){
cout<<-1<<endl;
return;
}
ans+=x-i;
tag[i]^=1;
tag[x]^=1;
s.erase(x);
if(x+1<n){
if(a[x-1]!=a[x+1]) s.insert(x+1);
else s.erase(x+1);
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;cin>>T;
while(T--) solve();
return 0;
}
1005:被四整除
难度:中等 , 出题人:潘俊龙
【思考过程】
关键不是“会不会死一次”,而是“是否能进入循环”。只要某次触发回满,系统就回到初始状态,后续无限重复。
【方法一:有限步枚举】
首次回满前,第 \(t\) 次受击后血量为 \(hp_t=n-td\)。
问题等价于是否存在 \(t\ge 1\) 使:
若存在这样的 \(t\),则回满后状态重回初始,之后必无限重复,输出 YES;否则最终降到 \(\le 0\),输出 NO。
把第二个条件写成同余形式:
设 \(g=\gcd(d,m)\)。同余存在最小正解且不超过 \(m/g\)(因此不超过 \(m\)),所以只需枚举 \(t=1,\ldots,\min\left(m,\left\lfloor\frac{n-1}{d}\right\rfloor\right)\) 即可。
单组复杂度 \(O(m)\)(本题 \(m\le 100\))。
(题目数据中的大数据不小心全部造成有限步内一定能满足 hp % m == 0 的情况了,实际大部分赛时代码都会 TLE。)
【方法二:数论判定(线性同余 + 模逆)】
触发回满等价于存在 \(t\ge 1\) 满足:
线性同余定理:令 \(g=\gcd(d,m)\),同余有解当且仅当 \(g\mid n\);否则直接 NO。
若 \(g\mid n\),令:
因 \(\gcd(d',m')=1\),可在模 \(m'\) 下求逆元 \(inv\equiv (d')^{-1}\pmod{m'}\),从而最小非负解满足:
此时最小正解为 \(t_{\min}=\begin{cases}r,&r>0\\ m',&r=0\end{cases}\)。只需检查 \(t_{\min}\le \left\lfloor\frac{n-1}{d}\right\rfloor\) 即可输出 YES/NO(逆元用扩展欧几里得求)。
C++ Solution
方法一代码:有限步枚举
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
void solve(){
int n,d,m;
cin>>n>>d>>m;
int lim=(n-1)/d;
int mx=min(m,lim);
for(int t=1;t<=mx;t++){
int hp=n-t*d;
if(hp>0&&hp%m==0){
cout<<"YES"<<endl;
return;
}
}
cout<<"NO"<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T=1;
cin>>T;
while(T--) solve();
return 0;
}
方法二代码:数论判定(线性同余 + 模逆)
#include <bits/stdc++.h>
using namespace std;
#define int long long
int extgcd(int a,int b,int &x,int &y){
if(!b){x=1;y=0;return a;}
long long x1,y1;
long long g=extgcd(b,a%b,x1,y1);
x=y1;
y=x1-a/b*y1;
return g;
}
int inv(int a,int mod){
int x,y;
extgcd(a,mod,x,y);
x%=mod;
if(x<0) x+=mod;
return x;
}
void solve(){
int n,d,m;cin>>n>>d>>m;
int g=std::gcd(d,m);
if(n%g){cout<<"NO\n";return;}
int d1=d/g,m1=m/g,n1=n/g;
int r=(__int128)n1*inv(d1%m1,m1)%m1; //r∈[0,m1-1]
int tmin=r?r:m1 ,lim=(n-1)/d;
cout<<(tmin<=lim?"YES\n":"NO\n");
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;cin>>T;
while(T--) solve();
return 0;
}
1006:别让厕纸被用完了!
难度:中等 ,出题人:方凯新
【思考过程】
两次补纸之间可以看成一个长度为 k 的周期。由于每次补纸都会把纸盒补满到 m,所以只要保证每个周期内的总耗纸量不超过 m 即可。
关键在于:只需要检查第一个周期 [1,k]。
第 i 个会员来的时间是 1, 1+aᵢ, 1+2aᵢ, ...,在任意连续 k 天中,他最多来 ceil(k/aᵢ) 次,而第一个周期 [1,k] 中正好就是这个次数。
因此第一个周期一定是最坏情况。
于是判断一个 k 是否可行,只需检查:
sum(bᵢ * ceil(k/aᵢ)) <= m
随着 k 变大,每个 ceil(k/aᵢ) 都不会变小,所以总耗纸量单调不下降,可以二分最大合法的 k。
【方法:二分答案】
设 check(k) 表示周期长度为 k 时是否够用。
第 i 个会员在第一个周期 [1,k] 中来的次数为:
(k+aᵢ-1)/aᵢ
这里是整数除法。
如果所有会员的总消耗不超过 m,则 k 合法,否则不合法。
二分上界可以由单个会员限制得到。对于任意会员 i,必须满足:
bᵢ * ceil(k/aᵢ) <= m
所以有:
k <= aᵢ * floor(m/bᵢ)
对所有会员取最小值,作为二分右边界。
如果 k=1 都不合法,说明第 1 天所有人一起来时纸都不够,直接输出 0。
时间复杂度 \(O(n \log V)\),其中 \(V\) 为二分上界。
C++ Solution
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
using i128 = __int128_t;
void solve(){
int n,m;
cin>>n>>m;
vector<int>a(n+1),b(n+1);
int r=LLONG_MAX;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i];
r=min(r,a[i]*(m/b[i]));
}
auto check = [&](int k)->bool{
i128 sum=0;
for(int i=1;i<=n;i++){
int cnt=(k+a[i]-1)/a[i];
sum+=(i128)cnt*b[i];
if(sum>m) return false;
}
return sum<=m;
};
if(!check(1)){
cout<<0<<endl;
return;
}
int l=1;
while(l<r){
int mid=l+(r-l+1)/2;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--) solve();
return 0;
}
1007:寻找隐藏暗号
难度:中等 ,出题人:林苏阳
【题目大意】
给定两个字符串 S 和 T。
需要统计 S 的所有非空子串中,有多少个子串包含 T 作为子序列。
注意:子串必须是连续的一段;子序列不要求连续,只要求字符相对顺序不变;即使两个子串内容相同,只要起止位置不同,也要分别计数。
【思路】
设:\(n = |S|,m = |T|\)。由于 |T| <= 50,可以考虑动态规划。
我们从左到右扫描字符串 S,维护当前已经匹配到 T 的哪些位置。
状态定义:
定义数组 \(dp\):表示当前扫描到某个位置时,有多少个子串已经匹配了 \(T\) 的前 \(i\) 个字符。
也就是说:
\(dp[0]\) 表示还没有匹配到 \(T\) 中任何字符的子串数量;
\(dp[1]\) 表示已经匹配了 \(T[0]\) 的子串数量;
\(dp[2]\) 表示已经匹配了 \(T[0..1]\) 的子串数量;
...
\(dp[m]\) 表示已经完整匹配了整个 \(T\) 的子串数量。
状态转移:
每扫描到 \(S\) 中的一个字符 \(s\),都可以新开一个以当前字符为结尾的子串,所以:\(dp[0]++;\)
然后枚举 \(T\)中的位置。
如果当前字符 s 可以匹配 T[i],那么原来已经匹配了前 i 个字符的子串,现在就可以进一步匹配到前 i + 1 个字符:
\(dp[i + 1] += dp[i];\)
\(dp[i] = 0;\)
答案统计:
每处理完一个字符后,\(dp[m]\) 表示当前已经完整匹配了 \(T\) 的子串数量。
这些子串都可以作为合法答案,因此:\(ans += dp[m];\)
最终输出 \(ans\)即可。
【复杂度分析】
设 \(n = |S|\),\(m = |T|\)。
每个字符最多枚举一次 \(T\),所以时间复杂度为:\(O(nm)\)
由于 \(m \le 50\),可以通过本题。
空间复杂度为:\(O(m)\)
C++ Solution
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
void solve(){
string S,t;cin>>S>>t;
int m=t.size();
vector<int>dp(m+1,0);
int ans=0;
for(char s:S){
//新开一个以当前字符为起点的子串
dp[0]++;
//倒序枚举
for(int i=m-1;i>=0;i--){
if(t[i]==s){
dp[i+1]+=dp[i];
dp[i]=0;
}
}
ans+=dp[m];
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int t=1;cin>>t;
while(t--){solve();}
}
1008:论坛热度监控
难度:中等 出题人:潘俊龙
【思考过程】
三个操作里最难的是“区间内最左满足阈值位置”,它要求维护值域信息并支持快速向左优先搜索,直接指向线段树。
【方法一:懒标记线段树】
维护每个结点:
mx:区间最大值;tag:区间加懒标记。
操作实现:
- 区间加:标准 lazy add;
- 区间最大值:标准 query;
- 找最左位置:若当前结点
mx<k直接剪枝,否则先递归左子树,再递归右子树。
单次 \(O(\log n)\),总复杂度 \(O(m\log n)\)。
C++ Solution
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define INF 0x3f3f3f3f3f3f3f3f
#define rep(i,j,k) for(int i=j;i<=k;++i)
#define per(i,j,k) for(int i=j;i>=k;--i)
const int N=2e5+5;
int n,m;
int a[N];
struct node{
int mx,tag;
}tr[N<<2];
void pushup(int x){
tr[x].mx=max(tr[x<<1].mx,tr[x<<1|1].mx);
}
void addtag(int x,int v){
tr[x].mx+=v;
tr[x].tag+=v;
}
void pushdown(int x){
if(!tr[x].tag) return;
addtag(x<<1,tr[x].tag);
addtag(x<<1|1,tr[x].tag);
tr[x].tag=0;
}
void build(int x,int l,int r){
tr[x].tag=0;
if(l==r){
tr[x].mx=a[l];
return;
}
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
pushup(x);
}
void modify(int x,int l,int r,int L,int R,int v){
if(L<=l&&r<=R){
addtag(x,v);
return;
}
pushdown(x);
int mid=(l+r)>>1;
if(L<=mid) modify(x<<1,l,mid,L,R,v);
if(R>mid) modify(x<<1|1,mid+1,r,L,R,v);
pushup(x);
}
int querymx(int x,int l,int r,int L,int R){
if(L<=l&&r<=R) return tr[x].mx;
pushdown(x);
int mid=(l+r)>>1;
int ans=-INF;
if(L<=mid) ans=max(ans,querymx(x<<1,l,mid,L,R));
if(R>mid) ans=max(ans,querymx(x<<1|1,mid+1,r,L,R));
return ans;
}
int querypos(int x,int l,int r,int L,int R,int k){
if(r<L||R<l) return -1;
if(tr[x].mx<k) return -1;
if(l==r) return l;
pushdown(x);
int mid=(l+r)>>1;
int res=-1;
if(L<=mid) res=querypos(x<<1,l,mid,L,R,k);
if(res!=-1) return res;
if(R>mid) res=querypos(x<<1|1,mid+1,r,L,R,k);
return res;
}
void solve(){
cin>>n>>m;
rep(i,1,n) cin>>a[i];
build(1,1,n);
while(m--){
int op; cin>>op;
if(op==1){
int l,r,x; cin>>l>>r>>x;
modify(1,1,n,l,r,x);
}
else if(op==2){
int l,r; cin>>l>>r;
cout<<querymx(1,1,n,l,r)<<endl;
}
else{
int l,r,k; cin>>l>>r>>k;
cout<<querypos(1,1,n,l,r,k)<<endl;
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
1009:旋转方阵
难度:中等 , 出题人:张景杰
【题目大意】旋转一个方阵,开始和每次旋转会让方阵中的数向下掉落,求反转k次后的方阵。
【思考过程】模拟矩阵旋转操作,
步骤1:先处理最开始的下落,
步骤2:让矩阵顺时针旋转,然后下落,重复k次。
【复杂度分析】每次下落和旋转都是 \(O(n^2)\),
要进行k次模拟下落和旋转,
综上时间复杂度为 \(O(n^2 k)\)。
C++ Solution
#include<bits/stdc++.h>
using namespace std;
int n,k;
int g[507][507];
int c[507][507];
int gc[507][507];
void shun(){//顺时针旋转矩阵
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
c[i][j]=g[n-j+1][i];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]=c[i][j];
}
}
return ;
}
void move(){//下落
for(int i=1;i<=n;i++){
int num=0;
for(int j=n;j>=1;j--){
if(g[i][j]==0)num++;
else break;
}
for(int j=1;j<=n;j++){
if(j<=num)c[i][j]=0;
else c[i][j]=g[i][j-num];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]=c[i][j];
}
}
return ;
}
void firstmove(){//第一次下落
for(int i=1;i<=n;i++){
for(int j=n;j>=1;j--){
if(g[i][j]!=0){
for(int k=j+1;k<=n;k++){
if(g[i][k]==0)swap(g[i][k],g[i][k-1]);
}
}
}
}
return ;
}
void print(){//打印答案
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(g[i][j]==0)cout<<". ";
else cout<<g[i][j]<<" ";
}cout<<"\n";
}
return ;
}
void solve()
{
cin>>n>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
string s;
cin>>s;
if(s==".")g[i][j]=0;
else g[i][j]=stoll(s);
gc[i][j]=g[i][j];
}
}
if(k==0){
print();
return ;
}
firstmove();
shun();
k--;
if(k==0){
print();
return ;
}
while(k--){
move();
shun();
}
print();
return ;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int QAQ=1;cin>>QAQ;
while(QAQ--) solve();
return 0;
}

浙公网安备 33010602011771号