12.21 模拟赛

T1

题面:一个长度为 \(n\) 的序列 \(\{a_1,a_2,\cdots,a_n\}\),求有多少个不同的 \(\gcd(a_i,a_j)(i\not=j)\)\(n\le2\times 10^5,a_i\le 10^7\)
题解:从大到小枚举值域,简单容斥,复杂度是调和级数 \(O(n\ln n)。\)

死亡回放:\(N\) 开小了。

核心代码
for (int s = maxa; s >= 1; s--) {
    int now = 0;
    for (int i = s; i <= maxa; i += s) now += vis[i];
    cnt[s] = 1ll * now * (now - 1) >> 1; 
    for (int i = 2 * s; i <= maxa; i += s) cnt[s] -= cnt[i];
    if (cnt[s]) ans++;
}

T2

洛谷link
题面:有一个二进制的完全平方数,但是它其中有 \(k\) 位被替换成 \(?\),请你找回原来的数。\(len\le 125,k\le40\)
题解:考虑折半(一半向上取整,两边其实长度一样),设该完全平方数为 \(A\)\(a=\sqrt A\),由于 \(k\le 40\),那么必然有一半 \(?\) 的个数是 \(\le 20\),我们考虑能否通过小的那边,确定答案。

我们通过高位确定答案

我们令已经确定的数位中1位置上的和为 \(x\),仍未确定的数位的位置上的和为 \(y\)
于是显然有 \(\sqrt{x}\le a \le \sqrt{x+y}\),还有 \(x\ge 2^n,y\le 2^{\frac{n}{2}}\)

\[\begin{align*} &\sqrt{x+y}-\sqrt{x}\\ &=\frac{y}{\sqrt{x+y}+\sqrt{x}}\\ &\le\frac{2^{\frac{n}{2}}}{\sqrt{2^n+2^{\frac{n}{2}}}+\sqrt{2^n}}\\ &=\frac{1}{\sqrt{1+\frac{1}{2^{\frac{n}{2}}}}+1}\\ &=\frac{1}{2} \end{align*} \]

于是我们只需要分别检查 \(\lfloor\sqrt{x}\rfloor,\lfloor\sqrt{x}\rfloor+1\) 即可,\(x\) 需要 \(2^\frac{k}{2}\) 枚举。

我们通过低位确定答案

具体看这

完整代码
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define out() (cout << "sb\n")
#define i128 __int128
#define p(i) ((i128(1)) << i)

int T;
string s;

bool check(const i128 &x, const i128 &mask1, const i128 &mask0) {
    return ((x | mask1) == x && (x & ~mask0) == x); //不多也不少=>刚刚好
}

i128 solve(const string &s) {
    int n = s.length();
    if (n == 1) 
        return s[0] == '0' ? -1 : 1;
    if (s[0] != '1' && s[1] != '1') {
        i128 ans = solve(s.substr(2));
        if (~ans) return ans * 4;
    }    
    i128 mask1 = 0, mask0 = 0; 
    for (int i = 0; i < n; i++) {
        mask1 |= (s[i] == '1') * p(i);
        mask0 |= (s[i] == '0') * p(i);
    } 
    int mid = n + 1 >> 1, cntf = 0, cnts = 0;
    for (int i = 0; i < mid; i++) cntf += (s[i] == '?');
    for (int i = n - mid; i < n; i++) cnts += (s[i] == '?');
    if (cntf < cnts) { //低位
        i128 r0 = mask0, r1 = mask1;
        vector<i128> v, now;
        v.push_back(1);
        for (int i = 2; i <= mid; i++) {
            i128 mask = p(i) - 1;
            mask0 = r0 & mask, mask1 = r1 & mask;
            now.clear();
            for (i128 &val : v) {
                if (check(val * val, mask1, mask0)) now.push_back(val);
                val |= p(i - 1);
                if (check(val * val, mask1, mask0)) now.push_back(val);
            }
            v = now;
        }
        mask1 = r1, mask0 = r0;
        for (i128 &val : v) 
            if (check(val * val, mask1, mask0)) return val * val; 
    } else { //高位暴力枚举
        i128 mask = 0, base = 0;
        for (int i = n - mid; i < n; i++) {
            mask |= (s[i] == '?') * p(i);
            base |= (s[i] == '1') * p(i);
        }
        i128 t = mask + 1;
        while (t) {
            t = (t - 1) & mask;
            i128 val = floor(sqrt((long double)(t | base)));
            if (check(val * val, mask1, mask0)) return val * val;
            val++;
            if (check(val * val, mask1, mask0)) return val * val;
        }
    }
    return -1;
}

int main() {
    // system("fc .out .out");
    // freopen(".in", "r", stdin);
    // freopen(".out", "w", stdout);
    IOS;
    cin >> T;
    // T = 1;
    for (int _ = 1; _ <= T; _++) {
        cin >> s; int n = s.length();
        if (n == 1) {
            cout << "Case #" << _ << ": ";
            cout << s << "\n";
            continue;
        }
        reverse(s.begin(), s.end());
        i128 ans = solve(s);
        cout << "Case #" << _ << ": ";
        for (int i = n - 1; ~i; i--) 
            cout << (int)((ans >> i) & 1);
        cout << "\n";
    }
    return 0;
}

T3

题面:三维空间中有 \(n\) 个点,\(n\) 个向量,你要给每个点分配一个向量,使得分配后每两个点之间的距离 \(\ge\) 原来两个点距离,输出分配方案。
\(n\le 500\)\(-10^{4}\ge\)每个点和向量的坐标大小 \(\le 10^4\)
题解:考虑动态调整,给每个点先分配一个向量。然后 \(O(n^2)\) 的枚举两个点,如果两个点之间的距离 \(\le\) 原来的距离,进行交换,在重新从头枚举,重复执行,直到满足条件。

核心代码
random_shuffle(p + 1, p + n + 1);
// for (int i = 1; i <= n; i++) cout << p[i] << " \n"[i == n];
while (1) {
    bool flag = 0;
    for (int i = 1; i <= n; i++)
        for (int j = i + 1; j <= n; j++)
            if (disa(i, j) > dis(i, j)) {
                swap(p[i], p[j]);
                flag = 1;
            }
    if (!flag) break;
}
posted @ 2025-12-21 14:50  lcx_OIer  阅读(2)  评论(0)    收藏  举报