【2020.9.29 NOIP模拟赛 T3】寻梦(fantasy)
作者:互联网
题意
多组询问,每次询问 \(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就对了。
标签:10,le,2020.9,NOIP,质数,times,limi,寻梦,ll 来源: https://www.cnblogs.com/JiaZP/p/13752829.html