FXJ Wiki

Back

收录范围#

  • CF 1931 Div.3 D
  • CF 1931 Div.3 E
  • CF 1931 Div.3 G
  • CF 2008 Div.3 G
  • CF 2008 Div.3 H
  • CF 1925 Div.2 B
  • CF 1928 Div.2 C
  • CF 1928 Div.2 D
  • CF 1928 Div.2 E
  • CF 2004 EDU Div.2 D
  • CF 2004 EDU Div.2 E
  • CF 1957 Div.2 C
  • CF 1957 Div.2 D
  • CF 1957 Div.2 E
  • CF 1935 Div.2 D
  • CF 1997 EDU Div.2 D

CF 1931 Div.3 D - Divisible Pairs#

给出一个数组 aa,求满足 (ai+aj)%x=0(aiaj)%y=0(i<j)(a_{i}+a_{j})\%x=0\cap(a_{i}-a_{j})\%y=0(i<j) 的个数。

Solution#

易得:

(ai%x+aj%x)%x=0,(ai%yaj%y)%y=0(a_{i}\%x+a_{j}\%x)\%x=0,(a_{i}\%y-a_{j}\%y)\%y=0 ai%x+aj%x=x,ai%yaj%y=0\to a_{i}\%x+a_{j}\%x=x,a_{i}\%y-a_{j}\%y=0

存下 ai%x,ai%ya_{i}\%x,a_{i}\%y,满足 (xai%x)%xai%y(x-a_{i}\%x)\%x\cap a_{i}\%y 即为对应的条件

void solve() {
    int n, x, y;cin >> n >> x >> y;
    vector<int> a(n);
    for (auto& i : a)cin >> i;
    ll cnt = 0;
    map<pair<int, int>, int> mod;
    for (int i = 0;i < n;i++) {
        cnt += mod[{(x - a[i] % x) % x, a[i] % y}];
        mod[{a[i] % x, a[i] % y}]++;
    }
    cout << cnt << '\n';
}
cpp

void solve() {
    int n, x, y;cin >> n >> x >> y;
    vector<int> a(n);
    for (auto& i : a)cin >> i;
    ll cnt = 0;
    map<pair<int, int>, int> mod;
    for (int i = 0;i < n;i++) {
        mod[{a[i] % x, a[i] % y}]++;
    }
    for (int i = 0;i < n;i++) {
        --mod[{a[i] % x, a[i] % y}];
        cnt += mod[{(x - a[i] % x) % x, a[i] % y}];
        ++mod[{a[i] % x, a[i] % y}];
    }
    cout << cnt / 2 << '\n';
}
cpp

CF 1931 Div.3 E - Anna and the Valentine’s Day Gift#

给出一个长度为 nn 的数组 aa,Anna 先手

  • Anna 选择一个 aia_{i}\to reverse(ai)reverse(a_{i}),去掉前导 0
  • Sasha 选择 ai,aja_{i},a_{j}\to cat(ai,aj)cat(a_{i},a_{j})

当 Anna 下完棋且数组中只剩下一个元素的时候,游戏结束,如果剩下的数字 10m\geq 10^m,Sasha 获胜,反之,Anna 获胜。

双方都以最佳方式玩游戏,输出获胜方。

Solution#

博弈论

容易发现,Anna 每次优先翻转 后导 0 最多的数,Sasha 每次优先以 有最多后导 0 的数 作为 aia_{i}

a={10,10,10,10}.m=5a=\{10,10,10,10\}.m=5 为例执行流程:

  • Anna:1,10,10,101,10,10,10\to Sasha:1,1010,101,1010,10
  • Anna:1,1010,11,1010,1\to Sasha:1,101011,10101
  • Anna:1,101011,10101\to Sasha:110101110101
  • Anna:101011101011\to Game-Over\text{Game-Over}101011105101011\geq 10^5\to Sasha WIN

即每次 Anna 删除一个最多的,Sasha 保留一个第二多的,以此类推。

比较简单。

void solve() {
    int n, m;cin >> n >> m;
    vector<pair<int, int>> a(n);for (auto& [i, j] : a)cin >> j;
    int sum = 0;
    for (int i = 0;i < n;i++) {
        string num = to_string(a[i].second);
        sum += num.size();
        int cnt = 0;
        for (int i = num.size() - 1;i >= 0;i--) {
            if (num[i] == '0')cnt++;
            else break;
        }
        a[i].first = cnt;
    }
    sort(a.begin(), a.end());
    for (int i = n - 1;i >= 0;i -= 2) {
        sum -= a[i].first;
    }
    if (sum - 1 >= m)cout << "Sasha\n";
    else cout << "Anna\n";
}
cpp

CF 1931 Div.3 G - One-Dimensional Puzzle#

每种类型包含 a,b,c,da,b,c,d 个元素。如果成功地将所有元素组合成一条长链,就算完成了。求有多少种方法(不能翻转)。

Solution#

组合数 (隔板法)

Codeforces Round 925 (Div. 3) A-G 比赛录屏+讲解(60分钟开始)_哔哩哔哩_bilibili

注意:本题写的 C(x,y)C(x,y) 写为了 C(y,x)C(y,x) (写反了 \dots)

前置小练习:

eg1:\text{eg1:} 将 10 个相同的小球放入 8 个盒子里(每个盒子至少有一个小球),有多少种放法?

插空法:10 个小球有 9 个空可以插,有 7 个隔板, (即两块板之间不能相邻且第一块板左侧与最后一块板右侧必须有球)。

则答案 C(7,9)C(7,9)

eg2:\text{eg2:} 将 10 个相同的小球放入 8 个盒子里 (盒子可以为空),有多少种放法?

隔板法:10 个小球和 7 个隔板,可以组成一个由 17 个物件的排列,从中选择 7 个位置放置隔板,这样就可以把小球分配到 8 个盒子中

答案:C(7,17)C(7,17)

eg3:\text{eg3:}x1+x2+x3+x4=10x_{1}+x_{2}+x_{3}+x_{4}=10 有多少个正整数解?

即在 10 个 1 之间的 9 个空里选择 3 个位置插入隔板,答案:C(3,9)C(3,9)

eg4:\text{eg4:}x1+x2+x3+x4=10x_{1}+x_{2}+x_{3}+x_{4}=10 有多少个非负整数解?

法一:可以变换一下思路:这时的 xi0x_{i}\geq 0,将等式两边同时加上 4,则 xi1x_{i}\geq 1x1+x2+x3+x4=14x_{1}+x_{2}+x_{3}+x_{4}=14 的正整数解

法二:将 3 个隔板和 10 组成一个 13 件物品的排列,选择 3 个位置放置隔板

答案:C(3,13)C(3,13)

则可以抽象:

x1+x2+x3++xr=nx_{1}+x_{2}+x_{3}+\dots+x_{r}=n 的非负整数解个数以及正整数解个数。

(1):(1): C(r1,r+n1)C(r-1,r+n-1)

(2):C(r1,n1)(2):C(r-1,n-1)

nn 个相同的小球放入 rr 个盒子里, (盒子可以为空和至少一个)各有多少种放法?

(1):C(r1,n+r1)(1):C(r-1,n+r-1)

(2):C(r1,n1)(2):C(r-1,n-1)

可以在 1,21,2 之间插入 33,在 2,12,1 之间插入 44

  • 121212121212
  • 212121212121
  • 333333333333 放在 1122 之间
  • 444444444444 放在 2211 之间
  • 133332444413333244441\textcolor{red}{1}\textcolor{green}{333\dots3}\textcolor{red}{2}\textcolor{gray}{444\dots4}\textcolor{red}{1}\textcolor{green}{333\dots3}\textcolor{red}{2}\textcolor{gray}{444\dots4}\textcolor{red}{1}

本题目插入的意思 ,设空位个数为 mm

  1. 先考虑把同类元素(cc 个 3 或 dd 个 4)放到 mm 个空位中(可以为空)的方法个数
  2. x1+x2+x3++xm=cdx_{1}+x_{2}+x_{3}+\dots+x_{m}=c|d 非负整数解个数

下面的个数为 C(m1,m+cd1)C(m-1,m+c|d-1),由于插入 343|4 不冲突,所以直接相乘。

a=ba=b

  • 1212121212121212 空位: 3:a,4:a+13:a,4:a+1
  • 2121212121212121 空位: 3:a+1,4:a3:a+1,4:a

方案数:C(a1,a+c1)×C(a,a+d)+C(a,a+c)×C(a1,a+d1)C(a-1,a+c-1)\times C(a,a+d)+C(a,a+c)\times C(a-1,a+d-1)

a=b1a=b-1

  • 21212122121212 空位: 3:a+1,4:a+13:a+1,4:a+1

方案数:C(a,a+c)×C(a,a+d)C(a,a+c)\times C(a,a+d)

a=b+1a=b+1

  • 12121211212121 空位: 3:a,4:a3:a,4:a

方案数:C(a1,a+c1)×C(a1,a+d1)C(a-1,a+c-1)\times C(a-1,a+d-1)

然后就是预处理与逆元的计算。这里把阶乘、逆元阶乘预处理到上界之后,所有组合数都可以 O(1)O(1) 取出;若不想写线性逆元,直接快速幂也能过,只是常数稍大一些。

const int mod = 998244353, N = 2e6 + 10;
ll fac[N], jv[N], inv[N];
void init(int n) {
    jv[0] = fac[0] = 1;
    for (int i = 1;i <= n;i++) {
        inv[i] = i == 1 ? 1 : (mod - mod / i) * inv[mod % i] % mod;
        fac[i] = fac[i - 1] * i % mod;
        jv[i] = jv[i - 1] * inv[i] % mod;
    }
}
ll C(int m, int n) {
    if (n < m || m < 0) return 0;
    return fac[n] * jv[n - m] % mod * jv[m] % mod;
}
void solve() {
    int a, b, c, d;cin >> a >> b >> c >> d;
    if (abs(a - b) >= 2) {
        cout << 0 << '\n';return;
    }
    ll ans = 0;
    if (!a && !b)ans = c == 0 || d == 0;
    else if (a == b) {
        ans = (C(a - 1, a + c - 1) * C(a, a + d) % mod + C(a, a + c) * C(a - 1, a + d - 1) % mod) % mod;
    } else if (a == b - 1) {
        ans = C(a, a + c) * C(a, a + d) % mod;
    } else if (a == b + 1) {
        ans = C(a - 1, a + c - 1) * C(a - 1, a + d - 1) % mod;
    }
    cout << ans << '\n';
}
main::init(2e6);
cpp

CF 2008 Div.3 G - Sakurako’s Task#

樱子为你准备了一项任务:

她给了你一个由 nn 整数组成的数组,让你选择 iijj 这样的 iji \neq jaiaja_i \ge a_j ,然后指定 ai=aiaja_i = a_i - a_jai=ai+aja_i = a_i + a_j 。只要满足条件,您可以对任意 iijj 执行任意次数的操作。

樱子问你,数组的 mexkmex_k ^{*} 在任意多次操作后的最大可能值是多少?

^{*} mexkmex_k 是数组中不存在的第 kk 个非负整数。例如:mex1({1,2,3})=0mex_1(\{1,2,3\})=0,因为 00 是数组中不存在的第一个元素;mex2({0,2,4})=3mex_2(\{0,2,4\})=3,因为 33 是数组中不存在的第二个元素。

Solution#

  • 需要证明: 根据操作,可以得到最终的的数组是 ai=(i1)×gcd({ai})a_{i}=(i-1)\times \gcd(\{a_{i}\})
  • 得到了这个数组,如何以一种较快的方式求出 mexk\text{mex}_{k}

最终能凑出来的是 0,g,2g,3g,(n1)g0,g,2g,3\mathbf{g}\dots,(n-1)g

在每个间隔有 g1g-1 个数没有凑出,一共有 (n1)(n-1)(g1)(g-1) 凑不出来。

如果大于了这个数,说明已经超出了 (n1)g(n-1)g,在范围外面还有 k(n1)(g1)k-(n-1)(g-1) 个数, 即 (n1)g+k(n1)(g1)=n1+k(n-1)g+k-(n-1)(g-1)=n-1+k,否则同理

按照这种方式来求 mexk\text{mex}_{k}

#define int long long
void solve() {
    int n, k;cin >> n >> k;
    vector<int> a(n + 1);
    for (int i = 1;i <= n;i++)cin >> a[i];
    int g = a[1];
    for (int i = 1;i <= n;i++)g = __gcd(g, a[i]);
    if (n == 1) {
        cout << (a[1] >= k ? k - 1 : k) << '\n';return;
    }
    int t = (g - 1) * (n - 1);
    if (k > t) {
        cout << n - 1 + k << '\n';
    } else {
        if (k % (g - 1) == 0) {
            cout << k / (g - 1) * g - 1 << '\n';
        } else {
            cout << k / (g - 1) * g + k % (g - 1) << '\n';
        }
    }
}
cpp

CF 2008 Div.3 H - Sakurako’s Test#

樱子即将参加一项测试。测试可以描述为一个整数数组 nn 和一个任务:

给定一个整数 xx ,樱子可以执行以下操作任意多次:

  • 选择一个整数 ii ( 1in1\le i\le n ),使得 aixa_i\ge x
  • aia_i 的值改为 aixa_i-x

任意多次使用此操作,她必须找出数组 aa 的最小中值 ^{*}

樱子知道数组,但不知道整数 xx 。有人说漏了嘴, xxqq 值中有一个会出现在下一次测试中,所以樱子问你每个这样的 xx 的答案是什么。

^{*} 长度为 nn 的数组的中位数是位于排序数组中间的元素(对于偶数 nn,位于 n+22\frac{n+2}{2}-th 位置;对于奇数,位于 n+12\frac{n+1}{2}-th 位置)。

Solution#

调和级数复杂度<->mod Q

根据贪心的思想,aiaimodxa_{i}\to a_{i} \bmod x

提前预处理出 x[1,n]x\in[1,n] 的所有情况,对于每一种情况,答案范围是 [0,x1][0,x-1]

可以二分答案(对于 xx,数组 aa 的最小中位数),对于每一个 midmid,可以计算出余数为 [0,mid][0,mid] 区间内的数的个数,只需要判断个数是否占 n2+1\lfloor{\frac{n}{2}}\rfloor+1 个即可。

时间复杂度: O(nlog2n)O(n\log^2n)

void solve() {
    int n, q;cin >> n >> q;
    vector<int> mp(n + 1);
    for (int i = 1;i <= n;i++) {
        int x;cin >> x;mp[x]++;
    }

    for (int i = 1;i <= n;i++)mp[i] += mp[i - 1];
    vector<int> ans(n + 1);

    for (int i = 1;i <= n;i++) {//x
        int l = 0, r = i - 1;
        while (l < r) {
            int mid = l + r >> 1;
            int s = 0;
            for (int j = 0;j <= n;j += i) {
                s += mp[min(n, j + mid)] - (j == 0 ? 0 : mp[j - 1]);
            }
            if (s >= n / 2 + 1) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        ans[i] = l;
    }
    while (q--) {
        int x;cin >> x;
        cout << ans[x] << ' ';
    }
    cout << '\n';
}
cpp

CF 1925 Div.2 B - A Balanced Problemset?#

xx 分解为 nn 个整数组成,使得这 nn 个数的 gcd 最大,也就是最平衡。

Solution#

想了半天没想出来 \dots

没太搞懂 \dots

这段代码已经被 HACK 了,现在提交会 T ![](./Pasted image 20240128171314.png)

void solve()
{
    int n, x;
    cin >> x >> n;
    if (n == 1)
    {
        cout << x << '\n';
        return;
    }
    for (int i = x / n; i >= 1; i--)
    {
        if (x % i == 0 && x / i >= n)
        {
            cout << i << '\n';
            return;
        }
    }
}
cpp

有: GCD(a1,a2,a3,,an)=GCD(a1,a1+a2,a1+a2+a3,,a1+a2+a3++an)GCD(a_1,a_2,a_3,\ldots,a_n) = GCD(a_1,a_1+a_2,a_1+a_2+a_3,\ldots,a_1+a_2+a_3+\ldots+a_n) GCD(a1,a1+a2,a1+a2+a3,,x)\leftrightarrow GCD(a_1,a_1+a_2,a_1+a_2+a_3,\ldots,x)

所以答案一定是 xx 的一个约数。

现在,考虑 xx 的一个因数 dd,我有两种想法 (1)(1)

  • n×dxn\times d\leq x,选择为:d,d,d,,x(n1)dgcd(d,2d,3d..,x)d,d,d,\ldots,x-(n-1)d\leftrightarrow \gcd(d,2d,3d..,x) 有可行答案 dd
  • ndn\leq d,有可行答案 x//dx//d(本来每一份是 xn,n\frac{x}{n},n 份,这时 ndn\leq d,如果分为 dd 份每一份就是 xd\frac{x}{d} 可以分为更多份,每一份也更大,n\geq n 的部分可以直接安到任意位置,这时的 xd\frac{x}{d} 也可以作为答案)

(2)(2)

  • 直接遍历因子 11xx,然后判断 n×dxn \times d \le x 并取 dd,会超时
  • 只遍历因子 11x\sqrt{x},再分别检查 ddx/dx / d 是否满足条件,这样就能把所有因子都覆盖到。

找到满足该条件的最大 dd。这可以在 O(x)\mathcal{O}(\sqrt{x}) 时间内通过简单的因数分解来实现。

![](./Pasted image 20240128180042.png)

void solve()
{
    int n, x;
    cin >> x >> n;
    int ans = 1;
    for (int i = 1; i * i <= x; i++)
    {
        if (x % i == 0)//如果i是x的因子
        {
            if (n <= x / i)//n*i<=x ->i i i ... x-(n-1)*i  ->i
                ans = max(ans, i);
            if (n <= i)
                ans = max(ans, x / i);
        }
    }
    cout << ans << '\n';
}
cpp

CF 1928 Div.2 C - Physical Education Lesson#

每个人都排成一排,并被要求站在 “第 kk 个 “的位置上。

1k,k12,1k,k12,1\sim k,k-1\sim 2,1\sim k,k-1\sim 2,\dots 以此类推。这样,每隔 2k22k - 2 个位置就重复一次。( k1k \neq 1)

给出在队伍中的位置 nn结算时得到的数字 xx,输出有多少个符合条件的 kk

eg n=10,x=2n=10,x=2k\to k = 2,3,5,62, 3, 5, 6

解决这些问题的例子是 kk

kk / №1122334455667788991010
2211221122112211221122
3311223322112233221122
5511223344554433221122
6611223344556655443322

Solution#

  • 当在左边部分的时候:只需要满足 n%(2k2)=xn\%(2 k-2)=x 即可(当为 0 时需要特判为 2)
  • 反之满足 2kn%(2k2)=x2 k-n\%(2 k-2)=x 即可

暴力做法:(TLE)由于是 O(n)O(n) (n109n\leq 10^9)的做法肯定会 T

void solve() {
    int n, x;cin >> n >> x;
    int ans = 0;
    for (int k = 2;k <= n;k++) {
        int m = n % (2 * k - 2);
        if (!m) m = 2;
        if (m <= k) {
            if (m == x) ans++;
        } else {
            m = 2 * k - m;
            if (m == x) ans++;
        }
    }
    cout << ans << '\n';
}
cpp

由上文:kk 满足 (2k2)×t+x=n(2k2)×t+2kx=n(2 k-2)\times t+x=n\cup(2k-2)\times t+2k-x=n

可以变形为:(2k2)(nx)(2k2)(n+x2k)(2k2)(n+x2)(2k-2)|(n-x)\cup(2k-2)|(n+x-2k)\to(2k-2)|(n+x-2)

所以只需要判断哪些数是 (nx)(n+x2)(n-x)\cup(n+x-2) 的偶数 (由于(2k-2)一定是偶数) 因子,即代表 (2k2)(2k-2),所以存入的时候存 kki2+1\frac{i}{2}+1

kkxx 还满足 kxk\geq x

NOTE: 试除法:要找到某个数的所有因子只需要 n\sqrt{ n }ii 是因子的情况下加入 i,i, ni\frac{n}{i} 即:

(不过当 nn 为平方数的时候会有重叠,将 vector 换为 set 去重即可)

auto find = [&](int n) {
	vector<int> ans;
	for (int i = 1;i * i <= n;i++) {
		if (n % i == 0) {
			ans.push_back(i);
			ans.push_back(n / i);
		}
	}
	return ans;
};
cpp

unordered_set 可换为 set。但是 unordered_set 的插入是 O(1)O(1)

void solve() {
    int n, x;cin >> n >> x;
    unordered_set<int> can;
    auto find = [&](int a) {
        unordered_set<int> ans;
        for (int i = 1;i * i <= a;i++) {
            if (a % i == 0) {
                if (i % 2 == 0)can.insert(1 + i / 2);
                if ((a / i) % 2 == 0)can.insert(1 + (a / i) / 2);
            }
        }
        return ans;
    };
    for (auto i : find(n - x))can.insert(i);
    for (auto i : find(n + x - 2))can.insert(i);
    int ans = 0;
    for (auto i : can) {
        if (i >= x)ans++;
    }
    cout << ans << '\n';
}
cpp

CF 1928 Div.2 D - Lonely Mountain Dungeons#

中土世界居民的军队将由几个小队组成。众所周知,每一对同种族的生物分属不同的小队,军队的总兵力就会增加 bb 个单位。但由于提摩西很难领导一支由大量小队组成的军队,因此由 kk 个小队组成的军队的总兵力会减少 (k1)x(k - 1) \cdot x 个单位。注意,军队总是由个至少一个班组成。

nn 个种族,而 ii 个种族的生物数量等于 cic_i 。确定他们所能组建的军队的最大兵力。

Solution#

三分(先单增,再单减)组合数

注意:long long 的计算稍不注意就会wa

三分这里的关键不是模板本身,而是先证明 check(k) 呈现“先增后减”的单峰形态。因为随着队伍数增加,每个种族内部贡献的变化是平滑的,总答案可以按单峰函数处理,于是可以安全三分,再在收缩后的短区间里暴力枚举收尾。

codeforces round 924 (Div2) A-E 思路分享_哔哩哔哩_bilibili

对于计算分成 kk 个队伍时的战斗力(每个种族其实是独立的,所以可以分开计算):

先算出 ik\frac{i}{k} 的整数部分,意味着每个队伍都至少有 ik\lfloor{\frac{i}{k}}\rfloor 个人

当每个队伍都是 ik\lfloor{\frac{i}{k}}\rfloor 个人,这时的贡献:ik×(k1)×ik×k2\frac{\lfloor{\frac{i}{k}}\rfloor\times(k-1)\times \lfloor{\frac{i}{k}}\rfloor\times k}{2}

对于一个人来说,其他队伍的人都有助于贡献:ik×(k1)\lfloor{\frac{i}{k}}\rfloor\times(k-1)

对于和他同一个队伍的人来说:ik×(k1)×ik\lfloor{\frac{i}{k}}\rfloor\times(k-1)\times \lfloor{\frac{i}{k}}\rfloor

其他队伍的也是一样,所以这时的贡献:ik×(k1)×ik×k2\frac{\lfloor{\frac{i}{k}}\rfloor\times(k-1)\times \lfloor{\frac{i}{k}}\rfloor\times k}{2}

余数部分,只有部分队伍仅有 1 人,贡献:i%k×(i%k1)2\frac{i\%k\times(i\%k-1)}{2}

余数和整数部分的接壤:则贡献:(k1)×ik×i%k(k-1)\times \lfloor{\frac{i}{k}}\rfloor\times i\%k

对于后加入的余数部分,之前整数部分和他不在同一个队伍的 (k1)×ik(k-1)\times \lfloor{\frac{i}{k}}\rfloor 个人需要算上贡献

则该种族的战斗力ik×(k1)×ik×k2+i%k×(i%k1)2+(k1)×ik×i%k(k1)×x\textcolor{green}{\frac{\lfloor{\frac{i}{k}}\rfloor\times(k-1)\times \lfloor{\frac{i}{k}}\rfloor\times k}{2}+\frac{i\%k\times(i\%k-1)}{2}+(k-1)\times \lfloor{\frac{i}{k}}\rfloor\times i\%k-(k-1)\times x}

void solve() {
    int n, b, x;cin >> n >> b >> x;
    vector<int> c(n);for (int i = 0;i < n;i++)cin >> c[i];
    auto check = [&](ll k) {//计算分成k个队伍时的战斗力
        ll ans = 0;
        for (auto i : c) {
            ll t = i / k, tt = i % k, res = 0;
            res += t * t * k * (k - 1) / 2;
            if (tt > 0) {//可忽略这个条件
                res += tt * (tt - 1) / 2;
                res += tt * t * (k - 1);
            }
            ans += res * b;
        }
        ans -= (k - 1) * x;
        return ans;
    };
    ll l = 1, r = *(max_element(c.begin(), c.end()));
    while (r - l + 1 > 3) {//这段代码的主要目的是将l,r区间缩小到足够小了方便之后枚举
        ll lmid = l + (r - l + 1) / 3;
        ll rmid = r - (r - l + 1) / 3;
        if (check(lmid) > check(rmid))r = rmid;//目的为了缩减区间长度
        else l = lmid;
    }
    ll ans = 0;
    for (ll i = l;i <= r;i++)ans = max(ans, check(i));
    cout << ans << '\n';
}
cpp

D_哔哩哔哩_bilibili (我认为最简单的思路):在 c[i]c[i] 中会有一些重复的数字,如果依次枚举的话重复的数字就重复计算了,所以可以算出之后乘以个数即可。

复杂度:最坏情况 (有 mm 个种族人数不同):1,2,3,,m1,2,3,\dots,m 互不相同 c=m(m+1)2m22×105n\sum c=\frac{m(m+1)}{2}\in m^2\leq 2\times10^5\in n, O(mn)=O(nn)\to O(mn)=O(n\sqrt{ n })

注:

  • num 数组只能开在外面,开在里面会 T ,要将 num 数组放里面,需要将 num 的大小改为 max(ci)\max(c_{i})
vector<int> num(2e5 + 1);
void solve() {
    int n, b, x;cin >> n >> b >> x;
    unordered_set<int> c;
    for (int i = 0;i < n;i++) {
        int x;cin >> x;
        c.insert(x);
        num[x]++;
    }
    auto check = [&](ll k) {
        ll ans = 0;
        for (auto i : c) {
            ll t = i / k, tt = i % k, res = 0;
            res += t * t * k * (k - 1) / 2;
            res += tt * (tt - 1) / 2;
            res += tt * t * (k - 1);
            ans += res * b * num[i];
        }
        ans -= (k - 1) * x;
        return ans;
    };
    ll l = 1, r = *(max_element(c.begin(), c.end())), ans = 0;
    for (ll i = l;i <= r;i++)
        ans = max(ans, check(i));
    cout << ans << '\n';
    for (auto p : c)num[p] = 0;
}
cpp

Codeforces Round 924 (Div. 2) A - E - 知乎

可以看出,最佳方案肯定是将每个种族均匀分配到每个队伍中。

假设某个种族有 cic_{i} 个,我们可以暴力枚举队伍数为 [1,ci1][1,c_{i}-1] 时对答案的贡献,对于 [ci,c]\left[ c_{i},\sum c \right] 区间贡献是确定的,可以使用静态区间加实现。

由于 c\sum c 有限制,所以这种方法可以在 O(c)O\left( \sum c \right) 的时间内完成。

void solve() {
    int n, b, x, m = 0;cin >> n >> b >> x;
    vector<int>c(n);for (int i = 0;i < n;i++)cin >> c[i], m += c[i];
    vector<ll>d(m + 1);
    auto add = [&](int l, int r, ll x) {
        d[l] += x;
        if (r + 1 <= m)d[r + 1] -= x;
    };
    for (auto x : c) {
        for (int i = 1;i < x;i++) {
            ll sum = 1ll * x * (x - 1);
            int t = x / i, r = x % i;
            sum -= (i - r) * 1ll * t * (t - 1);
            sum -= r * 1ll * (t + 1) * t;
            add(i, i, sum / 2);
        }
        add(x, m, 1ll * x * (x - 1) / 2);
    }
    ll ans = 0;
    for (int i = 1;i <= m;i++) {
        d[i] += d[i - 1];
        ans = max(ans, d[i] * b - 1ll * (i - 1) * x);
    }
    cout << ans << '\n';
}
cpp

D_哔哩哔哩_bilibili 平均分配最优,先排序,枚举队伍数量,种族数量小的下次不算了,数量大的在算一遍,直到不会被更新

#define int long long
void solve() {
    int n, b, x;cin >> n >> b >> x;
    vector<int> a(n);for (auto& i : a)cin >> i;sort(a.begin(), a.end());
    int maxx = 1e9, divi = 0, hascal = 0;
    int ans = 0;
    for (int i = 2;divi <= n - 1 && i <= maxx;i++) {
        int res = 0;
        for (int j = divi;j <= n - 1;j++) {
            if (a[j] <= i) {
                hascal += b * (a[j] * (a[j] - 1)) / 2;
                divi++;
            } else {
                int cnt = a[j] / i, mod = a[j] % i;
                int a1 = i - mod, a2 = mod;
                res += b * a[j] * (a[j] - 1) / 2;
                res -= b * a1 * cnt * (cnt - 1) / 2;
                res -= b * a2 * (cnt + 1) * cnt / 2;
            }
        }
        res += hascal;
        res -= (i - 1) * x;
        ans = max(ans, res);
    }
    cout << ans << '\n';
}
cpp

CF 1928 Div.2 E - Modular Sequence#

给你两个整数 xxyy 。长度为 nn 的序列 aa 如果是 a1=xa_1=x ,且对于所有 1<in1 < i \le naia_{i} 值要么是 ai1+ya_{i-1} + y 要么是 ai1modya_{i-1} \bmod y

判断是否存在长度为 nn 的模数序列,其元素之和等于 SS ,如果存在,请找出任何这样的序列。

Solution#

DP

暴力搜索O(2n)O(2^n)

void solve() {
    int n, x, y, s;cin >> n >> x >> y >> s;vector<int> a(n, 0);a[0] = x;
    ll sum = x;
    auto dfs = [&](auto self, int i, ll currSum) -> bool {
        if (currSum > s) return false; // 前缀和剪枝
        if (i == n) return currSum == s;
        a[i] = (a[i - 1] + y) % y;
        if (self(self, i + 1, currSum + a[i])) return true;
        a[i] = a[i - 1] + y;
        if (self(self, i + 1, currSum + a[i])) return true;
        return false;
    };
    if (dfs(dfs, 1, sum)) {
        cout << "yes\n";for (int i = 0;i < n;i++)cout << a[i] << " \n"[i == n - 1];
    } else {
        cout << "no\n";
    }
}
cpp

官方题解:

我们先来看答案的形式:首先会有形如 x,x+y,,x+kyx, x+y, \ldots, x+k\cdot y 的前缀,然后会有一些形如 xmody,xmody+y,,xmody+kyx \bmod y, x \bmod y+y, \ldots, x \bmod y+k\cdot y 的块。

我们可以从序列的所有元素中减去 xmodyx \bmod y,然后将所有元素除以 yy(所有元素最初的余数都为 xmodyx\bmod y,因此它们都是可被 yy 整除的)。设 b1=xxmodyyb_1 = \frac{x-x\bmod y}{y}。那么我们的序列将以 b1,b1+1,,b1+k1b_1, b_1+1, \ldots, b_1+k_1 开头,然后会有形如 0,1,,ki0,1,\ldots,k_i 的块。

现在让我们计算这些值:dpidp_i 表示形如 0,1,,kj0,1, \ldots, k_j 且和为 ii 的块序列的最小长度。我们可以利用动态规划计算从 00ss 的所有数的这个值。如果我们已经处理了从 00k1k-1 的所有值,那么对于 kk,我们已经计算出了最小长度,并且我们可以更新 k+1,k+1+2,k+1, k+1+2,\ldotsdpdp 值,总共不超过 ss 的值有 O(s)O(\sqrt{s}) 个。在同一个 dpdp 中,我们可以记录经过哪些值进行了重新计算,以便恢复答案。

现在,我们可以遍历形如 b1,b1+1,,b1+k1b_1, b_1+1,\ldots,b_1+k_1 的第一个块的长度。然后我们就知道剩余块的和,并利用预先计算的 dpdp,我们可以确定是否能够形成所需的序列。

codeforces round 924 (Div2) A-E 思路分享_哔哩哔哩_bilibili

序列形式如下图(当全部都减去 x%yx\%y,并除以 yy)

第一部分 x,x+y,x+k1yx,x+y,\dots x+k_{1}y\to xx%yy,xx%yy+1,xx%yy+2,,xx%yy+k1\frac{x-x\%y}{y},\frac{x-x\%y}{y}+1,\frac{x-x\%y}{y}+2,\dots,\frac{x-x\%y}{y}+k_{1}

其余部分 x%y,x%y+y,x%y+kiyx\%y,x\%y+y,\dots x\%y+k_{i}y\to 0,1,2,ki0,1,2,\dots k_{i}

dp[x]={达到这个面积最少的长度,从哪里转移过来,最后高度}

要满足条件必须满足:dp[s][0]&lt;=n

这里继续往下推,其实就是一个背包式 DP:dp[s] 维护凑出面积 s 时所需的最小长度、转移来源和最后一层高度。只要最后 dp[s][0] <= n,就说明能在长度限制内构造成功,再按记录的前驱回溯方案即可。

![](./Pasted image 20240215152354.png)

void solve() {
    ll n, x, y, s;cin >> n >> x >> y >> s;
    if (x + (n - 1) * (x % y) > s) {
        cout << "NO\n";return;
    }
    s -= (x % y) * n;
    vector<array<int, 3>>dp(s + 1, {INT_MAX,0,0});
    for (int ns = x - x % y, h = x - x % y, i = 1;ns <= s;i++, h += y, ns += h) {
        dp[ns] = {i,0,h};
    }
    for (int i = 0;i <= s;i++) {
        if (dp[i][0] != INT_MAX) {
            for (int j = 1, t = 0/*t代表一部分的柱子的长度*/;t + i <= s;t += j * y, j++) {
                dp[t + i] = min(dp[t + i], {dp[i][0] + j, i, (j - 1)* y});
            }
        }
    }
    if (dp[s][0] > n) {
        cout << "NO\n";return;
    }
    vector<int> ans(n);
    int v = s;
    while (v) {
        auto [i, ns, h] = dp[v];
        while (v != ns) {
            i--;
            ans[i] = h;
            v -= h, h -= y;
        }
    }
    cout << "YES\n";
    for (auto i : ans)cout << i + (x % y) << " ";cout << "\n";
}
cpp

CF 2004 EDU Div.2 D - Colored Portals#

在一条直线上有 nn 座城市。这些城市的编号从 11nn

传送门用于在城市之间移动。传送门有 44 种颜色:蓝色、绿色、红色和黄色。每个城市都有两种不同颜色的传送门。如果城市 ii 和城市 jj 的传送门颜色相同,则可以从城市 ii 移动到城市 jj (例如,可以在 “蓝-红 “城市和 “蓝-绿 “城市之间移动)。这种移动需要花费 ij|i-j| 枚金币。

你的任务是回答 qq 个独立的问题:计算从城市 xx 移动到城市 yy 的最小花费。

Solution#

二分

表面看是最短路,其实仔细想想就知道不是这个做法

思路:

从题目可以知道,一共六种组合,若出现两个城市无法配对的问题,则最多只用中转一次就能到另外一个城市

若除了 x,yx,y 颜色没有其他颜色的城市了,则无法到达,否则一定能找到一个城市作为中转达到另外一个城市。

现在就有另外一个问题:如何在其他不同颜色的所有城市中找到使得 ik+jk\left|{i-k}\right|+\left|{j-k}\right| 最小的值呢?

如果能找到一个 kk 使得 i<k<ji<k<j ,则这时答案就是 jij-i .

若不能找到这样的 kk,则每次二分的去寻找使得答案相对最小的,即距离 iijj 最近的 kk 即可,在这里面取最小值即可。

思路完全正确,但是写代码写错了… 正确代码如下:

const string tar[] = {"BG", "BR", "BY", "GR", "GY", "RY"};
void solve() {
    int n, q;cin >> n >> q;
    vector<string> s(n + 1);
    vector<vector<int>> idx(6);
    for (int i = 0;i < 6;i++)idx[i].push_back(0);
    for (int i = 1;i <= n;i++) {
        cin >> s[i];
        for (int j = 0;j < 6;j++)if (s[i] == tar[j])idx[j].push_back(i);
    }
    for (int i = 0;i < 6;i++)idx[i].push_back(1e9);

    while (q--) {
        int x, y;cin >> x >> y;
        if (x > y)swap(x, y);
        if (s[x][0] == s[y][0] || s[x][0] == s[y][1] || s[x][1] == s[y][1] || s[x][1] == s[y][0]) {
            cout << y - x << '\n';continue;
        }
        int ok = 0;
        for (int i = 0;i < 6;i++) {
            if (tar[i] == s[x] || tar[i] == s[y] || !idx[i].size()) continue;
            auto k = lower_bound(idx[i].begin(), idx[i].end(), x);
            if (*k != 1e9 && *k < y) {
                ok = 1;break;
            }
        }
        if (ok) {
            cout << y - x << '\n';continue;
        }
        int ans = 1e9;
        for (int i = 0;i < 6;i++) {
            if (tar[i] == s[x] || tar[i] == s[y] || !idx[i].size()) continue;
            auto k1 = lower_bound(idx[i].begin(), idx[i].end(), x);
            if (*k1 != 1e9) {
                ans = min(ans, *k1 - y + *k1 - x);
            }
            auto k2 = upper_bound(idx[i].begin(), idx[i].end(), x);
            --k2;
            if (*k2 != 0) {
                ans = min(ans, x + y - *k2 - *k2);
            }
        }
        if (ans == 1e9)ans = -1;
        cout << ans << '\n';
    }
}
cpp

其实代码还可以简化:

const string tar[] = {"BG", "BR", "BY", "GR", "GY", "RY"};
void solve() {
    int n, q;cin >> n >> q;
    vector<string> s(n + 1);
    vector<vector<int>> idx(6);
    for (int i = 0;i < 6;i++)idx[i].push_back(0);
    for (int i = 1;i <= n;i++) {
        cin >> s[i];
        for (int j = 0;j < 6;j++)if (s[i] == tar[j])idx[j].push_back(i);
    }
    for (int i = 0;i < 6;i++)idx[i].push_back(1e9);

    while (q--) {
        int x, y;cin >> x >> y;
        if (x > y)swap(x, y);
        if (s[x][0] == s[y][0] || s[x][0] == s[y][1] || s[x][1] == s[y][1] || s[x][1] == s[y][0]) {
            cout << y - x << '\n';continue;
        }
        int ans = 1e9;
        for (int i = 0;i < 6;i++) {
            if (tar[i] == s[x] || tar[i] == s[y] || !idx[i].size()) continue;
            auto k = lower_bound(idx[i].begin(), idx[i].end(), x);
            if (*k != 1e9) {
                ans = min(ans, abs(*k - y) + *k - x);
            }
            --k;
            if (*k != 0) {
                ans = min(ans, x + y - *k - *k);
            }
        }
        if (ans == 1e9)ans = -1;
        cout << ans << '\n';
    }
}
cpp

CF 2004 EDU Div.2 E - Not a Nim Problem#

爱丽丝和鲍勃正在玩一个游戏。他们有 nn 堆棋子,其中 ii 堆最初有 aia_i 颗棋子。

在他们的回合中,玩家可以选择任意一堆石子,并从中取出任意正数的石子,但有一个条件:

  • 让当前石堆中的石子数量为 xx 。从棋子堆中取走 yy,使得 xxyy 的最大公约数等于 1。

无法下棋的棋手输棋。两位棋手都以最佳状态下棋(也就是说,如果一位棋手的策略能让他获胜,那么无论对手如何回应,他都会获胜)。爱丽丝先下。

确定谁会赢。

Solution#

博弈论 /SG 函数

EDU:学习博弈论

可以先暴力列出 sgsg 函数,根据 sg 函数的值来找规律:

void solve() {
    constexpr int N = 500;
    vector<int> sg(N);
    for (int i = 1;i < N;i++) {
        set<int>s;
        for (int j = 1;j <= i;j++) {
            if (__gcd(i, j) == 1) {
                s.insert(sg[i - j]);
            }
        }
        int idx = 0;
        for (auto x : s) {
            if (x == idx)idx++;
        }
        sg[i] = idx;
    }
    vector<vector<int>> q(N);
    for (int i = 0;i < N;i++)q[sg[i]].push_back(i);
    for (int i = 0;i < N;i++) {
        cout << i << ": ";
        for (auto x : q[i])cout << x << " ";
        cout << '\n';
    }
}
cpp

得到 sg099sg_{0\sim 99}

0 1 0 2 0 3 0 4 0 2 0 5 0 6 0 2 0 7 0 8 0 2 0 9 0 3 0 2 0 10 0 11 0 2 0 3 0 12 0 2 0 13 0 14 0 2 0 15 0 4 0 2 0 16 0 3 0 2 0 17 0 18 0 2 0 3 0 19 0 2 0 20 0 21 0 2 0 4 0 22 0 2 0 23 0 3 0 2 0 24 0 4 0 2 0 3 0 25 0 2 
cpp

q099q_{0\sim 99}: (即 qsgiq_{sg_{i}})

0: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 
1: 1 
2: 3 9 15 21 27 33 39 45 51 57 63 69 75 81 87 93 99 
3: 5 25 35 55 65 85 95 
4: 7 49 77 91 
5: 11 
6: 13 
7: 17 
8: 19 
9: 23 
10: 29 
11: 31 
12: 37 
13: 41 
14: 43 
15: 47 
16: 53 
17: 59 
18: 61 
19: 67 
20: 71 
21: 73 
22: 79 
23: 83 
24: 89 
25: 97 
cpp

易得

  • ii 为偶数时 sgi=0sg_{i}=0
  • 只看第一项,从 2 开始,之后的每一项是 3,5,7,11,3,5,7,11,\dots 都是质数项即 sg3=2,sg5=3,,sgprimesi=i+1sg_{3}=2,sg_{5}=3,\dots,\to sg_{primes_{i}}=i+1
  • 在一个值 xx 对应多个 sgisg_{i} 时,可以发现后面的数的最小质因数就是第一个质数,即: sg{list}=xsg_{\{list\}}=x{in listi>0:minp[listi]=list0}\{in\ list \cap i> 0:minp[list_{i}]=list_{0}\}

则有:sgprimesi=i+1\displaystyle sg_{primes_{i}=i+1}sgi=sgminpisg_{i}=sg_{minp_{i}}

constexpr int N = 1e7;
int sg[N + 1];
void solve() {
    int n;cin >> n;
    int ans = 0;
    for (int i = 1;i <= n;i++) {
        int x;cin >> x;ans ^= sg[x];
    }
    if (ans != 0) {
        cout << "Alice\n";
    } else {
        cout << "Bob\n";
    }
}
int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    sieve(N);
    sg[0] = 0;sg[1] = 1;sg[2] = 0;
    for (int i = 1;i < primes.size();i++) {
        sg[primes[i]] = i + 1;
    }
    for (int i = 3;i <= N;i++) {
        sg[i] = sg[minp[i]];
    }
    int _ = 1;
    cin >> _;
    while (_--)
        solve();
}
cpp

CF 1957 Div.2 C - How Does the Rook Move?#

给您一个 n×nn \times n 棋盘,您和电脑轮流在棋盘上分别放置白车和黑车。在放置车的过程中,您必须确保没有两只车互相攻击。如果两只车共用同一行或同一列,则无论颜色如何,都会互相攻击。

有效的一步棋是将一只车放在一个位置( rrcc )上,使它不会攻击任何其他车。

你先开始,当你在自己的回合中走了一步有效的棋,将白车置于位置( rrcc )时,电脑会照搬你的棋,在它的回合中将黑车置于位置( ccrr )。如果是 r=cr = c ,那么电脑就无法映射你的棋步,并跳过它的回合。

您已经与计算机下了 kk 步棋(计算机也会尝试复制这些棋步),您必须继续下棋直到没有剩余的有效棋步为止。在下完 kk 步之后,如果继续下棋,最终会有多少种不同的配置?可以保证 kk 步和隐含的计算机步都是有效的。由于答案可能较大,请打印出 109+710^9+7 的模数。

如果存在一个坐标( rrcc ),其中一个配置中有一个车,而另一个配置中没有,或者坐标上的车的颜色不同,那么这两个配置就被认为是不同的。

Solution#

组合数学/DP

法一:DP 转移方程:f[i] = f[i - 1] + f[i - 2] * 2 * (i - 1)

若从后往前推:

对于棋下在对角线上时,选择 (i,i)(i,i) 这时消耗了 1r×1l1r\times1l 则贡献为 f[i1]f[i-1]

若没有下在对角线上:选定该行 i(row)i(row) 后,则列可以在前面 (i1)(i-1) 个位置任意挑选,列 i(line)i(line) 同理,消耗 2r×2l2r\times2l,则贡献为 2(i1)f[i2]2(i-1)f[i-2]

#define int long long
constexpr int mod = 1e9 + 7;
int f[300010];
void solve() {
    int n, k;cin >> n >> k;int cnt = n;
    for (int i = 1;i <= k;i++) {
        int x, y;cin >> x >> y;cnt -= 2 - (x == y);
    }
    cout << f[cnt] << '\n';
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    f[0] = 1;f[1] = 1;f[2] = 3;
    for (int i = 3;i <= 3e5;i++) {
        f[i] = f[i - 1] + f[i - 2] * 2 * (i - 1);f[i] %= mod;
    }
    int _ = 1;
    cin >> _;
    while (_--)
        solve();
}
cpp

组合数学:

Cnm=n!(nm)!×m!C_{n}^m=\frac{n!}{(n-m)!\times m!}

cntcnt 代表可用的行列数,则: ans=i=0cnt/2(Ccnt2i×C2ii×i!)i=0cnt/2(cnt!(cnt2i)!×i!)=i=0cnt/2(Ccnt2i×2i!i!)ans=\sum\limits_{i=0}^{cnt/2}(C_{cnt}^{2i}\times C_{2i}^{i}\times i!)\equiv\sum\limits_{i=0}^{cnt/2}\left( \frac{cnt!}{(cnt-2i)!\times i!} \right)=\sum\limits_{i=0}^{cnt/2}\left( C_{cnt}^{2i}\times \frac{2i!}{i!} \right)

不懂是如何推出的 \dots

#define int long long
constexpr int mod = 1e9 + 7;
int fac[300010];
int inv(int a, int b = mod - 2) {
    int ans = 1;
    while (b) {
        if (b & 1)ans = (ans * a) % mod;
        a = (a * a) % mod;b >>= 1;
    }
    return ans;
}
int c(int a, int b) {
    return fac[a] * inv(fac[a - b]) % mod * inv(fac[b]) % mod;
}
void solve() {
    int n, k;cin >> n >> k;int cnt = n;
    for (int i = 1;i <= k;i++) {
        int x, y;cin >> x >> y;cnt -= 2 - (x == y);
    }
    int ans = 0;
    for (int i = 0;i * 2 <= cnt;i++) {
        ans = (ans + c(cnt, 2 * i) * c(2 * i, i) % mod * fac[i]) % mod;
    }
    cout << ans << '\n';
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    fac[0] = fac[1] = 1;
    for (int i = 2;i <= 3e5;i++) {
        fac[i] = fac[i - 1] * i % mod;
    }
    int _ = 1;
    cin >> _;
    while (_--)
        solve();
}
cpp

CF 1957 Div.2 D - A BIT of an Inequality#

给你一个数组 a1,a2,,ana_1, a_2, \ldots, a_n 。请找出有多少个图元( x,y,zx, y, z )符合下列条件:

  • 1xyzn1 \leq x \leq y \leq z \leq n , 和
  • f(x,y)f(y,z)>f(x,z)f(x, y) \oplus f(y, z) > f(x, z) .

定义 f(l,r)=alal+1arf(l, r) = a_l \oplus a_{l + 1} \oplus \ldots \oplus a_{r}

Solution#

位运算

f(x,y)f(y,z)>f(x,z)    f(x,z)ay>f(x,z)f(x, y) \oplus f(y, z)>f(x,z)\implies f(x,z)\oplus a_{y} > f(x, z)

设前缀异或为 sssis_{i} 代表前 ii 个数进行异或。

则可以将要求简化为求 szsx1ay>szsx1s_{z}\oplus s_{x-1}\oplus a_{y}>s_{z}\oplus s_{x-1} 的个数。

现在进行分类讨论:设 sz,sx1s_{z},s_{x-1} 当前比特位分别为 r,l1r,l_{-1}tt 代表的是 aya_{y} 最高比特位的值

t=0t=0 时:无论 r,l1r,l_{-1}010|1szsx1s_{z}\oplus s_{x-1} 的值都不会变

t=1t=1 时,若此时 r,l1r,l_{-1} 的值不同,则最终的值会变小,否则最终的值会变大

总结:变大的情况只有 t=1r=l1=0 or 1t=1 \cap r=l_{-1}=0\text{ or }1 时才会出现。

这样对于 aia_{i},只要先找到其最高位为 1 的位置 (j=__lg(ai))(j=\_\_lg(a_{i}))

由题目可得 z[i,n],x1[0,i1]z \in[i,n],x-1 \in[0,i-1],所以答案就是 ii 两边都是 1 的情况加上两边都是 00 的情况。

[i,n][i,n] 中 1 的数量乘以 [0,i1][0,i-1] 中 1 的数量加上 [i,n][i,n] 中 0 的数量乘以 [0,i1][0,i-1] 中 0 的数量

1 的数量算出来之后,这个区间 0 的数量就是区间长度减去 1 的数量。

#define int long long
int c[32][100010];
void solve() {
    int n;cin >> n;vector<int> a(n + 1), s(n + 1);
    for (int i = 1;i <= n;i++) {
        cin >> a[i];s[i] = s[i - 1] ^ a[i];
        for (int j = 0;j <= 30;j++) {
            c[j][i] = c[j][i - 1] + (s[i] >> j & 1);
        }
    }
    int ans = 0;
    for (int i = 1;i <= n;i++) {
        int k = __lg(a[i]);
        ans += (c[k][n] - c[k][i - 1]) * c[k][i - 1];
        ans += (n - i + 1 - (c[k][n] - c[k][i - 1])) * (i - c[k][i - 1]);
    }
    cout << ans << '\n';
}
cpp

现在只需要进行预处理:需要得到的是 f(l,r)=0 or 1(k bit)f(l,r)=0\text{ or }1(\text{k bit}),即:从 llrr 的元素的第 kk 位一起异或起来是 00 还是 1

这里需要处理其前缀与后缀:(官方题解)

pre[i][j][k = 0] 代表 a[1j]a[1\sim j] 个数的第 ii 位是 00 的个数,k=1k=1 同理。

suf[i][j][k = 0] 代表 a[nj]a[n\sim j] 个数的第 ii 位是 00 的个数,k=1k=1 同理。

注意:由于这里没有错位的进行前缀与后缀计算,所以在对 aia_{i} 进行操作时,需要将 aia_{i} 归于

constexpr int Z = 30, MAX_N = 1e5 + 3;
int pref[Z][MAX_N][2];
int suff[Z][MAX_N][2];

void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 0; i < Z; i++) suff[i][n + 1][0] = suff[i][n + 1][1] = 0;
    for (int i = 0; i < Z; i++) {
        for (int j = 1; j <= n; j++) {
            int t = (a[j] >> i) & 1;
            for (int k = 0; k < 2; k++) pref[i][j][k] = (t == k) + pref[i][j - 1][k ^ t];
        }
        for (int j = n; j >= 1; j--) {
            int t = (a[j] >> i) & 1;
            for (int k = 0; k < 2; k++) suff[i][j][k] = (t == k) + suff[i][j + 1][k ^ t];
        }
    }
    long long ans = 0;
    for (int i = 1; i <= n; i++) {
        int z = __lg(a[i]); // <-> 31 - __builtin_clz(a[i])
        ans += 1ll * pref[z][i - 1][1] * (1 + suff[z][i + 1][0]);
        ans += 1ll * (1 + pref[z][i - 1][0]) * suff[z][i + 1][1];
    }
    cout << ans << "\n";
}
cpp

给你一个整数 nn 。函数 C(i,k)C(i,k) 表示从集合 { 1,2,,i1, 2, \ldots, i } 中选择 kk 个不同的数字并将它们围成一个圆圈 ^\dagger 的不同方法的数目。

i=1nj=1i(C(i,j)modj).\sum\limits_{i=1}^n \sum\limits_{j=1}^i \left( C(i,j) \bmod j \right).

由于这个值可能非常大,因此求它的模数 109+710^9+7

Solution#

威尔逊定理

容易知道 C(i,j)=Comb(i,j)×(j1)!=i!(ij)!×j=i(i1)(i2)(ij+1)jC(i,j)=Comb(i,j)\times(j-1)! =\frac{i!}{(i-j)!\times j}=\frac{i(i-1)(i-2)\dots(i-j+1)}{j}

可以知道的是 n(nj+1)n\dots(n-j+1)jj 项,由于是连续的 jj 个数,每个数对 jj 的 模数一定不一样 (0j1)(0\sim j-1),其中一定有一项能够被 jj 整除(0(modj)0\pmod j),至于其他项则构成了 (j1)!(j-1)!

    C(i,j)=(j1)!×ij(modj)\implies C(i,j)=(j-1)!\times \lfloor{\frac{i}{j}}\rfloor\pmod j

wilson\text{wilson} 定理:对于素数 pp(p1)!(p)1(modp)(p-1)! \equiv(p)-1\pmod p,对于合数 (p4)(p\neq4)(p1)!0(modp)(p-1)! \equiv0\pmod p

这里利用差分数组 dd,对于位置 i(i=k×j,k=1,2,3)i(i=k\times j,k=1,2,3\dots),则要使得 ij\lfloor{\frac{i}{j}}\rfloor 的结果相同,i[kj,kj+j1]i \in[kj,kj+j-1][i,i+j1][i,i+j-1] 位置的贡献 (ij)\left( \lfloor{\frac{i}{j}}\rfloor \right)ii 位置相同,直到 [i+j][i+j] 才使得 ij\lfloor{\frac{i}{j}}\rfloor 增加了 1,所以在 ii 位置加上对应贡献,在 [i+j][i+j] 位置减去相应贡献,这样,对 dd 数组进行前缀和后就能够计算出每个位置正确的贡献,对答案而言,是 [1,n][1,n] 的贡献之和,所以还需要对 dd 数组再进行一次前缀和则就是对应的答案。

#define int long long
constexpr int N = 1e6, mod = 1e9 + 7;
int vis[N + 10], pri[N + 10], isp[N + 10];//判断是否是素数
int d[N + 10];
void init(int n) {//线性筛
    int cnt = 0;
    for (int i = 2; i <= n; ++i) {
        if (!vis[i]) {
            pri[cnt++] = i;isp[i] = 1;
        }
        for (int j = 0; j < cnt; ++j) {
            if (i * pri[j] > n) break;
            vis[i * pri[j]] = 1;
            if (i % pri[j] == 0) break;
        }
    }
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    init(N);
    for (int j = 2;j <= N;j++) {
        if (j == 4 || isp[j]) {
            int v = (j == 4 ? 2 : j - 1);
            for (int i = j;i <= N;i += j) {//枚举j的倍数
                int t = v * (i / j) % j;
                d[i] = (d[i] + t) % mod;
                if (i + j <= N)d[i + j] = (d[i + j] - t + mod) % mod;
            }
        }
    }
    for (int i = 1;i <= N;i++)d[i] = (d[i] + d[i - 1]) % mod;
    for (int i = 1;i <= N;i++)d[i] = (d[i] + d[i - 1]) % mod;
    int _;cin >> _;
    while (_--) {
        int n;cin >> n;cout << d[n] << '\n';
    }
}
cpp

CF 1935 Div.2 D - Exam in MAC#

给出一个长度为 nn 的数组 ss,求 x,y(xy)x,y(x\leq y) (满足 x+y,yxx+y,y-x 都不是数组中的元素, 0x,yc0\leq x,y\leq c)的对数

Solution#

容斥原理/组合数学

主要是要往这个方面想,想到了就很简单了。

官方题解:

用容斥原理:cnt(x,y)cnt(x,y:x+ys)cnt(x,y:yxs)+cnt(x,y:x+y,yxs)\mathrm{cnt}(x, y) - \mathrm{cnt}(x, y: x + y \in s) - \mathrm{cnt}(x, y: y - x \in s) + \mathrm{cnt}(x, y: x + y, y - x \in s)

x,yx, y 的总对数数量是 (c+1)(c+2)2\frac{(c+1)\cdot(c+2)}{2}

x,y:x+ysx, y: x + y \in s。遍历和值 sis_i,假设 x+y=six + y = s_i,则对于 0xsi20 \leq x \leq \lfloor \frac{s_i}{2} \rfloor 会有一个对应的 yy,因此具有这样的和的对的数量是 si2+1\lfloor \frac{s_i}{2} \rfloor + 1

x,y:yxsx, y: y - x \in s。遍历差值 sis_i,假设 yx=siy - x = s_i,则对于 siycs_i \leq y \leq c 会有一个对应的 xx,因此具有这样的差的对的数量是 csi+1c - s_i + 1

x,y:x+y,yxsx, y: x + y, y - x \in s。假设 x+y=six+y=s_iyx=sjy-x=s_j,则 x=sisj2x = \frac{s_i - s_j}{2}y=si+sj2y = \frac{s_i+s_j}{2}。当奇偶性相同时就可以计入答案。 让我们计算 ss 中偶数和奇数的数量——分别为 evenevenoddodd。因此,这样的对的数量是 even(even+1)2+odd(odd+1)2\frac{even\cdot(even+1)}{2}+\frac{odd\cdot(odd+1)}{2}

#define int long long
void solve() {
    int n, c;cin >> n >> c;
    vector<int> a(n);
    for (int i = 0;i < n;i++) {
        cin >> a[i];
    }
    int cnt = (c + 2) * (c + 1) / 2, odd = 0;
    for (int i = 0;i < n;i++) {
        cnt -= a[i] / 2 + 1;
        cnt -= c - a[i] + 1;
        if (a[i] % 2)odd++;
    }
    int even = n - odd;
    cnt += (odd + 1) * odd / 2 + (even + 1) * even / 2;
    cout << cnt << '\n';
}
cpp

CF 1997 EDU Div.2 D - Maximize the Root#

给你一棵有根的树,由 nn 个顶点组成。树中的顶点编号为 11nn ,根顶点为 11 。数值 aia_i 写在第 ii 个顶点上。

您可以执行以下任意次数(可能为零)的操作:选择一个顶点 vv 该顶点至少有一个子顶点。对 vv 的子树( vv 本身除外)中的所有顶点 uu ,将 ava_v 增加 11 ,并将 aua_u 减少 11 。但是,每次操作后,所有顶点上的值都应该是非负值。

您的任务是通过上述操作计算出写入根的最大可能值。

Solution#

树形 DP/二分

这个题目一眼二分,但是可惜自己的图论方面的代码能力太差,没写出来。(似乎有许多解法)

O(nlogn)O(n\log n)

dfs 判断是否能够在 uu 的子树中得到至少 xx 的值

dfs 思路是:对于 1 的子树而言,需要满足大于等于 mid 的需求,若不满足,则子树的节点需要被增加一些才能满足需求,而这里增加了对于该子树节点的子树会提出更高的要求,即 xx 会增加。

#define int long long
void solve() {
    int n;cin >> n;
    vector<int> a(n + 1);
    for (int i = 1;i <= n;i++)cin >> a[i];
    vector<vector<int>> g(n + 1);
    for (int i = 2;i <= n;i++) {
        int x;cin >> x;
        g[x].push_back(i);
    }

    auto dfs = [&](auto self, int u, int x)->bool {
        if (x > 1e9)return 0;
        bool leaf = 1;
        if (u != 1) {
            x += max(0ll, x - a[u]);
        }
        for (auto v : g[u]) {
            leaf = 0;
            if (!self(self, v, x))return 0;
        }
        return (!leaf || x <= a[u]);
        };

    int l = 0, r = 1e10;
    while (l < r) {
        int mid = l + r >> 1;

        if (!dfs(dfs, 1, mid))r = mid;
        else l = mid + 1;
    }
    cout << a[1] + l - 1 << '\n';
}
cpp

O(n)O(n)

思路和我刚开始的想法接近,尽量让上下两层最小值接近,

uu 子树的最小值 mimiaua_{u} 还小,则证明子树没什么能提交给 aua_u 的了,这个子树最多能凑出 mimi 的值,

uu 子树的最小值 mimiaua_{u} 大,则可以按照题目要求将子树中的值整体提交给父亲一些,当接近的时候最优

搜索之后,根节点的子树则为下面的节点提交后的值,则答案就是 a1a_{1} 加上子树的最小值。

#define int long long
void solve() {
    int n;cin >> n;
    vector<int> a(n + 1), val(n + 1);
    for (int i = 1;i <= n;i++)cin >> a[i];
    vector<vector<int>> g(n + 1);
    for (int i = 2;i <= n;i++) {
        int x;cin >> x;
        g[x].push_back(i);
    }
    int inf = 1e18;
    auto dfs = [&](auto self, int u)->void {
        val[u] = a[u];
        int mi = inf;
        for (auto v : g[u]) {
            self(self, v);mi = min(mi, val[v]);//mn是求的u节点子树的最小值
        }
        if (mi != inf) {// 不是叶子节点
            if (val[u] > mi) {
                val[u] = mi;
            } else {
                val[u] = val[u] + mi >> 1;
            }
        }
        };
    dfs(dfs, 1);
    int ans = inf;
    for (auto v : g[1]) {
        ans = min(ans, val[v]);
    }
    cout << ans + a[1] << '\n';
}
cpp
Codeforces 题解整理 3:数论、博弈、组合与 EDU
https://astro-pure.js.org/blog/solutions-codeforces-3
Author 五香牛肉面
Published at 2026年3月6日
Comment seems to stuck. Try to refresh?✨