P12417 基础构造练习题 1 题解
非常有意思的一道题。
- 无解的情况:
首先感性理解当 \(n\) 为奇数时是无解的。具体的,我们先钦定这 \(n\) 个数都大于 1(因为题目要求对任意序列成立,所以证明某一类情况无解就可以)。
假设最后一次操作后 \(n\) 个数都变成了 \(x\),因为每次我们都选择两个数相乘,那么得到的一定比原来的数大,也就是说 \(x\) 一定是我们操作过程中得到的最大值。考虑每一次操作如果产生了新的最大值,最大值个数就会变成 2 个,如果产生的数是当前最大值,则最大值增加 2 个,否则不变。所以无论如何最大值的个数都不会是奇数个。
- \(O(n\log n)\) 步解法:
现在考虑如何构造方案。先考虑比较简单的 \(n=2^k\) 的情况,我们可以直接把 \(2^k\) 个数两两配对做一次操作,再将这 \(2^{k-1}\) 对两两分组,同组的两对一一对应做一次操作,那我们最后就可以操作 \(O(n\log n)\) 次解决问题。但是对于一般的 \(n\),分成两组可能会产生奇数个数,无解。我们可以单独留下两个,其他的两两分组,分治解决,所以问题变成了对于一个操作好了的序列,增加了两个数后怎么处理。我们可以先对增加的两个数做一次操作,那我们的序列将会形如:
接下来我们让前 \(n-4\) 个数依次和第 \(n-1\) 个数操作,就可以得到:
然后我们把后 4 个数单拎出来按上述 \(n=2^k\) 的情况操作,前 \(n-4\) 个数一前一后两两匹配做操作,就会发现它们操作出来是一样的!
我们把这两种方式结合到一起就可以 \(O(n\log n)\) 次解决一般情况了,可以得到六十多分的好成绩。接下来怎么优化呢?
- 正解(\(2n-1\) 步解法):
让我们先来观察一下上面我们加入两个数之后的第一步操作得到的序列:
我们发现这里可以成功,是因为前 \(n-4\) 个数中 \(x\) 因子数呈等差数列的形式。但是对于一般序列,我们不能保证前 \(n-2\) 个数相等,如果我们类似地操作最后不同的因子也不能像这样互补的合并啊。那加入还有一个一样的序列倒着拼过来貌似就可以互补了。
我们先构造两个一样的序列,直接把相邻两数组在一起操作一次就可以得到这样的序列(我们令 \(m=\frac n2\)):
现在我们把最后两个数单拎出来,剩下的数我们称奇数位置为第一组,偶数位置为第二组。按照刚才的思路我们让第一组从前往后和第 \(n-1\) 个数操作,第二组从后往前和第 \(n-1\) 个数操作,便于观察,这里我们取 \(n=12\),并用 \(a,b,c,d,e,f\) 分别表示 \(a_1,a_2,a_3,a_4,a_5,a_6\)。
第一组为:
第二组为:
然后我们试着把这两个序列首尾相接分别做操作:
比较可惜,我们这样凑出来的 5 对乘积并不相等,但好像比较有规律.直观感受一下问题出在了哪里,第1组每个数都有因子 \(a\),所以第二组也应该先让 \(a\) 做操作,这样错一下位就可以了。现在第二组为:
我们现在把这两组首尾相接相乘得到的数就都是 \(a^3b^2c^2d^3e^2f^2\),而最后两个一开始拎出来的数现在乘起来结果是 \(a^2b^2c^2d^2e^2f^2\)。差了一个 \(a\),怎么办呢?这里有一个技巧,我们可以一开始让两个 \(a\) 和 \(e\) 分别操作,得到 4 个 \(ae\) 并把前面两个 \(ae\) 当做新的 \(a\) 做上述操作,后面两个 \(ae\) 不管,到最后和最后两个数分别相乘就可以把那个 \(a\) 补上了。
现在让我们统计一下操作次数:转化为两个相同序列,\(\frac n2\)步;把 \(a\) 存下来,2 步;前 \(n-4\) 个数分别和第 \(n-1\) 个数操作,\(n-4\) 步;首尾相接合并,\(\frac {n-4}2\) 步;最后两个数相乘,1 步;最后两个数分别和一开始存下的 \(a\) 相乘,2 步。总共 \(\frac n2+2+(n-4)+\frac{n-4}2+2+1=2n-1\) 步。
code:
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e4;
int ans[N][2],n,res;
inline ll rd()
{
char c;ll f=1;
while((c=getchar())<'0'||c>'9')if(c=='-')f=-1;
ll x=c-'0';
while(('0'<=(c=getchar()))&&c<='9')x=x*10+(c^48);
return x*f;
}
int main()
{
for(int t=rd();t--;)
{
n=rd(),res=0;
if(n%2==1)printf("0\n");
else if(n==2)printf("1\n1\n1 2\n");
else if(n==4)printf("1\n4\n1 2\n3 4\n1 3\n2 4\n");
else if(n==6)printf("1\n11\n1 2\n3 4\n1 3\n2 4\n5 6\n1 5\n2 5\n1 2\n5 6\n3 5\n4 6\n");
else
{
for(int i=1;i<=n;i+=2) ans[++res][0]=i,ans[res][1]=i+1;
ans[++res][0]=1,ans[res][1]=n-2;
ans[++res][0]=2,ans[res][1]=n-3;
for(int i=1;i<=n-5;i+=2) ans[++res][0]=i,ans[res][1]=n-1;
ans[++res][0]=2,ans[res][1]=n-1;
for(int i=n-4;i>=4;i-=2) ans[++res][0]=i,ans[res][1]=n-1;
for(int i=1;i<=n-7;i+=2) ans[++res][0]=i,ans[res][1]=i+3;
ans[++res][0]=2,ans[res][1]=n-5;
ans[++res][0]=n,ans[res][1]=n-1;
ans[++res][0]=n-3,ans[res][1]=n-1;
ans[++res][0]=n-2,ans[res][1]=n;
printf("1\n%d\n",res);
for(int i=1;i<=res;i++) printf("%d %d\n",ans[i][0],ans[i][1]);
}
}
return 0;
}
/*
这玩意叫基础构造练习题
那我的两个小时算什么
nlogn步不是随便做
貌似有63分
但是2n-1真能做吗
不会
看题解
……
2n-1步好牛牛的一个做法
感觉好像看懂题解了
……
真吗??
……
最后留两个数啥意思
神秘……
瞟一眼代码似乎懂了
有点意思
怎么不对
现在精神状态良好
第一次在代码里发癫
调不出来欸
算了按题解的写法吧保险点
过了
好题啊
推荐大家都来做这道题
*/

浙公网安备 33010602011771号