2022 年杭电多校第九场补题记录

A Arithmetic Subsequence

题意:给定一个长度为 n n n 的序列,对它进行重新排序使得它不存在等差子序列。 n ≤ 5 × 1 0 3 n \leq 5\times 10^3 n5×103

解法:首先如果一个数字出现大于等于 3 3 3 次,则一定无解。

考虑一个等差子序列的二进制构成: x , y , z x,y,z x,y,z。首先都丢弃掉共同的最低位的 0 0 0,考虑公差为 1 1 1 的情况(公差更大的同理),则有 x = ( 01 ⋯ 01 ) x=(01\cdots 01) x=(0101) y = ( 01 ⋯ 10 ) y=(01\cdots 10) y=(0110) z = x = ( 01 ⋯ 11 ) z=x=(01\cdots 11) z=x=(0111),前面完全相同。则只有让 y y y 排列在 x , z x,z x,z 之前或者之后才行。注意到 x , z x,z x,z 同奇偶而 y y y 不同,因而可以按照末尾对其排序,则可以顺利让 x , z x,z x,z 置于 y y y 前或后。同理对公差更大的序列也可以进行类似的操作。因而最后的答案就是对数字按照 b i t r e v \rm bitrev bitrev 的大小进行排序即可。总时间复杂度 O ( n log ⁡ n ) \mathcal O(n \log n) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
unsigned rev(unsigned x)
{
    unsigned ans = 0;
    for (int i = 31; i >= 0; i--, x >>= 1)
        ans |= (x & 1) << i;
    return ans;
}
int main()
{
    int t, n;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        map<int, int> times;
        vector<pair<unsigned, unsigned>> a(n);
        bool flag = 1;
        for (int i = 0; i < n;i++)
        {
            scanf("%u", &a[i].second);
            times[a[i].second]++;
            a[i].first = rev(a[i].second);
        }
        for(auto i:times)
            if(i.second >= 3)
            {
                flag = 0;
                break;
            }
        if(!flag)
        {
            printf("NO\n");
            continue;
        }
        printf("YES\n");
        sort(a.begin(), a.end());
        for (auto i : a)
            printf("%d ", i.second);
        printf("\n");
    }
    return 0;
}

C Fast Bubble Sort

题意:给定长度为 n n n 的排列 { p i } \{p_i\} {pi} q q q 次询问 [ l , r ] [l,r] [l,r],问该区间需要经过多少次子区间旋转平移操作(即, a i a i + 1 ⋯ a j → a i + 1 ⋯ a j a i a_ia_{i+1} \cdots a_j \to a_{i+1} \cdots a_ja_i aiai+1ajai+1ajai a i a i + 1 ⋯ a j → a j a i a i + 1 ⋯ a j − 1 a_ia_{i+1} \cdots a_j \to a_ja_ia_{i+1} \cdots a_{j-1} aiai+1ajajaiai+1aj1),变成 B ( p [ l , r ] ) B(p[l,r]) B(p[l,r])。其中 B ( p [ x , y ] ) B(p[x,y]) B(p[x,y]) 操作如下:

int* B(int *p, int x, int y)
{
    for(int i=x;i<y;i++)
        if(p[i]>p[i+1])
            swap(p[i],p[i+1]);
    return p;
}

n , q ≤ 1 × 1 0 5 n,q \leq 1\times 10^5 n,q1×105

解法:利用单调栈求出每个点后面第一个比它大的值在哪里,第 i i i 个数字记为 R i R_i Ri。若 r i = i + 1 r_i=i+1 ri=i+1,则必然不会交换;否则它就会有交换操作,且直接交换到 R p R_p Rp。对应于可执行操作,就是一次旋转平移。同时,若满足 i < j ≤ r j ≤ r i i < j \leq r_j \leq r_i i<jrjri,则 j j j 不会被交换。考虑倍增, f i , j f_{i,j} fi,j 表示从第 i i i 个点开始经历 2 j 2^j 2j 次旋转平移操作所到达的点。若抵达最后一个跳跃点 p p p,且 R p ≥ r R_p \geq r Rpr,若 R p = p + 1 R_p=p+1 Rp=p+1 则不经历旋转平移,否则还需要一次旋转平移。

#include<bits/stdc++.h>
using namespace std;
const int N = 100000;
int a[N + 5], r[N + 5], nx[N + 5], f[N + 5][20];
int main()
{
    int t, n, q;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &q);
        for (int i = 1; i <= n;i++)
        {
            r[i] = n + 1;
            for (int j = 0; j < 20;j++)
                f[i][j] = 0;
        }
        for (int j = 0; j < 20;j++)
            f[n + 1][j] = n + 1;
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        stack<int> s;
        for (int i = n; i >= 1;i--)
        {
            while (!s.empty() && a[s.top()] < a[i])
                s.pop();
            if(!s.empty())
                r[i] = s.top();
            else
                r[i] = n + 1;
            s.push(i);
        }
        f[n + 1][0] = nx[n + 1] = n + 1;
        for (int i = n; i >= 1;i--)
        {
            if(r[i] > i + 1)
            {
                nx[i] = i + 1;
                f[i][0] = r[i];
            }
            else
            {
                f[i][0] = f[r[i]][0];
                nx[i] = nx[i + 1];
            }
        }
        for (int i = 1; i < 20; i++)
            for (int j = 1; j <= n + 1; j++)
                f[j][i] = f[f[j][i - 1]][i - 1];
        while(q--)
        {
            int l, r;
            scanf("%d%d", &l, &r);
            int ans = 0, place = l;
            for (int i = 19; i >= 0;i--)
                if (f[place][i] <= r)
                {
                    place = f[place][i];
                    ans += 1 << i;
                }
            printf("%d\n", ans + (nx[place] <= r));
        }
    }
    return 0;
}

F Mario Party

题意:给定长度为 n n n 的序列 { a n } \{a_n\} {an} q q q 次询问,每次从 l l l 号节点出发,初始钱数为 x x x,走到 i + 1 i+1 i+1 节点的时候若 x + a i + 1 ≥ 0 x+a_{i+1} \geq 0 x+ai+10 x ← x + a i + 1 x \leftarrow x+a_{i+1} xx+ai+1,否则不变,问抵达 r r r 节点时的钱数。 n , q ≤ 5 × 1 0 5 n,q \leq 5\times 10^5 n,q5×105 ∑ ∣ a i ∣ ≤ 1 × 1 0 6 \sum|a_i| \leq 1\times 10^6 ai1×106

解法:考虑在第 i i i 个节点的时候,两次询问里面人的钱数相同,则他们两个可以认为是一个人。

首先离线,然后从左到右遍历序列的时候维护一个询问的并查集。对于 a i ≥ 0 a_i \geq 0 ai0 的情况,那么显然它不会合并询问;否则,对于钱数为 x x x 和钱数为 x − a i x-a_i xai 的询问,就需要合并成一个询问。

考虑加入询问:如果当前的钱数没人了,则新建一个点;否则合并入并查集中,同时利用并查集维护每个询问的起始钱数对应于现在的钱数。回答只需要在并查集中查询对应的钱数即可。总时间复杂度 O ( n + q ) \mathcal O(n+q) O(n+q)

#include <bits/stdc++.h>
using namespace std;
const int N = 1000000;
int p[3 * N + 5], pos[3 * N + 5];
class DSU
{
    vector<int> father;

public:
    DSU(int n)
    {
        father.resize(n);
        for (int i = 0; i < n;i++)
            father[i] = i;
    }
    int getfather(int x)
    {
        return father[x] == x ? x : father[x] = getfather(father[x]);
    }
    void merge(int v, int u)
    {
        u = getfather(u), v = getfather(v);
        if (u != v)
            father[u] = v;
    }
};
int main()
{
    int caset, n, q;
    scanf("%d", &caset);
    for (int o = 1; o <= caset;o++)
    {
        scanf("%d%d", &n, &q);
        for (int i = 0; i <= 3 * N; i++)
        {
            p[i] = -1;
            pos[i] = 0;
        }
        DSU t(q);
        auto merge = [&](int u, int v)
        {
            assert(u <= 2 * N && u >= 0);
            assert(v <= 2 * N && v >= 0);
            if (p[u] == -1)
                return;
            if (p[v] == -1)
            {
                p[v] = p[u];
                pos[p[v]] = v;
            }
            else
                t.merge(p[v], p[u]);
            p[u] = -1;
        };
        int start = N;
        vector<int> a(n), ans(q), x(q);
        vector<vector<int>> add(n), del(n);
        for (int i = 0; i < n;i++)
            scanf("%d", &a[i]);
        for (int i = 0, l, r; i < q;i++)
        {
            scanf("%d%d%d", &l, &r, &x[i]);
            add[l - 1].push_back(i);
            del[r - 1].push_back(i);
        }
        for (int i = 0; i < n;i++)
        {

            if (a[i] < 0)
                for (int j = start; j < start - a[i];j++)
                    merge(j, j - a[i]);
            start -= a[i];
            for (auto j : add[i])
            {
                int now = x[j] + start;
                if(p[now] == -1)
                {
                    p[now] = j;
                    pos[j] = now;
                }
                else
                    t.merge(p[now], j);
            }
            for (auto j : del[i])
                ans[j] = pos[t.getfather(j)] - start;
        }
        for (auto i : ans)
            printf("%d\n", i);
    }
    return 0;
}

G Matryoshka Doll

题意:给定 n n n 个盒子的大小 { a i } \{a_i\} {ai},一个大小为 x x x 的盒子能放入大小为 y y y 的盒子中当且仅当 x + r ≤ y x+r \leq y x+ry。问将这 n n n 个盒子分成 k k k 组,每一组中小盒子都能完全放入本组任何比它大的盒子中的方案数。 n , k ≤ 5 × 1 0 3 n,k \leq 5\times 10^3 n,k5×103

解法:首先按照大小对 a i a_i ai 排序。考虑 f i , j f_{i,j} fi,j 表示前 i i i 小的盒子分成 j j j 堆的方案数,则 f i , j ← f i − 1 , j − 1 + f i − 1 , j max ⁡ ( 0 , j − c i ) f_{i,j} \leftarrow f_{i-1,j-1}+f_{i-1,j}\max(0,j-c_i) fi,jfi1,j1+fi1,jmax(0,jci),其中 c i c_i ci 表示前 i i i 个盒子中有多少个盒子不能放入第 i i i 个盒子。 f i − 1 , j max ⁡ ( 0 , j − c i ) f_{i-1,j}\max(0,j-c_i) fi1,jmax(0,jci) 的含义为,第 i i i 个盒子只能和前面所有的堆中,最大的盒子满足 a j + r ≤ a i a_j+r \leq a_i aj+rai 放在一堆,对于不能被 a i a_i ai 放进去的盒子,它一定会被单独的列在别的堆中。预处理 c i c_i ci 后总时间复杂度 O ( n 2 ) \mathcal O(n^2) O(n2)

#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
const int N = 5000;
int f[N + 5][N + 5], a[N + 5], l[N + 5];
int main()
{
    int t, n, k, r;
    scanf("%d", &t);
    while(t--)
    {
        memset(f, 0, sizeof(f));
        scanf("%d%d%d", &n, &k, &r);
        for (int i = 1; i <= n;i++)
            scanf("%d", &a[i]);
        sort(a + 1, a + n + 1);
        l[1] = 1;
        for (int i = 2; i <= n;i++)
        {
            int st = l[i - 1];
            while(st <= i && a[st] <= a[i] - r)
                st++;
            l[i] = st;
        }
        for (int i = 1; i <= n;i++)
            f[i][i] = 1;
        for (int i = 2; i <= n;i++)
            for (int j = 1; j < i && j <= k;j++)
                f[i][j] = (f[i - 1][j - 1] + 1ll * f[i - 1][j] * max(0, j - (i - l[i])) % mod) % mod;
        printf("%d\n", f[n][k]);
    }
    return 0;
}

H Shortest Path in GCD Graph

题意:有 n n n 个点的图,第 i i i 个节点和第 j j j 个节点之前有一条边权为 gcd ⁡ ( i , j ) \gcd(i,j) gcd(i,j) 的边。 q q q 次询问从 x x x y y y 节点的最短路长度和最短路条数。 n ≤ 1 × 1 0 7 n\leq 1\times 10^7 n1×107 q ≤ 5 × 1 0 4 q \leq 5\times 10^4 q5×104

解法:若 gcd ⁡ ( x , y ) = 1 \gcd(x,y)=1 gcd(x,y)=1,则最短路长度为 1 1 1,条数也为 1 1 1。否则一定可以通过 i → 1 → j i \to 1 \to j i1j 以长度为 2 2 2 抵达。考虑符合条件的最短路条数,必须满足 gcd ⁡ ( x , i ) = 1 \gcd(x,i)=1 gcd(x,i)=1 gcd ⁡ ( y , i ) = 1 \gcd(y,i)=1 gcd(y,i)=1,即是计算 ∑ i = 1 n [ gcd ⁡ ( i , l c m ( x , y ) ) = 1 ] \displaystyle \sum_{i=1}^n [\gcd(i,{\rm lcm}(x,y))=1] i=1n[gcd(i,lcm(x,y))=1]。设 u = l c m ( x , y ) u={\rm lcm}(x,y) u=lcm(x,y),有:
∑ i = 1 n [ gcd ⁡ ( i , u ) = 1 ] = ∑ i = 1 n ∑ d ∣ u μ ( d ) = ∑ d ∣ u μ ( d ) ∑ i = 1 ⌊ n d ⌋ 1 = ∑ d ∣ u μ ( d ) ⌊ n d ⌋ \begin{aligned} \sum_{i=1}^n [\gcd(i,u)=1]=&\sum_{i=1}^n \sum_{d|u}\mu(d)\\ =&\sum_{d|u}\mu(d)\sum_{i=1}^{\left \lfloor \frac{n}{d}\right \rfloor}1\\ =&\sum_{d|u}\mu(d)\left \lfloor \frac{n}{d}\right \rfloor\\ \end{aligned} i=1n[gcd(i,u)=1]===i=1nduμ(d)duμ(d)i=1dn1duμ(d)dn
因而现在只需要枚举 l c m ( x , y ) {\rm lcm}(x,y) lcm(x,y) n n n 小的因子。对 u , v u,v u,v 进行质因子分解,统计 u u u 的质因子,然后 2 k 2^k 2k 枚举所有质因子组合即可。由于 1 × 1 0 7 1\times 10^7 1×107 下至多只有 8 8 8 个质因子, l c m ( x , y ) {\rm lcm}(x,y) lcm(x,y)最多能包含的本质不同的质因子只有 12 12 12 个,因而完全可以通过。总复杂度 O ( q 2 k ) \mathcal O(q2^k) O(q2k),其中 k ≤ 12 k \leq 12 k12

#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 5;
bool vis[N + 5];
int tot, prime[N + 5], mu[N + 5], minfactor[N + 5];
void sieve(int n)
{
    mu[1] = 1;
    for (int i = 2; i <= n;i++)
    {
        if(!vis[i])
        {
            minfactor[i] = i;
            prime[++tot] = i;
            mu[i] = -1;
        }
        for (int j = 1; j <= tot && (long long)prime[j] * i <= n;j++)
        {
            int num = prime[j] * i;
            vis[num] = 1;
            mu[num] = -mu[i];
            minfactor[num] = minfactor[i];
            if (i % prime[j] == 0)
            {
                mu[num] = 0;
                break;
            }
        }
    }
}
int q, n, m, u, v, ans;
vector<int> factor;
void fac(int x)
{
    while (x > 1)
    {
        factor.push_back(minfactor[x]);
        x /= minfactor[x];
    }
}
void dfs(int step, long long val)
{
    if (val > n)
        return;
    if (step == m)
    {
        ans += mu[val] * (n / val);
        return;
    }
    dfs(step + 1, val * factor[step]);
    dfs(step + 1, val);
}
int main()
{
    sieve(N);
    scanf("%d%d", &n, &q);
    while(q--)
    {
        scanf("%d%d", &u, &v);
        int d = __gcd(u, v);
        if (d == 1)
        {
            printf("1 1\n");
            continue;
        }
        printf("2 ");
        factor.clear();
        fac(u), fac(v);
        sort(factor.begin(), factor.end());
        factor.erase(unique(factor.begin(), factor.end()), factor.end());
        m = factor.size();
        ans = 0;
        dfs(0, 1);
        printf("%d\n", ans + (d == 2));
    }
    return 0;
}

J Sum Plus Product

题意:给定长度为 n n n 的序列 { a i } \{a_i\} {ai},每次等概率随机选择两个数字 a , b a,b a,b,将它们从序列中删除,并添加 a b + a + b ab+a+b ab+a+b,问最终得到数字的期望。 n ≤ 500 n \leq 500 n500

解法:整个序列的 ∏ i = 1 n ( a i + 1 ) \displaystyle \prod_{i=1}^n (a_i+1) i=1n(ai+1) 在除了最后一次操作的时候是不会变的,最后一次操作会让它减少 1 1 1,因而输出 ∏ i = 1 n ( a i + 1 ) − 1 \displaystyle \prod_{i=1}^n (a_i+1)-1 i=1n(ai+1)1 即可。

K The Alchemist of the Discrete Mathematics

题意:给定质数 p p p,问 F p {\Bbb F}_p Fp 中有多少个集合 S S S 满足存在一个域上 n n n 次可约多项式 f ( x ) = g ( x ) k ( x ) f(x)=g(x)k(x) f(x)=g(x)k(x),且 S = T ∩ F p S=T \cap {\Bbb F}_p S=TFp,其中 T T T 为模意义方程 f ( x ) g ( c x ) ≡ 0 ( m o d p ) f(x)g(cx) \equiv 0 \pmod p f(x)g(cx)0(modp) 的解集。 p ≤ 5 × 1 0 5 p\leq 5\times 10^5 p5×105 n n n 无限制, c ∈ F p c \in {\Bbb F}_p cFp

解法:显然在 c c c 的作用下, F p {\Bbb F}_p Fp 被分成了 p − 1 o r d ( c ) \dfrac{p-1}{{\rm ord}(c)} ord(c)p1 个长度为 o r d ( c ) {\rm ord}(c) ord(c) 的环,和 0 0 0 构成的自环。由于 c c c 的作用,选择了 x x x 就可以顺带选择 c − 1 x c^{-1}x c1x(环上下一个点)。因而对于 h ( x ) h(x) h(x) 上连续 l l l 个零点,只需要 ⌈ l 2 ⌉ \left \lceil \dfrac{l}{2}\right \rceil 2l f ( x ) f(x) f(x) 零点即可——若需要连续的 x 1 , x 2 x_1,x_2 x1,x2,其中 x 1 = c x 2   m o d   p x_1=cx_2 \bmod p x1=cx2modp,则可以让 x − x 1 ∣ g ( x ) x-x_1|g(x) xx1g(x),这样 x 2 x_2 x2 就为 g ( c x ) g(cx) g(cx) 的根。因而该问题可以等价于环上选择若干个不相邻的点的方案数,为 ( n − s s ) + ( n − s − 1 s − 1 ) \displaystyle {n-s \choose s}+{n-s-1 \choose s-1} (sns)+(s1ns1),其中 n n n 为环长, s s s f ( x ) f(x) f(x) 的根(不相邻点数目)。最后还原到 h ( x ) h(x) h(x) 的根的选择,即是这 s s s 个后续节点(因为在选择不相邻点的时候,是将该点与下一个点绑定了)可选可不选。同时需要特判 n = 2 s n=2s n=2s n = 2 s − 1 n=2s-1 n=2s1 这两种情况:对于 n = 2 s n=2s n=2s,朴素选点只有两种选法,但是这两种选法下全选方案其实是一样的,需要减掉一种;对于 n = 2 s − 1 n=2s-1 n=2s1,在环上选点方案为 0 0 0,但是其实有一种,且仅有一种: f ( x ) f(x) f(x) 选择 1 , 3 , 5 ⋯   , n 1,3,5\cdots,n 1,3,5,n,对应于 h ( x ) h(x) h(x) 的全选。因而最后可以得到的 f ( x ) f(x) f(x) 中选择 s s s 个零点,对应于 h ( x ) h(x) h(x) 的零点方案数为 2 s ( ( n − s s ) + ( n − s − 1 s − 1 ) ) − [ n = 2 s ] + [ n + 1 = 2 s ] \displaystyle 2^s\left({n-s \choose s}+{n-s-1 \choose s-1}\right) -[n=2s]+[n+1=2s] 2s((sns)+(s1ns1))[n=2s]+[n+1=2s]

考虑 k k k 个环,即是使用生成函数做 k k k 次幂即可。最后选择不超过 n n n f ( x ) f(x) f(x) 的根,因为对于域上的多项式,任意次幂均有不可约多项式。此处需要它不能产生 F p {\Bbb F}_p Fp 上的根,那么如果仅选择了 m m m 个零点,只需要找一个 n − m n-m nm 次的不可约多项式即可,此处 n − m ≥ 2 n-m \geq 2 nm2,因为一次不可约多项式可以产生 F p \Bbb F_p Fp 上的根。

int get_ord(int p, int c)
{
    int val = 1, ord = 0;
    while (!ord || val != 1)
    {
        ord++;
        val = 1ll * val * c % p;
    }
    return ord;
}
long long cal(int n, int s)
{
    int ans = th[s] * (C(n - s, s) + C(n - s - 1, s - 1)) % P;
    if (n + 1 == 2 * s)
        inc(ans, 1);
    if (n == 2 * s)
        dec(ans, 1);
    return ans;
}
int main()
{
    th[0] = 1;
    for (int i = 1; i < L;i++)
        th[i] = th[i - 1] * 2 % P;
    printf("%d %d\n", cal(4, 2), cal(7, 4));
    int t, n, p, c;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d%d%d", &p, &c, &n);
        int len = get_ord(p, c), cnt = (p - 1) / len;
        Poly f(p + 1, 0);
        for (int i = 0; i <= (len + 1) / 2;i++)
            f[i] = cal(len, i);
        Poly g = f.power(p + 1, cnt);
        int ans = 0;
        for (int i = 0; i <= min(p, n);i++)
        {
            inc(ans, g[i]);
            if (i)
                inc(ans, g[i - 1]);
        }
        if (n == 1)
            dec(ans, 1);
        printf("%d\n", ans);
    }
    return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值