[刷题笔记] Luogu P9345 夕阳西下几时回
Problem
Description
给定一个整数\(n\),有一个数组\(a\)的内容是\(1,2,3\)……\(n\)。(不一定按照顺序排列,只保证内容)特别地,我们令\(a_{n+1}=1\)。
还有一个数组\(b\),满足\(b_i=gcd(a_i,a_{i+1})\) (\(gcd(a,b)\)为\(a\)和\(b\)的最大公约数)
给定一个整数\(k\),试构造出一种排列使得\(b\)数组中不同的数字数量为\(k\)个。如果不存在直接输出No即可,无需输出排列。
Analysis
结论题。
首先一个结论,\(b\)数组中最多有\(⌊\frac{n}{2}⌋\)不同的数字。
证明:由于\(\forall a_i \in[1,n]\)且内容不重复,\(a\)的大小为\(n\),因此对于\(\forall a_i,\forall a_j\)最大公约数最大为\(⌊\frac{n}{2}⌋\)。如果最大公约数超过\(⌊\frac{n}{2}⌋\)则一定不满足\(\forall a_i \in[1,n]\)。得证。
那么第一问就解决了,判断是否\(k\leq ⌊\frac{n}{2}⌋\)即可。
接下来如何构造呢?
我们先忽略有\(k\)个不同的数字,考虑如何最大化也就是有\(⌊\frac{n}{2}⌋\)个不同数字的时候如何构造。
由于\(gcd\)是取相邻的两数,我们肯定要尽可能的使一个数和她的倍数相邻。举个例子,如果\(n=6\)则\(1,2,4,3,6\)这样的构造方式显然是最优的。
到这里我们已经有了一个初步的思路,那么如何去重呢?也就是构造的时候不能出现重复的数字。
这很简单,我们可以先跑一遍2的整次幂,然后再枚举奇数的倍数进行构造。显然奇数的倍数一定不是二的整次幂。这样我们就实现了去重。
那么从奇数开始不断\(\times 2\)有没有可能重复呢?
显然不会。因为一个数字(除了刚开始的奇数)一定是由上一个数字数字\(\times 2\)扩散来的,而同理上一个数字一定是由上一个数字\(\times 2\)得到的......容易发现一个数字可以被哪个奇数扩散到是唯一的。所以不会重复。证毕。
其实到这里我们就已经可以解决CF的问题了:CF1858C
和Luogu重题了被CN选手骂但还是rated了,回复No comment/cf
接下来考虑构造\(k\)个不同的数字。
构造\(k\)个不同的数字我们只需要操作到\(k\times 2\)就可以了。至于在区间\([k\times 2,n]\),直接相邻构造即可,因为相邻数字的\(gcd=1\),不会影响答案。
至此,本题得解,代码如下:
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int T;
int main()
{
cin>>T;
while(T--)
{
int n,k;
scanf("%d%d",&n,&k);
if(k > n/2)
{
cout<<"No"<<endl;
continue;
}
cout<<"Yes"<<endl;
cout<<"1 ";
for(int i=n;i>=2*k+1;i--) cout<<i<<" "; //多余部分直接输出
for(int i=2;i<=2*k;i*=2) cout<<i<<" "; //构造2的整次幂
for(int i=3;i<=2*k;i+=2) //奇数构造
{
for(int j=i;j<=2*k;j*=2) cout<<j<<" ";
}
cout<<endl;
}
return 0;
}