(多校)子集 (subset)
第一次考场切构造题
首先我们摸一下样例
有一个显然的结论
如果我们依次从左到右把 \(n\) 个数分成 \(\frac{n}{k}\) 份
并且每一份大小都为 \(k\)
那么每一个子集在每一组数中都恰好选一个
\[1.2.3 : 4.5.6 : 7.8.9 : 10.11.12
\]
所以,我们不妨把序列表示成:
\[1.2.3 : 1.2.3 : 1.2.3 : 1.2.3
\]
转换成矩阵:
\[1-1-1-1
\]
\[2-2-2-2
\]
\[3-3-3-3
\]
那么题意就转换成让每一列上的数互换,求一种方案使得每一行的数字之和相等
样例这种情况很好想,我么只要把 \(\frac{n}{2k}\) 列的数字 \(reverse\) 一下就好了
这种情况其实是针对 \(n/k\) 为偶数的情况的
考虑 \(n/k\) 为奇数的情况
容易发现我们只需要大力手玩一下前三列的数字,使其每一行的数字和相等
对于剩下的 \(n/k-3\) 列的数字按 \(n/k\) 为偶数的情况做就好了
Code
#include <bits/stdc++.h>
#define re register
#define int long long
// #define lls long long
#define pir make_pair
#define fr first
#define sc second
#define db long double
using namespace std;
const int mol=998244353;
const int maxn=1e6+10;
const int INF=1e9+10;
inline int qpow(int a,int b) { int ans=1; while(b) { if(b&1) (ans*=a)%=mol; (a*=a)%=mol; b>>=1; } return ans; }
inline int read() {
int s=0,w=1; char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') { s=s*10+ch-'0'; ch=getchar(); }
return s*w;
}
int t,n,k,tot,fac[maxn];
vector<int>vec[maxn];
inline void wor() {
for(re int i=1;i<=tot;i++) vec[i].clear();
tot=n/k;
for(re int i=1;i<=tot;i++) for(re int j=1;j<=k;j++) vec[i].push_back(j);
if(tot&1) {
if(fac[k]*3%k!=0) { puts("No"); return ; }
int lim=fac[k]*3/k,fg=lim-1-k;
for(re int i=1;i<=k;i++) { vec[2][i-1]=fg; vec[3][i-1]=lim-i-fg; fg++; if(fg>k) fg=1; }
for(re int i=1;i<=(tot-3)/2;i++) reverse(vec[3+i].begin(),vec[3+i].end());
} else {
for(re int i=1;i<=tot/2;i++) reverse(vec[i].begin(),vec[i].end());
}
puts("Yes");
for(re int i=1;i<=k;i++) {
for(re int j=1;j<=tot;j++) printf("%lld ",(j-1)*k+vec[j][i-1]);
puts("");
}
}
signed main(void) {
freopen("erp.in","r",stdin); freopen("erp.out","w",stdout);
int lims=1e6;
for(re int i=1;i<=lims;i++) fac[i]=fac[i-1]+i;
t=read();
while(t--) {
n=read(); k=read();
if(n==1) { puts("Yes 1"); continue; }
if(n/k==1) { puts("No"); continue ; }
wor();
}
}