传送门:1948E
标签:构造
题目大意
给出两个整数,n 和 k。存在一个有 n 个顶点的图,顶点编号从 1 到 n,初始状态下没有边。你必须给每个顶点分配一个整数;令 ai 为顶点 i 上的整数。所有的 ai 应该是从 1 到 n 的互不相同的整数。分配完整数后,对于每一对顶点 (i, j),如果 |i−j|+|ai−aj|≤k 成立,就在它们之间添加一条边。要求你创建一个图,它可以被分割成尽可能少的完全图(cliques)。图中的每个顶点应该恰好属于一个完全图。
输入:第一行包含一个整数 t (1≤t≤1600) — 测试用例的数量。每个测试用例由一行组成,包含两个整数 n 和 k (2≤n≤40; 1≤k≤2n)。
输出
对于每个测试用例,输出三行:
第一行包含 n 个互不相同的整数 a1, a2, …, an (1≤ai≤n);
第二行包含一个整数 q (1≤q≤n) ;
第三行包含 n 个整数 c1, c2, …, cn (1≤ci≤q)
算法分析
- 我们需要两步来解决本题:一是分析最大团的大小;二是展示一种构造方法,总是允许我们获得最大可能尺寸的团。
- 首先我们分析最大团的大小。显然,最大团的大小不能超过k。如果同一个团中有至少k+1个顶点,则至少其中两个顶点(i 和 j)之间的绝对差值大于等于k。由于a_i不等于a_j,那么|a_i - a_j|大于等于1。所以 |i-j| + |a_i - a_j| 至少为 k+1,所以这两个顶点之间不会有边相连(并且不能属于同一个团)。
- 然后我们要找到一种构造方法,始终允许我们创建k大小的团。尝试解决k=n的问题;如果n > k,我们可以将所有顶点划分为⌈ n/k⌉个团如下:对每个团分配连续的顶点块和要分配给它们的数字(例如,顶点1到k和数字1到k属于第一个团,顶点k+1到2k和数字k+1到2kn属于第二个团),然后在这些块上使用n=k的解决方案。
- 为了获得n=k的解决方案,我们可以尝试本地暴力破解,比如n≤10,并分析结果。那么其中的构造方法如下:令m = ⌈ n/2⌉; 将所有顶点和数字1到k分成两部分:[1,m]和[m+1,k]; 然后,在每个部分中,顶点索引越大,它得到的整数越小。所以看起来就是:a_1 = m, a_2 = m - 1, …, a_m = 1, a_{m+1} = n, a_{m+2} = n - 1, …, a_n = m + 1. 我们可以证明,任何两个不同半部分的顶点之间的“距离”正好为k,同一半部分内的任何两个顶点之间的距离不超过2(m-1),它永远不会超过k。
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n,k,ans[80];
signed main(){
int t; cin>>t;
while (t--){
cin>>n>>k;
int x=(n+k-1)/k,id=0,now=0;
for (int i=1; i<=n; i+=k){
int cnt,x=1;
if (i+k>n){
cnt=n-i+1;
}
else cnt=k;
int mid=(cnt-1)/2;
for (int j=0; j<cnt; j++){
ans[i+j]=id+(mid-j-1);
if (ans[i+j]<id) ans[i+j]+=cnt;
}
id+=cnt;
}
for (int i=1; i<=n; i++)
cout<<ans[i]+1<<" ";
cout<<"\n"<<x<<"\n";
for (int i=1; i<=n; i++){
if ((i-1)%k==0) now++;
cout<<now<<" ";
}
cout<<"\n";
}
return 0;
}