[IOI 2024] 消息篡改者

通信大学习。

Alice 有一个长度为 \(S\) 的布尔数组,现在她需要通过传输数据包的方式将它发送给 Bob。

我们定义一个数据包是一个长度为 \(31\) 的布尔数组。由于宇宙射线的影响,数据包中的一些位置可能被修改。具体地,我们给定长度为 \(31\) 的布尔数组 \(C\),其包含 \(15\)\(1\)\(16\)\(0\),所有满足 \(c_i=1\) 的位置 \(i\) 均可能被修改。

一开始只有 Alice 知道布尔数组 \(C\),你需要实现 Alice 和 Bob 的通信。具体地,你需要实现:

  • send_message 函数,该函数接收长度为 \(S\) 的布尔数组 \(M\) 和长度为 \(31\) 的布尔数组 \(C\)。该函数可以调用 send_packet 函数来发送数据包,同时我们记 send_message 中调用 send_packet 的次数为 \(Q\)

  • send_packet 函数,该函数接收长度为 \(31\) 的布尔数组 \(A\),表示 Alice 发送的数据包为 \(A\)。其会对 \(A\) 进行修改,并返回长度为 \(31\) 的布尔数组 \(B\),表示 Bob 接收的数据包为 \(B\)

  • receive_message 函数,该函数接收 \(Q\) 个长度为 \(31\) 的布尔数组,第 \(i\) 个布尔数组 \(B_i\) 表示第 \(i\) 次调用 send_packet 后生成的布尔数组。该函数需要返回一个长度为 \(S\) 的布尔数组,且其需要与 \(M\) 相同。

子任务编号 $S \leq $ 分数
\(1\) \(64\) \(10\)
\(2\) \(1024\) \(90\)

对于每个子任务,设其满分为 \(X\),则你的得分 \(T\) 由下式计算:

\[T = \begin{cases} X & Q \leq 66 \\ X \times {0.95}^{Q-66} & 66 < Q \leq 100 \\ 0 & Q > 100 \end{cases} \]

假设宇宙射线足够聪明,他有一套算法。

数组从 \(0\) 开始标号。

我们首先解决 \(S\) 变化的问题,我们不妨令 \(S\)\(1025\),将多出的部分用 \(100 \dots 0\) 填充,容易发现这样一定能够还原数组 \(M\)

我们考虑如下命题:

我们可以使用一个数据包 \(A\) 传输一个布尔值 \(V\)

考虑被宇宙射线修改的位置个数不超过数据包长度的一半,我们将 \(A\) 中的所有值设为 \(V\),此时 \(B\) 中最少存在 \(16\)\(V\),对于 \(B\) 中的值求众数即可得到 \(V\)

接下来我们就得到了需要传输 \(S\) 个数据包的方法,即每次使用一个数据包传输 \(M\) 的一位,这样可以得到 \(10\) 分。什么你说要把 \(S\) 补充到 \(1025\) 拿不到分?对于第一个子任务只需要将 \(S\) 补充到 \(65\) 即可。

接下来考虑更进一步,我们发现每次用一个数据包 \(A\) 传输一个布尔值 \(V\) 还是太浪费了,因为我们没有较好的利用不会被修改的位置。

考虑优化,我们先用上面的方法将 \(C\) 数组传输给 Bob,接下来只需要使用可用的位置把 \(M\) 传输给 Bob 即可。这样调用 send_packet 的次数 \(Q=31+\lceil \frac{1025}{16} \rceil=96\),可以得到 \(10+90 \times {0.95}^{96-66}=29.31\) 分。

接下来考虑进一步优化。我们先有下面的性质:

数组 \(C\) 中的第一个 \(0\) 的下标 \(i\) 必然满足 \(0 \leq i < 16\)

证明显然,考虑利用这个性质。我们先将第一个 \(0\) 的下标 \(i\) 传输给 Bob,接下来我们考虑使用下标 \(i\) 传输数组 \(C\)。具体地,我们需要再使用 \(29\) 个数据包,其中 \(c_i\) 对于 Bob 已知,可以不用传输。同时 \(c_{30}\) 是 Bob 可以通过计算得出的。这样我们只需要 \(29+4=33\) 个数据包就可以传输 \(C\) 数组,而上面的做法只需要使用 \(31\) 个数据包。你发现这个做法更劣,吗?

注意到 Bob 会一次收到所有数据包,当 Bob 得到前 \(33\) 个数据包后就可以还原数组 \(C\),而这个方法的好处在于它在每次传输中只会占用一位。考虑对于传输 \(C\)\(29\) 个数据包都利用剩余的 \(15\) 个位置传输信息,这样我们就只需要使用 \(33+\lceil \frac{1025-29 \times 15}{16} \rceil=70\) 个数据包即可完成传输,可以得到 \(10+90 \times {0.95}^{70-66}=83.31\) 分。

这条路好像很难走下去了。事实证明,正解和这个几乎没有关系。

我们考虑在 \(66\) 个包的限制下,有用的位置只有 \(66 \times 16 = 1056\) 个。除去需要传输 \(M\)\(1025\) 个位置以外,剩余传输 \(C\) 的位置只有 \(31\) 个,容易发现其恰好为 \(C\) 的长度!

考虑 \(C\) 的什么东西等于 \(31\),我们注意到 \(C\) 的相邻两个 \(0\) 的位置差之和为 \(31\)

对于每个 \(0\),设其与下一个 \(0\) 的距离为 \(x\),我们在该位上传输 \(x-1\)\(0\)\(x\)\(1\)。对于 Bob 来说,他需要找到每一位上第一个 \(1\) 的位置。容易发现对于 \(c_i=0\) 的情况,Bob 容易还原出下一个 \(0\) 的位置。此时我们考虑图论建模,将每个位置与可能的下一个 \(0\) 的位置连有向边,此时每个点的出度为 \(1\),图一定构成内向基环树森林。

虽然对于 \(c_i=1\) 的位置,我们并不清楚其出边会连向哪里,但图中所有 \(c_i=0\) 的出边都不会被更改,其恰好构成长度为 \(16\) 的环。而我们发现,\(31\) 个点的内向基环树森林最多存在一个大小为 \(16\) 的环,我们容易找到这个环。

接下来,我们根据这个环,就可以找到所有 \(c_i=0\) 的位置。这样做恰好使用了 \(31\) 个位置传输 \(C\),我们可以获得 \(100\) 分。

太深刻了。菜菜 Oken 喵的笨笨脑子最多只能想到 \(29.31\) 分的那个做法。

#include "message.h"
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int N=31,L=1025,P=66;
void send_message(vector<bool> M,vector<bool> C){
	M.push_back(1);
	while(M.size()<L){
		M.push_back(0);
	}
	vector<bool> vec[N];
	int pre=0;
	for(int i=0;i<N;i++){
		if(C[i]==0){
			int pre_tot=0;
			for(int j=(i+1)%N;C[j]==1;j=(j+1)%N){
				vec[i].push_back(0);
				pre_tot++;
			}
			vec[i].push_back(1);
			pre_tot++;
			while(pre_tot<P){
				vec[i].push_back(M[pre++]);
				pre_tot++;
			}
		}
		else{
			for(int j=0;j<P;j++){
				vec[i].push_back(0);
			}
		}
	}
	for(int i=0;i<P;i++){
		vector<bool> A;
		for(int j=0;j<N;j++){
			A.push_back(vec[j][i]);
		}
		send_packet(A);
	}
}
vector<bool> receive_message(vector<vector<bool> > R){
	vector<bool> vec[N];
	for(int i=0;i<P;i++){
		for(int j=0;j<N;j++){
			vec[j].push_back(R[i][j]);
		}
	}
	vector<int> to;
	for(int i=0;i<N;i++){
		int cnt=0;
		for(int j=0;j<P;j++){
			cnt++;
			if(vec[i][j]==1){
				break;
			}
		}
		to.push_back((i+cnt)%N);
	}
	vector<bool> good;
	for(int i=0;i<N;i++){
		vector<bool> vis,cycle;
		for(int j=0;j<N;j++){
			vis.push_back(0);
			cycle.push_back(0);
		}
		int pre=i;
		while(!vis[pre]){
			vis[pre]=true;
			pre=to[pre];
		}
		while(!cycle[pre]){
			cycle[pre]=true;
			pre=to[pre];
		}
		int cnt=0;
		for(int j=0;j<N;j++){
			if(cycle[j]){
				cnt++;
			}
		}
		if(cnt==16){
			for(int j=0;j<N;j++){
				good.push_back(cycle[j]);
			}
			break;
		}
	}
	vector<bool> ans;
	for(int i=0;i<N;i++){
		if(good[i]){
			bool flag=false;
			for(int j=0;j<P;j++){
				if(flag){
					ans.push_back(vec[i][j]);
				}
				if(vec[i][j]==1){
					flag=true;
				}
			}
		}
	}
	int lst_1=0;
	for(int i=0;i<L;i++){
		if(ans[i]==1){
			lst_1=i;
		}
	}
	vector<bool> final;
	for(int i=0;i<lst_1;i++){
		final.push_back(ans[i]);
	}
	return final;
}
posted @ 2025-12-20 10:29  Oken喵~  阅读(9)  评论(2)    收藏  举报