【题解】P3583 [POI2015] KWA

模拟赛出这道题???

还好赛时乱搞做出来了(/hanx

link

Description

定义一个数 \(n\) 的拆分为:将 \(n\) 表示为若干个不同的正整数的平方和。令 \(k(n)\)\(n\) 的拆分中最大数的最小值。若不存在满足要求的拆分,则 \(k(n)=\infty\)

给定 \(n\)\(n\le10^{18}\)),求 \(k(n)\) 以及 \(\sum_{i=1}^n[\exists j>i,k(j)<k(i)]\)

Solution

赛时看到 1e18 直接震惊

这个拆分似乎没有什么好性质,于是暴力算一下 \(1\sim10^4\) 以内的数,发现只有 \(2\sim128\) 中的 \(31\) 个数无法被表示。大胆猜测只有这几个数无法被表示。

\(S(n)=\sum_{i=1}^ni^2=\frac16n(n+1)(2n+1)\)\(t=\min\{x\mid S(x)\ge n\}\)。那么显然有 \(k(n)\ge t\)(否则全选也表示不了)。

上界不大好处理。正难则反,考虑从 \(S(t)\) 里蒯出几个不需要的平方数,即考虑 \(S(t)-n\)。由于 \(n\) 足够大,可以认定 \(n>S(t)-n\),于是我们缩小了要考虑的数的范围。如果 \(S(t)-n\) 能被表示出来,那么显然 \(k(n)=t\)。但是如果 \(k(S(t)-n)=\infty\) 就不能这样。

观察打表数据大胆猜测剩下这种情况是 \(k(n)= t+1\)。感性理解一下有 \(n-(t+1)^2\) 比较小,可以用 \(1\sim t\) 构造出来,于是 \(n\) 就构造出来了。\(S(t)-n\) 只有 \(31\) 种取值,所以 \(k(n)=t+1\) 也只有 \(31\) 个。

于是可以求出 \(k(n)\),时间复杂度感觉是比较松的 \(O(n^{1/3})\)?题解的 \(O(\log)\) 感觉不大对。

第二问考虑把 \(1\sim n\) 的数分为若干个 \((S(x-1),S(x)]\) 的块求解。\(t>30\) 后可以整块求解(每块只有 \(31\) 个取值为 \(x+1\) 的),前面的暴力就行了。

实际做的时候,\(n\) 比较小可能出问题,可以考虑预处理出一部分答案。我一开始预处理写错了结果你谷和模拟赛数据都没卡掉

时间复杂度就是求 \(k(n)\) 的复杂度。

code:

#include <cstdio>
#include <cmath>

typedef long long ll;
const ll Bad[31] = {2,3,6,7,8,11,12,15,18,19,22,23,24,27,28,31,32,33,43,44,47,48,60,67,72,76,92,96,108,112,128};
	// Numbers that can't be represented
const ll inf = 1e18;
const ll N = 9455,maxn = 2.5e4; // S(31) = 9455

ll n,t,k[maxn];

inline ll S(ll n) { return n * (n + 1) / 2 * (2 * n + 1) / 3; } // Prefix Square Sum

inline ll Get(ll n) { // Least t s.t. S(t) >= n
	ll t = pow(n * 3.,1. / 3);
	for(;S(t) >= n;) t --;
	for(;S(t) < n;) t ++;
	return t;
}

inline ll K(ll n) { // k(n)
	if(n <= N) return k[n];
	ll t = Get(n);
	if(K(S(t) - n) >= t) return t + 1;
	return t;
}

inline void Init() { // Pre-compute the value of k(i) for i in 1 ~ S(31)
	for(ll i = 1;i <= N;i ++) k[i] = inf;
	for(ll s = 0,i = 1;i <= 32;i ++,s += i * i)
		for(ll j = s;j >= 0;j --) if(k[j] < inf && k[j + i * i] > i) k[j + i * i] = i;
}

int main() {
	scanf("%lld",&n); Init();
	ll Cnt_res = 0,t,K_res = K(n);
	if(K_res < inf) printf("%lld",K_res); else putchar('-'); // Part 1
	for(ll i = 1;i <= N && i <= n;i ++) if(k[i] == inf || S(k[i] - 1) > i) Cnt_res ++; // 1 ~ S(31)
	if(n > N) {
		t = Get(n); Cnt_res += 31LL * (t - 31);
		for(int i = 0;i < 31;i ++) if(S(t) - Bad[i] <= n) Cnt_res ++;
	}
	printf(" %lld\n",Cnt_res); // Part 2
	return 0;
}
posted @ 2022-10-05 12:49  Sya_Resory  阅读(25)  评论(0编辑  收藏  举报