【题解】CF1336D 题解

CF1336D 题解

思路分析

推式子的题,细节多。

为方便后续表述,下面定义一些东西:

  • \(a_i\) 代表在初始的集合 \(S\)\(i\) 的个数。
  • “三同”代表原题中的 \(\text{triplet}\)
  • “三连“代表原题中的 \(\text{straight}\)

采用兔队的构造方法,膜拜兔队!

STEP 1:分析插入后三同的变化量

三同的计算公式就是从每个数中任意选三个出来。

也就是下面这个式子:

\[\text{C}^3_{x_1}+\text{C}^3_{x_2}+\text{C}^3_{x_3}+\ldots+\text{C}^3_{x_n} \]

其中 \(x\) 表示当前的集合中,每个数的个数。

假设插的是 \(i\)

由于只有 \(x_i\) 加了 \(1\),因此变化量为 \(\text{C}^3_{x_i+1}-\text{C}^3_{x_i}\)

推导一下式子:

\[\begin{aligned}\text{C}^3_{x_i+1}-\text{C}^3_{x_i} & = \dfrac{(x_i+1)x_i(x_i-1)}{6}-\dfrac{x_i(x_i-1)(x_i-2)}{6} \\ & = \dfrac{x_i(x_i-1)[x_i+1-(x_i-2)]}{6} \\ & = \dfrac{3x_i(x_i-1)}{6} \\ & = \dfrac{x_i(x_i-1)}{2} \\ & = C_{x_i}^2 \end{aligned} \]

于是有如下结论:

  • \(a_i\) 大于等于二时,那么就可以往里面插入一个 \(i\),然后根据变化量来计算具体的值。

  • 当然,此时如果 \(a_i\) 小于二,那么插入后这个变化量就是零。此时无法确定精确的值。

无论如何,我们都要往 \(1\)\(n-1\) 里各插入一个。

那么为什么不往 \(n\) 里面插入呢?这就涉及到三连的问题了。因为到此时,我们还没使用三连的变化量。

STEP 2:分析插入后三连的变化量

还是假设我们要插 \(i\)

根据上述结论,我们可以肯定,\(1\)\(i - 1\) 都插了一个。

然后就可以开始推式子了,这里为了方便,以 \(n=7\),插入 \(5\) 为例。

原来的三连:

\[(a_1+1)(a_2+1)(a_3+1)+(a_2+1)(a_3+1)(a_4+1)+(a_3+1)(a_4+1)a_5+(a_4+1)a_5a_6+a_5a_6a_7 \]

插入 \(5\) 后:

\[(a_1+1)(a_2+1)(a_3+1)+(a_2+1)(a_3+1)(a_4+1)+(a_3+1)(a_4+1)(a_5+1)+(a_4+1)(a_5+1)a_6+(a_5+1)a_6a_7 \]

注意观察含有 \(a_5+1\) 的那几项,于是我们可以算出变化量为:

\[(a_3+1)(a_4+1)+(a_4+1)a_6+a_6a_7 \]

一般化:

\[(a_{i-2}+1)(a_{i-1}+1)+(a_{i-1}+1)a_{i+1}+a_{i+1}a_{i+2} \]

那么我们只要知道了 \(1\)\(i-1\) 的所有 \(a\) 的值,根据前面三连的变化量,即可算出 \(a_i\)。注意 \(i\) 必须大于等于 \(4\)

那么我们就可以回答上面的问题了:\(n\) 可以根据三连的变化量推出来,不用单独询问计算。

怎么推?非常简单,我们直接看 \(n-1\) 的三连变化量就可以了,它的值为:

\[(a_{n-3}+1)(a_{n-2}+1)+(a_{n-2}+1)a_n \]

直接算就可以了。

那么这里又引出了另一个问题,万一有值无法确定怎么办?

STEP 3:根据三连的变化量确定小于二的值

假设 \(a_i\) 无法根据三同确定。

参考推 \(n\) 的方法,去看 \(i-1\) 的三连变化量。

它的变化量为:

\[(a_{i-3}+1)(a_{i-2}+1)+a_i(a_{i-2}+1+a_{i+1}) \]

注意这时候我们还有个条件就是 \(a_i<2\),因为只有这样才无法根据三同确定。

假设 \(i-1\) 的三连变化量为 \(g\),我们可以分类讨论:

  • 如果 \(g-(a_{i-3}+1)(a_{i-2}+1)\) 不为零,说明 \(a_i\) 一定不是零,这样 \(a_i\) 就只能是一。
  • 如果 \(g-(a_{i-3}+1)(a_{i-2}+1)\) 为零,由于 \(a_{i-2}\geq 0\)\(a_{i+1}\geq 0\),所以 \(a_{i-2}+1+a_{i+1}\geq 1\)。因此 \(a_i\) 为零。

这样我们就把小于二的值给确定了。

但是需要注意,上述结论仅在 \(i\geq 4\) 时成立。

这里又引出一个问题:\(i\leq 3\) 时怎么确定?

STEP 4:确定余下的值

基本上做完了,但是我们还需要确定出 \(i \leq 3\) 时,小于二的 \(a_i\) 的值。

怎么算?这里我直接给出结论:再插一个 \(1\)

根据三同的变化量公式,此时我们必然能确定 \(a_1\) 的值,无论它的大小是多少。

那么我们就去考虑 \(a_2\)\(a_3\)

注意到再插入一个 \(1\) 之后,他三连的变化量为 \((a_2+1)(a_3+1)\)

同时我们还知道第一次插 \(1\) 的时候,三连的变化量为 \(a_2a_3\)

于是根据上面两个变化量,我们可以算出 \(a_2+a_3\) 的值,不妨设其为 \(t\)

那么现在,如果在 \(a_2\)\(a_3\) 中,有一个可以根据三同确定,那另一个也就确定了,所以我们就只用考虑 \(a_2\)\(a_3\) 均小于二的情况。

分类:

  • \(t=0\),都是 \(0\)
  • \(t=2\),都是 \(1\)
  • \(t=1\),看插 \(2\) 的三连变化量,为 \((a_1+1)a_3+a_3a_4=a_3(a_1+a_4+1)\)。那么如果变化量为 \(0\),由于 \(a_{1}\geq 0\)\(a_{4}\geq 0\),那么 \(a_1+a_4+1 \geq 1\),也就是说 \(a_3=0\),那么 \(a_2=1\)。反之,如果变化量不为 \(0\),那么 \(a_3=1\)\(a_2=0\)

于是就可以确定所有的值了!

代码

#include <iostream>
using namespace std;
const int N = 1e5 + 10;

int lastt, lastl; //前一个的三同和三连
int nowt, nowl; //现在的三同和三连
int dt[N]; //三同的变化量
int dl[N]; //三连的变化量
int a[N]; //答案

//根据 C(k,2) 推出 k
int orzscz(int k) 
{
	return (sqrt(8 * k + 1) + 1) / 2;
}

int main()
{
	int n;
	cin >> n;
	cin >> lastt >> lastl; //上一个三同和三连
	for(int i = 1;i < n;i++) //插 1 到 n - 1
	{
		cout << "+ " << i << endl;
		cin >> nowt >> nowl;
		dt[i] = nowt - lastt; //三同变化量
		dl[i] = nowl - lastl; //三连变化量
		if(dt[i]) a[i] = orzscz(dt[i]); //根据三同尝试确定
		lastt = nowt;
		lastl = nowl;
	}
	cout << "+ 1" << endl; //最后一个插 1
	cin >> nowt >> nowl;
	dt[n] = nowt - lastt;
	dl[n] = nowl - lastl;
	if(dt[n]) a[1] = orzscz(dt[n]) - 1; //根据三同推出,因为多插了一个 1,所以要减去一
	int sum = dl[n] - dl[1] - 1; //a_2+a_3 的值
	if(a[2]) a[3] = sum - a[2]; //如果已知a[2]就可算a[3]
	else if(a[3]) a[2] = sum - a[3]; //如果已知a[3]就可算a[2]
	else //都不知道
	{
		if(sum == 0) a[2] = a[3] = 0; //是零就零
		else if(sum == 2) a[2] = a[3] = 1; //是二就两个一
		else //是一
		{
			//看插2的三连变化量
			if(dl[2])
			{
				a[2] = 0;
				a[3] = 1;
			}
			else
			{
				a[2] = 1;
				a[3] = 0;
			}
		}
	}
	for(int i = 4;i < n;i++)
	{
		if(!a[i])
		{
			//根据推出的公式确定无法确定的值
			int t = dl[i - 1] - (a[i - 3] + 1) * (a[i - 2] + 1);
			if(t) a[i] = 1;
			else a[i] = 0;
		}
	}
	a[n] = (dl[n - 1] - (a[n - 3] + 1) * (a[n - 2] + 1)) / (a[n - 2] + 1); //根据公式计算 a_n 的值
    //输出答案
	cout << "! ";
	for(int i = 1;i <= n;i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
	return 0;
}
posted @ 2023-10-10 15:55  邻补角-SSA  阅读(21)  评论(0)    收藏  举报  来源