[NOI2021] 量子通信题解

发现自己洛谷博客里的这篇文章

当时写得很稚嫩,多多指教

讲下我在同步赛的心路历程:

看懂题意后我意识到:
这道题目的强制在线形同虚设,只需要对于每一个询问算它异或一与不异或一的两种答案即可,然后对于所有询问依次判断它应该取哪一个答案。

想到这里,我就开始认为出题人不会傻到放一个没有用的强制在线,因此开始考虑离线算法

如果先不看 k ,这道题实际上在求当前询问与字典中的串最少有多少个不同的位

这一类求值问题让我想到离线分治的系列算法,然后就开始考虑按位分治

我太菜了,没有切出这道题

转而一看 k 的数据范围, \(k \leq 15\) ,肯定是刻意而为的,我就开始思考略去无用的分治节点

果然还是太菜了,发现节点数依然是 \(O(n^2)\)

观察一下刚刚的分治策略,有点像平面最近点对那道题,那道题我第一次是使用随机化切的

随机化?

随机化!

思路:

再次发扬人类智障智慧

可以考虑将所有询问和字典中的串一起离线下来排序(刚才讲了离线方法),想到由于 \(k \leq 15\) ,因此真正影响答案的串至少会有 240 位与询问串相同,所以这两个串大概率离得不太远,选取若干个检查过去即可(类似于平面上的点旋转以后求最近点对)

然而事实上可能离得很远

所以应该随机选取几个主元进行 \(O(n)\) 的基数排序,再去更新答案

然而还是不行:
万一所有询问都是一样的,算法至少需要 \(O(m^2)\),因为所有询问都会被排在一起

这个比较好处理,预处理出每一个位置的下一个非询问节点即可

注意一点,为了常数,我们不去改变这些 01 串真正的存储位置,而是用一个数组存下它们的相对位置,这样避免了 256 的常数

如果检查前后 30 个,选取 15 个主元,重复做 20 次的话,想象一下就可以知道出错概率极小,这就是我考场写的代码,几乎保证了不出错

然而它会T成68pts

于是我开始了我的调参之路:

开太大会 T ,开太小会 WA,不大不小会又 T 又 WA

因此不建议任何后人步我的后尘

最后调出来是这样的:

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=650003,M=250003;
const int zyf=14,em=14,eg=10;
bitset<256> s[N];
inline ull myRand(ull &k1, ull &k2) {
    ull k3=k1,k4=k2;
    k1=k4;
    k3^=(k3<<23);
    k2=k3^k4^(k3>>17)^(k4>>26);
    return k2+k4;
}
inline void gen(int n,ull a1,ull a2) {
    for (register int i=1; i<=n; i++)
        for (int j=0; j<256; j++)
            s[i][j]=(myRand(a1,a2)&(1ull<<32))?1:0;
}
inline char nc(){
	static char buf[1000000],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin)),p1==p2?EOF:*p1++;
}
int q1[N],q2[N],q[N],r1,r2;
int nxt[N],pre[N];
int ans[M],k[M];
char t[64];
int OX[300];
ull a1,a2;
bool las=0;
int n,m,tot;
inline int gt(){
	int x=0;
	char c=nc();
	while ((c<'0'||c>'9')&&(c<'A'||c>'F')) c=nc();
	for (int i=0; i<64; i++) t[i]=c,c=nc();
	while (c<'0'||c>'9') c=nc();
	x=c^48; c=nc();
	if (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48);
    return x;
}
inline  void srt(int x){
	r1=r2=0;
	for (int i=1; i<=tot; i++)
		if (s[q[i]][x]) q2[++r2]=q[i];
		else q1[++r1]=q[i];
	for (int i=1; i<=r1; i++) q[i]=q1[i];
	for (int i=1; i<=r2; i++) q[i+r1]=q2[i];
    //基数排序,q 存储第 i 个串现在的位置
}
int st;
void sol(){
	for (register int i=1; i<=zyf; i++) srt(st--);
	int tmp=tot+1;
	for (register int i=tot; i; i--){
		nxt[i]=tmp;
		if (q[i]<=n) tmp=i;
	}
   	tmp=0;
   	for (register int i=1; i<=tot; i++){
		pre[i]=tmp;
		if (q[i]<=n) tmp=i;
	}
	for (register int i=1; i<=tot; i++)
		if (q[i]>n){
			int b1=pre[i],b2=nxt[i];
			for (register int j=1; j<=em&&b1&&b2<=tot; j++){
                int xsl=(int)(s[q[i]]^s[q[b1]]).count();
                if (xsl<ans[q[i]-n]) ans[q[i]-n]=xsl;
                xsl=(int)(s[q[i]]^s[q[b2]]).count();
                if (xsl<ans[q[i]-n]) ans[q[i]-n]=xsl;
				b1=pre[b1]; b2=nxt[b2];
			}
		}
}
int main(){
    srand(time(0));
    st=rand()%110+140;
	memset(ans,0x3f,sizeof ans);
	for (int i=0; i<10; i++) OX['0'+i]=i;
	for (int i=0; i<6; i++) OX['A'+i]=i+10;
	scanf("%d%d%llu%llu",&n,&m,&a1,&a2);
	tot=n+2*m;
	gen(n,a1,a2);
	for (register int i=1; i<=m; i++){
		k[i]=gt();
		for (register int j=0; j<64; j++){
			int yxh=OX[t[j]];
			s[n+2*i-1][4*j]=(yxh>>3)&1;
            s[n+2*i][4*j]=((yxh>>3)&1)^1;
			s[n+2*i-1][4*j+1]=(yxh>>2)&1;
			s[n+2*i][4*j+1]=((yxh>>2)&1)^1;
			s[n+2*i-1][4*j+2]=(yxh>>1)&1;
			s[n+2*i][4*j+2]=((yxh>>1)&1)^1;
			s[n+2*i-1][4*j+3]=yxh&1;
			s[n+2*i][4*j+3]=(yxh&1)^1;
		}
	}
	for (register int i=1; i<=tot; i++) q[i]=i;
	for (int i=1; i<=eg; i++) sol();
	bool las=0;
	for (register int i=1; i<=m; i++){
		if (las) las=(ans[2*i]<=k[i]);
		else las=(ans[2*i-1]<=k[i]);
		if (las) puts("1");
        else puts("0");
	}
    //重新统计强制在线的答案
	return 0;
    //极度卡常的代码,带有随机化因素,因此您提交上去可能会 WA/TLE
}

以上做法仅供参考,正式考试使用视人品而定(告诉了我们行善积德的重要性)

看起来同样是随机化 AC 了这道题,但本质上还是一种非正解(因为考场上没有足够的数据给你调参),最终想稳定拿下 100pts 还是要使用上一篇题解的做法

话说这道题的分块期望正解我都想不出来,果然我还是太菜了

posted @ 2021-11-19 08:57  yyyyxh  阅读(80)  评论(0编辑  收藏  举报