题解:P13355 [GDCPC 2024] 循环赛

· · 题解

题意简述

用一张竞赛图表示循环赛结果,结点的出度就是得分。

要求任取 z+1 个结点,导出子图中都同时存在全胜者和全败者。求合法竞赛图中最少可能出现多少种不同出度。

解题思路

先处理 z=1。此时每次只选两个结点,两者之间必有一个胜者和一个败者,所以没有额外限制。

竞赛图的出度总和为 n(n-1)/2。当 n 为奇数时,存在每个结点出度都为 (n-1)/2 的正则竞赛图,答案可以为 1。当 n 为偶数时,平均出度不是整数,所有出度不可能相同;删除一个奇数阶正则竞赛图中的结点,可以得到只含两种出度的近正则竞赛图。因此:

\operatorname{ans}=1+[2\mid n]

下面设 z\ge2

将竞赛图的强连通分量缩点。任意两个分量之间的边方向一致,否则两个分量能够互相到达。缩点后仍是一张竞赛图,又因为它是有向无环图,所以各分量构成唯一方向的全序。

按照后面的分量战胜前面的分量排列这些强连通分量。大小为 2 的竞赛图不强连通,因此每个分量的大小为 1 或至少为 3

需要限制非单点强连通分量的位置。

强连通竞赛图存在经过全部结点的有向环,并且存在从 3 到分量大小的每一种长度的有向环。若某个分量大小超过 z,就在其中选择一个长度为 z+1 的有向环。环上每个结点至少有一场胜利和一场失败,所选结点中便没有全胜者和全败者,与题目条件矛盾。因此每个分量的大小都不超过 z

考虑一个大小 s\ge3 的强连通分量。设它之前、之后分别有 L,R 个结点。

L+s>z,选择该分量的全部 s 个结点,再从前面选择 z+1-s 个结点。分量内部的哈密顿环保证每个分量结点都会输给另一个分量结点;前面的结点又会输给整个分量。因此所选结点中没有全胜者,产生矛盾。于是:

L+s\le z

对全败者作对称讨论,可以得到:

s+R\le z

p=n-z。因为 n=L+s+R,上面两个不等式等价于:

\begin{aligned} L & \ge p \\ R & \ge p \end{aligned}

也就是说,任何非单点分量之前和之后都至少有 p 个结点。因此,全序中最前面的 p 个结点和最后面的 p 个结点都只能各自构成单点分量。

2p\ge n,前后两段已经覆盖全部结点。此时所有强连通分量都是单点,整张竞赛图只能是传递竞赛图。其出度依次为 0,1,\dots,n-1,答案为 n。该条件也就是 2z\le n

下面考虑 2p<n。除去前后各 p 个单点分量,中间还剩:

r=n-2p=2z-n

p 个单点分量的出度依次为 0,1,\dots,p-1。中间每个结点都战胜前面的 p 个结点,又输给后面的 p 个结点,所以它的总出度等于内部出度加 p,取值范围为 [p,p+r-1]。最后 p 个单点分量的出度依次为 p+r,p+r+1,\dots,n-1

三段的出度范围互不相交。因此,前后两段固定产生 2p 种出度,只需让中间竞赛图的内部出度种类尽量少。

r 为奇数时,中间使用正则竞赛图,只产生一种内部出度。当 r 为偶数时,平均内部出度 (r-1)/2 不是整数,至少需要两种出度;从一个 r+1 阶正则竞赛图中删除一个结点,即可构造只含两种出度的近正则竞赛图。

由于 r=2z-nn 的奇偶性相同,中间最少产生 1+[2\mid n] 种出度。总答案为:

2p+1+[2\mid n]=2(n-z)+1+[2\mid n]

还需验证上述构造满足题目条件。任取 z+1 个结点,其补集只有 n-z-1=p-1 个结点,所以前 p 个单点分量中至少选到一个结点,后 p 个单点分量中也至少选到一个结点。所选结点里,全序位置最靠后的单点战胜其余所有结点,最靠前的单点输给其余所有结点,条件成立。

综上:

\operatorname{ans}= \begin{cases} 1+[2\mid n] & z=1 \\ n & z\ge2,2z\le n \\ 2(n-z)+1+[2\mid n] & z\ge2,2z>n \end{cases}

每组数据只进行常数次运算,时间复杂度为 O(1),空间复杂度为 O(1)

参考代码

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

using ll=long long;
ll solve(ll n,ll z)
{
    if(z==1)return 1+(n%2==0);
    ll p=n-z;
    if(2*p>=n)return n;
    return 2*p+1+(n%2==0);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
    {
        ll n,z;
        cin>>n>>z;
        cout<<solve(n,z)<<'\n';
    }
    return 0;
}