杂题全谈
2024-01-06

想不到好标题了。

有句话怎么说来着,罗马不是一天建成的,是一天天建成的。

还有什么,Do in Rome as the Romans' do,还有一句,All roads leads to Rome。


A. 连续的零 zero

http://222.180.160.110:61235/contest/4647/problem/1

做个前缀和,看看任意一个长度为 \(k\) 的区间中有几个 \(1\)。复杂度 \(O(n)\)

namespace XSC062 {
using namespace fastIO;
const int maxn = 5e5 + 5;
const int inf = 0x3f3f3f3f;
int n, m, res = inf;
int a[maxn], s[maxn];
int min(int x, int y) {
    return x < y ? x : y;
}
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        scanf("%1d", &a[i]);
        s[i] = s[i - 1] + a[i];
        if (i >= m)
            res = min(res, s[i] - s[i - m]);
    }
    print(res, '\n');
    return 0;
}
} // namespace XSC062

B. 反回文串 anti

http://222.180.160.110:61235/contest/4647/problem/2

\(n\) 为奇时,中间的元素一定和自己相等,故无解。

当数量最多的一个字符个数超过 \(\dfrac n2\) 时,由鸽巢得无解。

剩下的情况一定有解。

证明

可以找到一种合法的构造方式。我们列出一个列数为 \(2\),行数为 \(\dfrac n2\) 的表格,将所有相同字母排列在一起,按照从左到右,从上到下的方式将字母填入表格,最后将第 \(i\) 行第一列的字母填入 \(a_i\),第 \(i\) 行第二列的字母填入 \(a_{n-i+1}\),即可完成构造。

一种字母只在第一列或第二列出现当然合法,如果从第一列持续到第二列,因为任意字母出现次数不超过 \(\dfrac n2\) 次,所以同一行的两列不会出现同一种字母。

这叫什么,有字证明。

感觉可以拿去出一道类似于「找到字符串字典序最小的反回文串」之类的小水题

然后现在我们知道有解了,怎么找到最优解呢。

比如有一组 \((a_i,a_{n-i+1})=(\texttt a, \texttt a)\),还有一组 \((a_j, a_{n-j+1})=(\texttt b, \texttt b)\),那我们直接把 \(a_i\)\(a_j\) 交换,皆大欢喜。

这就说明我们需要把值不相等的非法 \(a_i\)\(a_j\) 配对。

然后我们就可以沿用证明中的构造方式,分组配对,一定最优,每组代价为 \(1\)

同一行两个值 \(v\) 相等,因为已经最优了,所以不能再在非法串中寻找答案。应该找合法对中某个值交换,每组代价为 \(2\)。具体和谁交换我们不用担心,只要找到一组 \((a_i,a_{n-i+1})\) 满足 \(a_i\ne v\)\(a_{n-i+1}\ne v\) 就可以了,然后我们又知道 \(v\) 的个数 \(\ne \dfrac n2\),假设 \(\dfrac n2\) 对中每队都有至少一个 \(v\),由于当前这一对有两个相同的 \(v\),那么 \(v\) 的个数就会大于 \(\dfrac n2\),矛盾了,所以一定能找到。

对于非法总对数是奇数的情况,我们要钦定一个非法对强制让其和合法对交换,贪心一下取非法对数量最多的 \(v\) 的某一对最优。

namespace XSC062 {
using namespace fastIO;
const int maxm = 35;
const int maxn = 2e5 + 5;
char s[maxn];
int cnt[maxm], p[maxn];
int T, n, tot, res, now;
int main() {
    scanf("%d", &T);
    while (T--) {
        tot = 0;
        scanf("%d %s", &n, s + 1);
        if (n & 1) {
            puts("-1");
            continue;
        }
        memset(cnt, 0, sizeof (cnt));
        for (int i = 1; i <= n; ++i)
            ++cnt[s[i] - 'a' + 1];
        for (int i = 1; i <= 26; ++i) {
            if (cnt[i] * 2 > n) {
                puts("-1");
                goto noSol;
            }
        }
        memset(cnt, 0, sizeof (cnt));
        for (int i = 1; i * 2 <= n; ++i) {
            if (s[i] == s[n - i + 1])
                ++cnt[s[i] - 'a' + 1], ++tot;
        }
        std::sort(cnt + 1, cnt + 27,
                    std::greater<int>());
        res = now = 0;
        if (tot & 1) {
            res = 1, --cnt[1];
            std::sort(cnt + 1, cnt + 27,
                        std::greater<int>());
        }
        for (int i = 1; i <= 26; ++i) {
            while (cnt[i]--) {
                if (++now > tot / 2) {
                    if (i == p[now - tot / 2])
                        res += 2;
                    else ++res;
                }
                else p[now] = i;
            }
        }
        print(res, '\n');
        noSol: ;
    }
    return 0;
}
} // namespace XSC062

C. 除与减 divsub

http://222.180.160.110:61235/contest/4647/problem/3

小数学,还好。

假设 \(n=d\times k^p\),其中 \(k\nmid d\),那么我们分两种情况讨论。

  1. \(p=0\),即 \(k\nmid n\),那么 \(n\bmod k=1\),即 \(k\mid (n-1)\)

    这个时候问 \(k\) 的个数就相当于是在问 \(n-1\)\(1\) 以外的因子个数。假设 \(n-1={x_1}^{p_1}{x_2}^{p_2}\cdots {x_m}^{p_m}\),那么答案为 \((\prod p_i+1)-1\),减去的是 \(1\)

  2. \(p\ne 0\)\(k\mid n\)

    这个时候好像并没有什么好的转化。好消息是 \(n\) 的范围是 \(10^{12}\),根号枚举因数复杂度跑得过。所以我们就可以暴力判定 \(n\) 的所有因数是否满足条件。

时间复杂度,\(O(\sqrt n\times \log n)\),枚举因数是根号,算次数(也就是算 \(d\))是 \(\log\)

#define int long long
namespace XSC062 {
using namespace fastIO;
int n, m, res, cnt;
int main() {
    read(n), m = n;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            m = n;
            while (m % i == 0) m /= i;
            if (m % i == 1) ++res;
            if (i * i != n) {
                m = n;
                while (m % (n / i) == 0)
                    m /= (n / i);
                if (m % (n / i) == 1) ++res;
            }
        }
    }
    m = n - 1, cnt = 1;
    for (int i = 2; i * i <= m; ++i) {
        if (m % i == 0) {
            int now = 0;
            while (m % i == 0)
                ++now, m /= i;
            cnt *= now + 1;
        }
    }
    if (m != 1) cnt *= 2;
    print(res + cnt, '\n');
    return 0;
}
} // namespace XSC062
#undef int

D. 图书管理员 librarian

http://222.180.160.110:61235/contest/4647/problem/4

[SDOI2008] 郁闷的小 J。

关于这个,我们发现自己不会考场现冲主席树。哎,打 CDQ 又怕写挂。

我们发现这道题的修改都是单点的,询问也只关于某一种颜色,不同的颜色之间没有影响。

于是我们可以把操作离线下来,初始视作将某颜色在某位置增加,修改视作将某颜色在某位置删除,将另一颜色在该位置增加,将所有操作按颜色离散化分类然后 vector 下来,对于所有颜色从前到后树状数组做一遍操作就能 \(O(n\log n+q\log n)\) 解决。

树状数组清空是肯定不能 memset 的,复杂度不对。那么怎么办呢?把所有操作撤回去就可以了。

顺带一提我是洛谷上最优解。

namespace XSC062 {
using namespace fastIO;
const int maxn = 2e5 + 5;
struct __ {
    int ty, l, r, v;
    __() {}
    __(int t1, int l1, int r1, int v1 = 0) {
        if (t1 == 0)
            ty = 0, l = l1, v = r1;
        else ty = 1, l = l1, r = r1, v = v1;
    }
};
char ty;
std::map<int, int> tab;
std::vector<__> q[maxn];
int n, m, tot, x, y, v, id;
int Bit[maxn], a[maxn], res[maxn];
int lowbit(int x) { return x & -x; }
void add(int x, int v) {
    for (; x <= n; x += lowbit(x))
        Bit[x] += v;
    return;
}
int ask(int x) {
    int res = 0;
    for (; x; x -= lowbit(x)) res += Bit[x];
    return res;
}
int main() {
    read(n), read(m);
    for (int i = 1; i <= n; ++i) {
        read(a[i]);
        if (!tab.count(a[i]))
            tab[a[i]] = ++tot;
        a[i] = tab[a[i]];
        q[a[i]].emplace_back(0, i, 1);
    }
    while (m--) {
        scanf("%1s", &ty);
        if (ty == 'C') {
            read(x), read(y);
            if (!tab.count(y))
                tab[y] = ++tot;
            y = tab[y];
            q[a[x]].emplace_back(0, x, -1);
            q[a[x] = y].emplace_back(0, x, 1);
        }
        else {
            ++id;
            read(x), read(y), read(v);
            if (!tab.count(v)) continue;
            v = tab[v];
            q[v].emplace_back(1, x, y, id);
        }
    }
    for (int i = 1; i <= tot; ++i) {
        for (auto &j : q[i]) {
            if (j.ty == 0) add(j.l, j.v);
            else {
                res[j.v] =
                    ask(j.r) - ask(j.l - 1);
            }
        }
        for (auto &j : q[i])
            if (j.ty == 0) add(j.l, -j.v);
    }
    for (int i = 1; i <= id; ++i)
        print(res[i], '\n');
    return 0;
}
} // namespace XSC062

E 会单独开一篇。


F. 树 tree

http://222.180.160.110:61235/contest/4647/problem/6

CF916E。

大分讨给我整不会了,更给我整不会的是下来过后发现这只是个小分讨。

更新子树和子树查询我们都会。换根 DP 我们也都写过,都知道换根并不会对子树结构产生大的影响。所以应当是能根据已知信息推测出子树在原树上对应的点集的。

\(r\) 为当前树根,\(\text {LCA}(x,y)\)\(x,y\)\(1\) 为根时的 LCA,\(\text {LCA}'(x,y)\) 表示 \(x,y\)\(r\) 为根时的 LCA。

那么对于 \(\text {LCA}'(x,y)\),肯定是要讨论 \(x,y\)\(r\) 的位置关系的。

  1. \(\text {LCA}(x,y)\)\(r\) 的子孙。此时 \(\text {LCA}'(x,y) = \text {LCA}(x,y)\)
  2. \(\text {LCA}(x,y)\)\(r\) 的祖先。那么说明至少有一个点不是 \(r\) 的子孙。此时 \(\text {LCA}(x,y)'\) 的值为 \(r\) 为另一个点的 LCA。

整理可得 \(\text {LCA}'(x,y)\)\(\text {LCA}(x,y)\)\(\text {LCA}(x,r)\)\(\text {LCA}(y,r)\) 中的深度最大者。

对于以 \(r\) 为根时的子树 \(x\)

  1. \(x=r\),此时子树为整棵树。
  2. \(\text {LCA}(x,r)\ne x\),即 \(r\) 不为 \(x\) 的子孙,此时子树就是以 \(1\) 为根是的子树 \(x\)
  3. \(\text {LCA}(x,y)=x\),即 \(r\)\(x\) 的子孙,此时子树是整棵树除开 \(x\) 包含 \(r\) 的儿子及其子孙。修改和查询的时候容斥一下就好。这个时候的子树倍增跳一下就能找到。

然后就是常规线段树维护了。时间复杂度 \(O(n\log n)\)

namespace XSC062 {
using namespace fastIO;
const int maxm = 35;
const int maxn = 1e5 + 5;
#define lt (p << 1)
#define rt (lt | 1)
struct _ { int l, r, u, d; };
_ t[maxn << 2];
int f[maxn][maxm];
std::vector<int> g[maxn];
int a[maxn], dfn[maxn], rfn[maxn];
int n, q, r, ty, x, y, v, si, now;
int top[maxn], dep[maxn], tab[maxn];
void swap(int &x, int &y) {
    x ^= y ^= x ^= y;
    return;
}
void DFS(int x) {
    dep[x] = dep[f[x][0]] + 1;
    dfn[x] = ++now, tab[now] = x;
    for (auto i : g[x]) {
        if (i == f[x][0]) continue;
        f[i][0] = x;
        for (int j = 1; j <= si; ++j)
            f[i][j] = f[f[i][j - 1]][j - 1];
        DFS(i);
    }
    rfn[x] = now;
    return;
}
void pushup(int p) {
    t[p].u = t[lt].u + t[rt].u;
    return;
}
void pushdown(int p) {
    if (t[p].d) {
        t[lt].d += t[p].d;
        t[rt].d += t[p].d;
        t[lt].u += t[p].d *
                (t[lt].r - t[lt].l + 1);
        t[rt].u += t[p].d *
                (t[rt].r - t[rt].l + 1);
        t[p].d = 0;
    }
    return;
}
void bld(int p, int l, int r) {
    t[p].l = l, t[p].r = r;
    if (l == r) {
        t[p].u = a[tab[l]];
        return;
    }
    int mid = (l + r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    pushup(p);
    return;
}
void add(int p, int x, int v) {
    t[p].u += v;
    if (t[p].l == t[p].r) return;
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (x <= mid) add(lt, x, v);
    else add(rt, x, v);
    return;
}
void add(int p, int l, int r, int v) {
    if (l <= t[p].l && t[p].r <= r) {
        t[p].d += v;
        t[p].u += (t[p].r - t[p].l + 1) * v;
        return;
    }
    pushdown(p);
    int mid = (t[p].l + t[p].r) >> 1;
    if (l <= mid) add(lt, l, r, v);
    if (r > mid) add(rt, l, r, v);
    pushup(p);
    return;
}
int ask(int p, int l, int r) {
    if (l <= t[p].l && t[p].r <= r)
        return t[p].u;
    pushdown(p);
    int res = 0,
        mid = (t[p].l + t[p].r) >> 1;
    if (l <= mid) res = ask(lt, l, r);
    if (r > mid) res += ask(rt, l, r);
    return res;
}
int LCA(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    for (int i = si; ~i; --i) {
        if (dep[f[x][i]] >= dep[y])
            x = f[x][i];
    }
    if (x == y) return x;
    for (int i = si; ~i; --i) {
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    }
    return f[x][0];
}
void Add(int x, int v) {
    int rlca = LCA(r, x);
    if (x == r) add(1, 1, n, v);
    else if (rlca != x)
        add(1, dfn[x], rfn[x], v);
    else {
        add(1, 1, n, v);
        int p = r;
        for (int i = si; ~i; --i) {
            if (dep[f[p][i]] >= dep[x] + 1)
                p = f[p][i];
        }
        add(1, dfn[p], rfn[p], -v);
    }
    return;
}
void tAdd(int x, int y, int v) {
    int llca = LCA(r, x), rlca = LCA(r, y),
        ulca = LCA(x, y);
    if (dep[llca] >= dep[rlca] &&
        dep[llca] >= dep[ulca]) Add(llca, v);
    else if (dep[rlca] >= dep[llca] &&
        dep[rlca] >= dep[ulca]) Add(rlca, v);
    else Add(ulca, v);
    return;
}
int tAsk(int x) {
    int rlca = LCA(r, x);
    if (x == r) return t[1].u;
    if (rlca != x)
        return ask(1, dfn[x], rfn[x]);
    int p = r;
    for (int i = si; ~i; --i) {
        if (dep[f[p][i]] >= dep[x] + 1)
            p = f[p][i];
    }
    return t[1].u - ask(1, dfn[p], rfn[p]);
}
void add(int x, int y) {
    g[x].push_back(y);
    return;
}
int main() {
    read(n), read(q), r = 1;
    si = log(n) / log(2.0);
    for (int i = 1; i <= n; ++i) read(a[i]);
    for (int i = 1; i < n; ++i) {
        read(x), read(y);
        add(x, y), add(y, x);
    }
    dep[0] = 1, DFS(1), dep[0] = 0;
    bld(1, 1, n);
    while (q--) {
        read(ty);
        if (ty == 1) read(r);
        else if (ty == 2) {
            read(x), read(y), read(v);
            tAdd(x, y, v);
        }
        else {
            read(x);
            print(tAsk(x), '\n');
        }
    }
    return 0;
}
} // namespace XSC062
#undef int

一言 - Hitokoto