[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 还是要使用上一篇题解的做法
话说这道题的分块期望正解我都想不出来,果然我还是太菜了