Codeforces Round 812 (Div. 2)

· 题解

中间困了好久,, D就不想做

然而晚上又睡不着,于是起来补

C. Build Permutation

懵了半天没发现规律,打个表才发现..

可以分段搞,开始的上限 nownow 是 n−1n - 1

我们设 l1≤now≤l2l_1 \leq now \leq l_2,且 l1,l2l_1,l_2 都是完全平方数

不难发现,如果 l1=nowl_1 = now,直接按 now→0now \to 0 的顺序把下标 0→now0 \to now 排一遍就好

然后考虑 l1+1→nowl_1 + 1 \to now 的部分,这部分要成为完全平方数只能变到 l2l_2,但是 l1→0l_1 \to 0 加上 l2−l1−1→l2−nowl_2 - l_1 - 1 \to l_2 - now 是不行的

我在这里卡了好久

然后打表发现从后往前考虑补成对应的平方数就好了,代码比较清楚

#include <bits/stdc++.h>
using namespace std;
int main() {
	int tt;
	cin >> tt;
	while (tt--) {
		int n;
		cin >> n;
		vector<int> ans(n);
		int now = n - 1;
		while (now > 0) {
			int l1 = sqrt(now);
			int l2 = sqrt(now) + 1;
			// cout << l2 << ' ' << now << '\n';

			if (l1 * l1 == now) {
				for (int i = 0; i <= now; ++i)
					ans[i] = l1 * l1 - i;
				break;
			}

			for (int i = now; l2 * l2 - i <= now; --i)
				ans[i] = l2 * l2 - i;
			now = l2 * l2 - now - 1;
		}
		for (int i = 0; i < n; ++i)
			cout << ans[i] << " \n"[i == n - 1];
	}
	return 0;
}

D. Tournament Countdown

一共有 2n2^n 个人

如果按照题意一轮轮询问下去,次数就是

2n−1+2n−2+⋯+20=2n−1\begin{equation*} 2^{n-1}+2^{n-2}+\cdots+2^{0}=2^n-1 \end{equation*}

而题目要求的是不超过 ⌈23 2n⌉\lceil \frac{2}{3}\ {2^n} \rceil 次询问,显然寄了

对于询问a b,给定的是赢的次数,第一轮会淘汰 2n−12^{n-1} 个人,
即有 2n−12^{n-1} 个人赢 0 次,
一直到第 n+1n + 1 轮,还剩 1 个人,赢了 nn 次

因此我们询问 (a, b),就会得到多种情况

  1. 相等,说明a b都被淘汰了
  2. a>b,说明b被淘汰
  3. 反之a被淘汰

并且对于同一层的 (a, b), (c, d),询问必从两组中各挑一个,这样相等说明ab 都被淘汰,因为b d肯定决出胜负,不等则询问胜者与败者组的另一个

这样2次询问能把两组4个人变为1个人

令 22k≤2n<22k+22^{2k}\leq 2^n < 2^{2k+2}

经过 12n+18n+⋯+214kn\frac{1}{2}n+\frac{1}{8}n+\cdots+2 {\frac{1}{4}}^k n 次询问会使 2n2^n 缩小到 2n22k\frac{2^n}{2^{2k}} (0 or 1) 的范围

得到总次数为 232n(1−14k)\frac{2}{3}2^{n}(1-\frac{1}{4^k}),分类讨论一下
nmod  2n \mod 2 的值,发现都是成立的

#include <bits/stdc++.h>
#define m (1 << n)
using namespace std;
int main() {
  int tt;
  cin >> tt;
  while (tt--) {
    int n;
    cin >> n;
    auto ask = [&](int &x, int &y) {
      cout << "? " << x << ' ' << y << endl;
      int tem;
      cin >> tem;
      return tem;
    };
    vector<int> a(m);
    for (int i = 0; i < m; ++i)
      a[i] = i + 1;
    while (m >= 4) {
      vector<int> b;
      for (int i = 0; i < m; i += 4) {
        int t1 = ask(a[i], a[i + 2]), t2;
        if (t1 == 0) {
					t2 = ask(a[i + 1], a[i + 3]);
					b.push_back(t2 == 1 ? a[i + 1] : a[i + 3]);
				}
        else if (t1 == 1) {
					t2 = ask(a[i], a[i + 3]);
					b.push_back(t2 == 1 ? a[i] : a[i + 3]);
				}
        else {
					t2 = ask(a[i + 1], a[i + 2]);
					b.push_back(t2 == 1 ? a[i + 1] : a[i + 2]);
				}
      }
			a = b;
			n -= 2;
    }
		if (a.size() == 1)
			cout << "! " << a[0] << endl;
		else {
			int ans = (ask(a[0], a[1]) == 1) ? a[0] : a[1];
			cout << "! " << ans << endl;
		}
  }
  return 0;
}

E. Cross Swapping

发现如果令 k=a,k=bk=a,k=b 进行两次操作,Aab和AbaA_{ab}和A_{ba} 的位置不会改变,剩下的则是正常的行列交换

因此不难发现如果 1⋯n1\cdots n 全部操作一遍,不会改变这个矩阵

因此如果贪心的考虑每一位,优先满足第一行字典序最小,那么只要考虑 A1i=Ai1A_{1i}=A_{i1} 的情况就好了,其中
i∈[1,n]i\in [1,n],令这样的位置为集合 SS

显然我们在 SS 里面进行决策,不会改变第一行的情况

考虑 i∈Si \in S,对 ii 进行操作会影响到第 ii 行与第 ii 列的元素,然后发现对于第二列,也是如此,如果第二列交换与不交换都是这样,那么就作为改变第三行答案的选项留在集合里

逝了世发现上面那个想法假了,,wa 2但是我看不到数据,有高手看到可以Hack一下我..

案发现场

爬去看题姐..

简单讲就是说,从前往后考虑,然后每个i考虑操作或者不操作,那么在满足前面的关系上,顺序对每个位置涉及到的 i,ji,j 操作一下

然后用并查集维护就好了、、

扩展域还是边带权都一样

#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif

#include <bits/stdc++.h>
using namespace std;
int main() {

	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);

	int tt;
	cin >> tt;
	while (tt--) {
		int n;
		cin >> n;
		vector a(n + 1, vector<int>(n + 1, 0));


		vector fa(2 * n + 1, 0);
		iota(fa.begin(), fa.end(), 0);

		function<int(int)> find = [&](int o) {
			return o == fa[o] ? o : fa[o] = find(fa[o]);
		};

		function<void(int, int)> merge = [&](int u, int v) {
			if (find(u) == find(v))
				return;
			fa[find(v)] = find(u);
		};


		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= n; ++j)
				cin >> a[i][j];

		for (int i = 1; i <= n; ++i)
			for (int j = i + 1; j <= n; ++j) {
				if (a[i][j] == a[j][i] || find(i) == find(j) || find(i) == find(j + n))
					continue;
				if (a[i][j] < a[j][i]) {
					merge(i, j);
					merge(i + n, j + n);
				} else {
					merge(i, j + n);
					merge(i + n, j);
				}
			}

		for (int i = 1; i <= n; ++i) {
			if (find(i) <= n) {
				// deb(i);
				for (int j = 1; j <= n; ++j)
					swap(a[i][j], a[j][i]);
			}
		}

		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= n; ++j)
				cout << a[i][j] << " \n"[j == n];
	}
	return 0;
}

中间非常诡异的是这个,第46行的merge如果换一下顺序,就会wa

仔细思考一下,因为我的merge写法导致如果调换顺序,事实上并没有把 i,ji,j 分割开..