[Informatics] (Legacy) CodeForces + Atcoder 问题几则

发布这篇文章时本人已退役 OI,内容撰写于几年前,这里挑了写的比较好的几个题出来,作为留念和参考。

至于以后会不会做 CF 和 ATC 的题继续在 ACM 留下我的身影,谁都说不准啊

Problem 0

Source: AT_arc173_c - Not Median

简单一点讲一下吧。

观察到答案会有很多 \(3\)\(5\) 之类的小数,因此构造一个具有巨大答案的数列:\(\{-a,a,-b,b,0,-c,c,-d,d\}\)(相对大小)。

尽管我们要计算出 \(0\) 处的答案需要扫过大量的元素,但是手玩一下这个数列,可以发现:非边界元素都可以确定对应答案为 \(\bm 3\)

于是我们就可以在扫的过程当中确定哪些点的答案肯定是 \(3\),不用再次计算。这样就可以做到均摊 \(\mathcal {O(n)}\)

实际上实现会有一些麻烦。会有几种情况停止扫描:

  1. 在当前元素的左面 / 右面连着出现两个符号相同的。
  2. 关于当前元素对称的两个位置上元素符号相同。

注意优先级。条件 \(1\) 的长度更短,所以优先判断条件 \(1\)。至于左右没有优先级。

但是条件 \(1\) 有一个 Corner Case:假如截下来长度不是奇数,就只能往反方向借一个。但是对于数列首尾,就没法借了,所以只能暴力算首尾位置的答案。

注意:如果这里能借一个,就保证满足条件。如果不满足早就在之前被判掉了。

细节大概就这么多。如果你担心填 \(3\) 填的太多了少填两三个爆算也行。

这就做完了。AC Code

Problem 1

Source: CF1762D GCD Queries

下面简称 \(\gcd(p_i,p_j)\)\(\text{z}(i,j)\)

这个限制看起来就像每两步询问就可以检验一个数是否为 \(0\),或者直接判断其不为 \(0\)。但是直接下手有些困难,我们显然需要找到一些性质。

考虑到对于一个排列中元素都不相同,根据这个性质,如果 \(p_i=0\),那么一定满足 \(\text{z}(i,j)\not =\text{z}(i,k)\),反过来 \(p_i\not =0\) 的充分条件是 \(\text{z}(i,j) = \text{z}(i,k)\)

这一条看起来很有用,但是过于狭隘,不一定存在 \(\text{z}(i,j)=\text{z}(i,k)\),而这又不是必要条件。考虑到这种情况已经需要询问 \(2\) 次,我们不妨继续在 \(\text{z}(i,j)\)\(\text{z}(i,k)\) 的大小关系上做文章。

于是考虑 \(\text{z}(i,j)\not =\text{z}(i,k)\),此时发现没法判定 \(p_i\),但是当 \(p_i=0\) 的时候肯定 \(j,k\) 都不为 \(0\),如果 \(p_i \not = 0\) 的时候就说明 \(\text{z}(i,j)\)\(\text{z}(i,k)\)\(p_i\) 的两个不同的因数。

不妨设 \(\text{z}(i,j)<\text{z}(i,k)\),那么即使最劣情况下 \(\text{z}(i,k)=p_i\),无法判定 \(p_k\),也可以判定 \(p_j\),因为 \(\gcd(p_j,p_i)\not= p_i\not=\gcd(p_i,0)\),所以 \(p_j\not=0\)

在以上观察之后我们发现,任意的询问 \(\text{z}(a,b)\)\(\text{z}(a,c)\) 都可以对满足 \(x\in\{a,b,c\}\)\(x\) 进行判定其 \(p_x\not=0\)。于是我们发现只需要判定 \(n-2\) 次就一定可以得到可能为 \(0\) 的两个元素。

Implementation

#include <bits/stdc++.h>
using namespace std;
int solve();
signed main() { int t; cin >> t; while (t--) solve(); return 0; }
int _gcd(int x, int y) { 
    cout << "? " << x << " " << y << endl;
    int r; cin >> r; return r;
}
int f(int i, int j, int k) {
    int A = _gcd(i, k), B = _gcd(j, k);
    if(A == B) return k;
    if(A < B) return i;
    return j;
}
int solve() {
    int n;
    cin >> n;
    if(n == 2) {
        puts("! 1 2"); int x; cin >> x;
        return 0;
    }
    priority_queue<int> q;
    for(int i = 1; i <= n; i++) q.push(i);
    while(q.size() != 2) {
        int x = q.top(); q.pop();
        int y = q.top(); q.pop();
        int z = q.top(); q.pop();
        int w = f(x, y, z);
        if(x != w) q.push(x);
        if(y != w) q.push(y);
        if(z != w) q.push(z);
    }
    cout << "! " << q.top() << ' ';
    q.pop();
    cout << q.top() << endl;
    int x; cin >> x;
    if(x == -1) return puts("WA");
    //This line will be never executed.
    return 0;
}

时间复杂度 \(\mathcal O(n\log n)\),理论可 \(\mathcal O(n)\)

Problem 2

Source: CF1715F Crop Squares

这篇解答采用 CF Hint 式的解答,应该更有启发性

1

考虑像下图一样构造多边形:

2

Hint 1,对于答案的 \(y\) 值和重叠面积 \(s_0\),有一定的函数关系,一个 \(y\) 对应一个 \(s_0\),一个 \(s_0\) 对应一个 \(y\)

换言之,同一 \(y\) 值得到的重叠面积相同,同一重叠面积得到的 \(y\) 值相同,

3

容易得到,重叠面积是一个 \(\frac{y}{m}\times 1\) 的矩形加上一个 \(\frac{1}{m}\times1\) 的直角三角形,即,\(s_0=\frac{y+0.5}{m}\),稍微变化下形式即可得到 \(y=s_0m-0.5\)

证明略,运用相似即可。

4

同理的锯齿状,横着询问,对于重叠面积 \(s_1\) 同理得到 \(x=s_1n-0.5\)

这样,在两次询问内,我们就得到了答案的 \(x,y\)

5

注意到这样的锯齿状不被认为是封闭的多边形,所以我们可以将这些点稍微下调 \(10^{-7}\) 个单位长度即可:

如图,橙色的点下调即可。(横式同理)

Implementation

对于 C++ 语言,cout << endl 会自动刷新缓存,所以可以不使用 cout << flush

本题的询问次数很少,所以你刷不刷新都没关系。

AC link

void query(int x, int y, long double &d, bool opt) {
	printf("? %d\n", 2 * x + 2);
	for(int i = 0; i < x; i++) {
		int tx = i, ty = 0, ttx = i, tty = y;
		if(opt) {
			printf("%d %d\n", ty, tx);
			printf("%d %d.99999999\n", tty, ttx);
		}
		else {
			printf("%d %d\n", tx, ty);
			printf("%d.99999999 %d\n", ttx, tty);
		}
	}
	if(opt) printf("%d %d\n%d %d\n", y, x, y, 0);
	else printf("%d %d\n%d %d\n", x, y, 0, y);
	cin >> d;

	return ;
}
signed main()
{
	int n, m;
	scanf("%d %d", &n, &m);
	long double s0, s1;
	query(n, m, s0, 0);
	query(m, n, s1, 1);
	printf("! %.8Lf %.8Lf\n", s1 * n - 0.5, s0 * m - 0.5);
	return 0;
}

Problem 3

Source: CF1421E - Swedish Heroes

有点观察但是绝对没有 2k7 的题。我觉得这个最多 2k4。

首先想到区间 dp,数据范围暗示我们这不可行。

那怎么办。我们不难发现符号序列应该满足:

  • 存在两个连着的同号。
  • 设正号数量为 \(p\),则 \(n+p-2\)\(3\) 的倍数。

然后 \(dp[i][p \bmod 3][\text{last}][第一条满足]\) 就可以 \(\mathcal O(n)\) 算了。到这里我们就做完了,但是为什么这个结论是对的呢?

注:以下所提深度,满足根节点深度为 \(1\)

必要性:

首先我们可以把整个操作序列变成一棵二叉苹果树。某个节点深度的奇偶即决定了其最终正负。考虑二叉苹果树的性质,那么一定会存在至少一对相邻节点深度相同,其有同一个父亲。

其次就是 \((n+p)\bmod 3=2\) 的证明。我们考虑对于一个奇深度的节点,如果挂上两个儿子,那么会使得 \(n\gets n+1,p\gets p+2\),因此 \((n+p)\bmod 3\) 不变。如果对于一个偶深度的节点挂上两个儿子,那么会使得 \(n\gets n+1,p\gets p-1\),因此 \((n+p)\bmod 3\) 还是不变。考虑初始状态 \(n=p=1\),所以 \((n+p)\bmod 3=2\)

充分性:

即,为什么一定一个满足条件的符号序列一定可以还原成一个操作序列。

依旧是一棵二叉苹果树,这里我们就相当于一棵叶子节点深度奇偶性序列满足要求时一定还原出一棵二叉苹果树。

将所有连着的同号的节点给它们连上父亲。

这样,我们就得到了类似于这样的东西:

因为我们把能连的都连了,所以不会产生左右两个孤立点的深度奇偶性都和合并过一次的点的深度奇偶性相同,因为这种情况我们可以多合并一次。如图:

然后就回到了和初始问题一样的规模,并且在这样贪心的合并下,一定可以保证,永远存在两个连着的同号。我们依旧使用类似的处理方式,每次合并两个,根据上面提到的证明就一定不会产生深度冲突。

最后一个问题即,如果还原到根节点的时候深度为 \(0\),这种是没有可能的。一旦满足了 \(n+p-2\)\(3\) 的倍数,如果合并两个奇深度点,\(n\gets n-1,p\gets p-2\);如果合并两个偶深度点,\(n\gets n-1,p\gets p+1\),所以和模 \(3\) 不变,最终留下的一定是 \(n=p=1\),满足要求。

最后注意 \(n=1\) 特判,因为 \(n=1\) 不需要存在相邻两个同号的。

Code

Problem 5

Source: ARC065'F シャッフル (Shuffle)

*2300, dp, greedy.

虽然 AT 有 \(2600\) 但是现在再回来看好像每一步都很简单。

数据范围启示我们写一个 \(\mathcal O(n^2)\) 的算法。如果可以完全 shuffle,那么显然是一个 \(C_{i,j}=C_{i-1,j}+C_{i-1,j-1}\) 的组合数,但是这里的 shuffle 加上了一些限制。

考虑沿用组合数的递推式:\(dp_{i,j}\) 为前面 \(i\)\(j\)\(1\) 的方案数,而我们要做的就是钦定能够被转移到的区间的上下界。

因为 \(l_i\) 单调不减,所以显然可以得到每个元素向右所能走到最远的地方 \(R_i\)。在可控范围内使得 \(1\) 最多或者最少就是上下界,这显然是贪心问题。

具体的,上界就是把 \(1\) 往前放,因此上界为 \([1,R_i]\)\(1\) 的数量。

下界是把 \(1\) 往后放,因此下界为 \(i\) 除去 \([1,R_i]\)\(0\) 的数量。

因为可能有太多 \(1\) 或者 \(0\),所以记得与 \([0,i]\) 取交集。

注意不合法状态和边界状态和组合数处理都是同理的。

  • \(dp_{i,0}=dp_{i-1,0}\)
  • \(dp_{0,0}=1\)
  • 不合法状态为 \(0\)

顺带一提,官方题解使用了区间 dp 的理解方式,然后因为 \(L\)\(R\) 相关把其中的一维优化掉了。看看这么想会不会帮助你理解这道题。

AC Rec.

posted @ 2026-02-09 22:27  Monomanic  阅读(22)  评论(0)    收藏  举报