【2020.9.29 NOIP模拟赛 T3】寻梦(fantasy)(CF986F)

题目链接

原题:CF986F Oppa Funcan Style Remastered

题意

多组询问,每次询问 \(n\) 能否能划分成若干 \(k\) 的因数(可重)的和。

\(T \le 10^4,n \le 10^{18},k \le 10^{15}\)。保证每个测试点内不同的 \(k\) 的数量不超过 \(50\)

题解

显然只用考虑 \(k\) 的质因数。题意可以表述成是否存在 \(p_1x_1+p_2x_2 + p_3x_3 + ... + p_mx_m=n\) 的一组非负整数解。这时同余最短路的经典模型。找出 \(k\) 的最小质因数 \(p_1\),那么要求就转化为初始点为0,每次可以加 \(p_2,...,p_m\),用最短路算法处理出让当前的点的值模 \(p_1\) 的意义下同余于 \(i\) 的最小的当前的值是多少,每个 \(n\) 直接查 \(n \bmod p_1\) 的最小值是否大于 \(n\),据此判断即可。复杂度:\(O(50 \times p_1\log p_1 + T)\)

不过这个算法可以被一个质数 \(k\) 卡死。所以需要对质数进行特判(直接判是否是倍数)。但是两个大质数还是能卡死我们,于是还需要对两个大质数进行特判,即我们需要知道是否存在 \(p_1x_1+p_2x_2=n\) 的非负整数解。这个可以通过 exgcd 搞出 \(x_1\) 的最小非负整数解,此时 \(x_2\) 尽可能的大;如果此时都无法满足要求,那么就不可行,否则可行。复杂度:\(O(50 \times \sqrt[3]{k} \log \sqrt[3]{k} + T\log p)\)

过于模板,就不放代码了。(其实是没写)

另解(错解)

根据某年NOIPD1T1凯哥的疑惑,两个整数 \(a,b\) 能够通过相加构成 \(ab-a-b\) 以上的所有数。于是我们可以取出最小的 \(p_1,p_2\),以此作为一个上限 :\(limi = p_1 \times p_2 - p_1 - p_2\)。对于 \(n > limi\) 的情况可以直接判掉。对于 \(n \le limi\) 的情况,我们可以直接完全背包找出所有能组成的数,直接判断 \(n\) 即可。但是 \(limi\) 可能很大,我们没办法背包背那么多,于是我们在 \(limi > 3 \times 10^7\) 的时候只背 \(\le 3 \times 10^7\) 的数。此时 \(k\) 最多有三个质数,如果有 \(0,1,2\) 个质数,方法同上;如果有 \(3\) 个质数,我们可以对于每个 \(\le limi\)\(n\),枚举最大的两个质数的系数来判断。这样的复杂度为 \(O(50 \times 3 \times 10^7 \times 3(k 的质因子个数) + T \times \frac{p_1p_2}{p_2} \times \frac{p_1p_2}{p_3}) = O(4 \times 10^9 + T \times p_1p_2)\)。由于数据水,复杂度跑不满。

bitset<N> bt;
ll limi;
ll k_pri[N], kpcnt;
bool flag;
inline void work(ll k) {
	flag = false;
	limi = 1e18;
	kpcnt = 0;
	bt.reset();
	if (k == 1) {
		flag = true;
		return ;
	}
	for (int i = 1; i <= pcnt; ++i) {
		if (k % pri[i] == 0) {
			k_pri[++kpcnt] = pri[i];
			while (k % pri[i] == 0)	k /= pri[i];
			if (k == 1)	break;
		}
	}
	if (k > 1)	k_pri[++kpcnt] = k;
	if (kpcnt <= 2)	return ;
	k_pri[kpcnt + 1] = 1;
	limi = k_pri[1] * k_pri[2] - k_pri[1] - k_pri[2];
	bt[0] = 1;
	ll up = min(limi + 1, 1ll * N);
	for (int i = 1; i <= kpcnt && k_pri[i] <= limi; ++i)
		for (ll d = k_pri[i]; d < up; ++d)
			bt[d] = bt[d] | bt[d - k_pri[i]];
}

inline void ADD(ll &a, ll &b, ll &P) 
inline ll quickmul(ll x, ll k, ll P) 
void exgcd(ll a, ll b, ll &x, ll &y)

inline bool sol(ll n) {
	if (flag)	return false;
	if (kpcnt == 1)	return n % k_pri[1] == 0;
	if (kpcnt == 2) {
		ll x, y, p1 = k_pri[1], p2 = k_pri[2];
		exgcd(p1, p2, x, y);
		x = quickmul(x, n, p2);
		if (1.0 * x * p1 > 2e18)	return false;
		return n - x * p1 >= 0;
	}
	if (n > limi)	return true;
	if (n < N)	return bt[n];
	ll t = k_pri[3], tt = k_pri[2], ttt = k_pri[1];
	for (ll d = 0; d <= n; d += t) {
		for (ll dd = 0; dd + d <= n; dd += tt) {
			if ((n - d - dd) % ttt == 0)	return true;
		}
	}
	return false;
}

int main() {
	int _; read(_);
	if (_ >= 13)	up = 2.19e7;
	else	up = 1e6;
	init();
	int tcn; read(tcn);
	for (int i = 1; i <= tcn; ++i) read(tc[i].n), read(tc[i].k), tc[i].id = i;
	sort(tc + 1, tc + 1 + tcn, cmp);
	for (int i = 1; i <= tcn; ++i) {
		if (tc[i].k != tc[i - 1].k) work(tc[i].k);
		ans[tc[i].id] = sol(tc[i].n);
	}
	for (int i = 1; i <= tcn; ++i) {
		puts(ans[i] ? "YES" : "NO");
	}
	return 0;
}

总结反思

又一次把带着调试语句以及十来个 bug 的代码交了上去...毕竟这种题半个小时是很难写完调完的,并且还是在考试的最后半个小时。还是不够熟练,想的时间太长,大概想了一个小时。啥时候我才能有 zzz 那么熟练啊?

我在洛谷做的题和 zzz 差不了多少,但是可能我做题时自己思考得少一些,可能以后要学着 zzz 半个小时没做出来再看题解。

反正膜zzz就对了。

posted @ 2020-09-30 08:27  JiaZP  阅读(271)  评论(0编辑  收藏  举报