出题人题解

出题人题解, 若题解有修改,第一时间见: 传送门

继上一套练习赛104104,这一套卷子依旧是我和ZZ巨组队一起出的。然后还是我端茶倒水,然后帮ZZ巨完成了最后一块拼图(确信)。 我出了A,B,C,EA,B,C,E,然后Z巨出了DD。最后Z巨想出了FF题的95%95\%的idea,我帮其完成了剩余的5%5\%的数位DP思想找具体的那O(logS)\mathcal{O}(\log S)个区间的部分。

然后比赛那天的下午,跟验题人商量了一下临时加强了EE,范围从10510910^5\rightarrow10^9(原本甚至想101810^{18}),以至于赛时的难度E>FE>F

除了CC题过于经典,整套卷子质量海星,指的是E,FE,F海星(雾

A、切蛋糕的贝贝

题意:将一个正n(1n109)n(1\le n\le 10^9)边形分成面积之比为1:1:4:5:1:41:1:4:5:1:4最少需要几刀

因为1+1+4+5+1+4=161+1+4+5+1+4=16,即判断nn是否为1616的倍数,如果不是,则无解。

是的话,则最少仅需55刀即可达到目标,切法如下:

  • 第一刀切穿过重心的对角线,左右两边8:88:8
  • 然后第二刀将左边的等分即切出4:44:4
  • 3,4,53,4,5刀,将右边的分为1:1:1:51:1:1:5
#include <bits/stdc++.h>
using namespace std;

int n;

int main()
{
    cin >> n;
    cout << (n % 16 ? -1 : 5);
}

B、抱歉,这没有集美

题意:求解长度为n(1n106)n(1\le n\le 10^6)的排列pp中,至少存在一个位置满足gcd(pi,i)\text{gcd}(p_i,i)为偶数的排列数量。

本题理论上有相当一部分选手会使用next_permutationnext\_permutation打表,然后推出结论

逆向思维,考虑一个序列,不存在一个ii满足gcd(pi,i)gcd(p_i,i)是偶数,也就是所任意偶数只能放在奇数位置,所以分开考虑奇数位置和偶数位置即可。然后我们对nn的奇偶性进行分类讨论:

  • nn为奇数时,n+12\cfrac{n+1}{2}个奇数位置需要放n12\cfrac{n-1}{2}个偶数和11个奇数,我们从n+12\cfrac{n+1}{2}个数中选出一个奇数,然后两种位置分别全排列,总方案为Cn+121(n+12)!(n12)!=((n+12)!)2C_{\frac{n+1}{2}}^{1}(\cfrac{n+1}{2})!(\cfrac{n-1}{2})!=((\cfrac{n+1}{2})!)^2
  • nn为偶数时,两种位置分别全排列,总方案数为(n2)!(n2)!=((n2)!)2(\cfrac{n}{2})!(\cfrac{n}{2})!=((\cfrac{n}{2})!)^2

故,不存在一个ii满足gcd(pi,i)gcd(p_i,i)是偶数的排列方案有(n+12!)2(\lfloor \cfrac{n+1}{2}\rfloor !)^2

综上,总的方案为n!(n+12!)2n!-(\lfloor \cfrac{n+1}{2}\rfloor !)^2,可以计算阶乘O(n)\mathcal{O}(n)求解答案。

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
typedef long long LL;

constexpr int N = 1e6 + 10, md = 1e9 + 7;

LL fac[N];
void init()
{
    fac[0] = 1;
    rep(i, 1, 1000000)
        fac[i] = fac[i - 1] * i % md;
}
int main()
{
    int i;
    cin >> i;
    init();
    LL ans = fac[i] - fac[(i + 1) / 2] * fac[(i + 1) / 2] % md;
    ans %= md;
    if(ans < 0)
        ans += md;
    cout << ans;
}

C、打牌的贝贝

题意: 两人都持有n(1n106)n(1\le n \le 10^6)张牌,且每张牌的数字都不同。共nn回合,每轮都是BeiBeiBeiBei攻击,NingNingNingNing防守。每回合BeiBeiBeiBei出一张牌,NingNingNingNing必须出一张比当前回合他出的大的牌。如果nnNingNingNingNing都成功防御则其获胜,反之BeiBeiBeiBei获胜。共T(1T106)T(1\le T \le 10^6)组样例,输出二人各自获胜的情况种类。

首先声明下,本题不是博弈题,这不是二者轮流操作的公平组合游戏。

容易得到,先手打出一张牌xx时候,后手一定会打出他目前已有的手牌中,最小的大于xx的牌。

先考虑给定nn时,共有多少种分牌方案,即在2n2n个位置中选取出nn个位置当作先手的牌(位置的编号即是大小,也就已经排序好了),剩余的位置即是后手的牌,即总共的方案数量为C2nnC_{2n}^{n}。考虑后手获胜的情况,我们把先手的牌所处的位置,都换成左括号,后手的牌的位置都换成右括号。这样我们就得到了一个括号序列,当且仅当这是一个合法的括号序列时,后手才能获胜,故后手获胜的情况为卡特兰数CatalannCatalan_n,易得卡特兰数可以O(n)\mathcal{O}(n)预处理出来,然后O(1)\mathcal{O}(1)询问回答。

于是就得到了先手获胜的方案数为C2nnCatalannC_{2n}^n-Catalan_n,我们通过递推式Catalann+1=4n+2n+2CatalannCatalan_{n+1}=\cfrac{4n+2}{n+2}Catalan_n可以计算出卡特兰数。再此之前,我们需要计算出n+2n+2的逆元,这个先O(n)\mathcal{O}(n)预处理阶乘和阶乘的逆元,然后O(1)\mathcal{O}(1)求解,而C2nnC_{2n}^{n}这个数也可以由2n2n的阶乘和nn的阶乘的逆元O(1)\mathcal{O}(1)计算出来。

当然用卡特兰数的这个递推式也是同样的效果C2nnC2nn1C_{2n}^{n}-C_{2n}^{n-1}

总的时间复杂度为O(n+T)\mathcal{O}(n+T)。出题人认为阶乘逆元的预处理复杂度不是该题想考查的重点,故处理阶乘逆元的复杂度放宽到O(nlogn+T)\mathcal{O}(n\log n+T)也能通过,即nn最大仅为10610^6

#include <bits/stdc++.h>
using namespace std;

#define el '\n'
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define lop(i, a, b) for (int i = (a); i < (b); i++)
#define dwn(i, a, b) for (int i = (a); i >= (b); i--)

typedef long long LL;

constexpr int N = 1e6 + 10, md = 1e9 + 7;

int T, n, m;
LL f[N], fac[2 * N], ifac[N], inv[N];

LL fpow(LL a, int b)
{
    LL res = 1;
    while(b)
    {
        if(b & 1)
            res = res * a % md;
        a = a * a % md;
        b >>= 1;
    }
    return res;
}

LL finv(int i)
{   
    return fpow(i, md - 2);
}

void init()
{
    int R = N - 8; // 1e6 + 2
    fac[0] = 1;
    rep(i, 1, 2 * R) //阶乘逆元
        fac[i] = fac[i - 1] * i % md;
    ifac[R] = fpow(fac[R], md - 2);
    dwn(i, R, 2) //阶乘倒数逆元
        ifac[i - 1] = ifac[i] * i % md;
    rep(i, 1, R) //倒数逆元
        inv[i] = fac[i - 1] * ifac[i] % md;
    f[0] = 1;
    R -= 2;//最大的i + 2也被算出来了
    lop(i, 0, R)
    { //计算卡特兰数
        f[i + 1] = f[i] * (4 * i + 2) % md * inv[i + 2] % md;
        //这是一个很坑的点i+2
    }
}

LL C(LL n, LL m)
{//n选m
    return fac[n] * ifac[m] % md * ifac[n - m] % md;
}

int main()
{
    cin.tie(0);
    cout.tie(0);
    cin.sync_with_stdio(false);
    init();
    cin >> T;
    while (T--)
    {
        cin >> n; 
        LL ans1 = ((C(2 * n, n) - f[n]) % md + md) % md;
        cout << ans1 << ' ' << f[n] << el;
    }
}

D、点分治分点

题意:给定一个 nn 个点, mm 条带权的边的有向图,定义一条简单路径的 lowlow 值为其路径上的边权的最小值,d(u,v)d(u, v) 为从 uuvv 所有简单路径的最大 lowlow 值。对于给定的ssuu11nn 输出 d(s,u)d(s, u),如果没有任何一条简单路径则输出 1-1

  1. 我们先把边按照权值排序,从大到小加边。

  2. 每次加一条边 (ui,vi,wi)(u_i, v_i, w_i),如果 uiu_iss 连通,我们就从 uiu_i 开始跑一次 BFSBFS,对于在该次 BFSBFS 中连通的点,它的答案就是 wiw_i

  3. 我们对每个跑过的点打个标记,由于每个点每条边都只会被遍历一次,因此我们计算答案的复杂度为 O(n+m)\mathcal{O}(n+m)

#include <bits/stdc++.h>
using namespace std;

constexpr int N = 1e5 + 10, M = 1e7;

int vis[N], ans[N]; // 答案
vector<int> g[N]; // 存边

struct Edge {
    int u, v, w;
    bool operator < (const Edge &t) const {
        return w > t.w;
    }
} E[N];

void bfs(int s, int w)
{
    vis[s] = 1;
    ans[s] = w;
    queue<int> q;
    q.push(s);
    while(q.size())
    {
        int u = q.front(); q.pop();
        for (auto &v : g[u])
        {
            if(vis[v]) continue;
            vis[v] = 1;
            ans[v] = w;
            q.push(v);
        }
    }
}

void solve()
{
    int n, m, s; // n个点, m条边
    cin >> n >> m >> s;
//     n = m = 1e5,s=1;
    for (int i = 1; i <= m; i ++ )
    {
        int u, v, w;
        cin >> u >> v >> w;
//         u=1,v=i,w=i;
        E[i] = {u, v, w};
    }

    sort(E + 1, E + m + 1); // 按照边权降序

    vis[s] = 1;
    memset(ans, -1, sizeof ans);
    for (int i = 1; i <= m; i ++ )
    {
        auto &[u, v, w] = E[i];
        g[u].push_back(v); // 加边
        // 寻找是否拥有新可达点
        if(vis[u] == 1 && vis[v] == 0) bfs(v, w);
    }

    ans[s] = -1;
    for (int i = 1; i <= n; i ++ )
        cout << ans[i] << " \n"[i == n];
}

int main()
{
//     ios::sync_with_stdio(0), cin.tie(0);
    int t = 1;
    while (t -- ) solve();
    return 0;
}

E、角剖分剖角

牛客编辑器有字数上限,剩余部分见知乎: 传送门

F、胖头鱼头胖

牛客编辑器有字数上限,剩余部分见知乎: 传送门