虫逢——随机化数据的随机化处理
【清华集训2014】虫逢
一道随机化数据的好题。
题干
小强和阿米巴是好朋友。
阿米巴告诉小强,变形虫(又叫阿米巴虫)和绝大多数生物一样,也是有 DNA 的。并且,变形虫可以通过分裂的方式进行无性繁殖。
我们把一个变形虫的基因组抽象成一个大小为 LLL 的基因集合。每个基因都是一个 444 位长的字符串(字符包括大小写字母、数字、符号 “~!@#%^&()[]`:;"'<>,.?/|=-{}”)。现在,有 N个变形虫凑到了一起。由于他们是从天南海北过来的,我们可以认为,他们的基因组都是从一个大小为 个变形虫凑到了一起。由于他们是从天南海北过来的,我们可以认为,他们的基因组都是从一个大小为 个变形虫凑到了一起。由于他们是从天南海北过来的,我们可以认为,他们的基因组都是从一个大小为M的“变形虫基因库“中独立的随机的选取L个基因得到的。目前人类并不了解这个基因库里都有什么基因,但是我们知道它的大小是 的“变形虫基因库“中独立的随机的选取L个基因得到的。目前人类并不了解这个基因库里都有什么基因,但是我们知道它的大小是 的“变形虫基因库“中独立的随机的选取L个基因得到的。目前人类并不了解这个基因库里都有什么基因,但是我们知道它的大小是M$。
这时,环境突然发生巨变。这\(N\)个变形虫在外界的刺激下同时进行了一次分裂。每个变形虫分裂成了两个。分裂的过程中,原来的变形虫的基因组(基因的集合)被原样的复制成了两份,分别进入两个新的变形虫。两个新变形虫中的一只的基因组中有一半发生了突变,被替换为“变形虫基因库”中随机的其他的基因。如果两个变形虫是由原来的一个变形虫产生的,我们叫它们“同源”的。
给出 \(2N\) 个变形虫的基因组,请你找出每个变形虫同源的另一只虫是谁。
输入格式
第一行三个整数 \(N\)、\(M\)、\(L\)。
接下来一行 \(2NL×4\)个字符,依次表示每个集合中的元素。集合内的元素之间的顺序是无关紧要的。
输出格式
输出\(2N\)行,每行一个整数表示第\(i\)个变形虫(从\(1\)开始标号)同源的另一只变形虫是谁。
思路:
50pt暴力
非常容易想到一个朴素的做法。
我们对序列中的每一个基因片段都单独计算hash值,同时标记出这个基因属于哪一只虫子。
同时,我们用结构体去存储每一个基因的所属与hash值。
这样,我们就可以以hash值大小为关键字排序。排序后,基因会排列成一段一段的。
我们建立一个二维的\(fri[i][j]\)数组,表示第\(i\)和\(j\)只虫子间有多少相同的基因,即相似度。
这样,我们就可以对每一段中的每一个基因的所属\(i\),\(j\)只虫子间的数组提供贡献,最后\(O(n^2)\)的找出每一对相似度恰好为\(L\)的虫子。
这个算法的时间和空间复杂度均约为\(O(n^2)\),可以说暴力到极致了。
Coding:
#include<bits/stdc++.h>
#define N 7200
#define M N*2*80
#define base 131
#define ll long long
#define ull unsigned long long
#define f1(i,n,m) for(int i=n;i<=m;++i)
#define f2(i,n,m) for(int i=n;i>=m;--i)
using namespace std;
char s[M*4];
int n,m,l,tot;
int bel[M],fri[N][N],to[N];
ull hs[M];
int cnt[N];
struct node{
ull hs;
int bel;
}a[N*80];
inline ull get(char c[]){
ull res=0;
f1(i,1,4)res=res*base+(int)c[i];
return res;
}
bool cmp(node a,node b){
return a.hs<b.hs;
}
signed main(){
int pre;
freopen("dna.in","r",stdin);
freopen("dna.out","w",stdout);
scanf("%d%d%d",&n,&m,&l);
scanf("%s",s+1);
f1(i,1,2*n)f1(j,1,l){
a[++tot].hs=get(s+(i-1)*l*4+(j-1)*4);
a[tot].bel=i;
}
sort(a+1,a+1+tot,cmp);
f1(i,1,tot)
if(a[i].hs==a[i-1].hs){
f1(j,pre,i-1)fri[a[i].bel][a[j].bel]++;
else pre=i;
}
f1(i,1,2*n)f1(j,1,i-1)
if(fri[i][j]+fri[j][i]>=(l/2))to[i]=j,to[j]=i;
f1(i,1,2*n)printf("%d\n",to[i]);
return 0;
}
100pt做法
这个做法的正确性是基于题目数据的随机性得到的
首先,和暴力做法一样,我们先用hash预处理出每一个基因片段,这里我们为了方便存储数据,将hash值利用unordered_map进行离散化(由于在离散化中
会多次查询数据,使用map常数会很非常大~)。
此时,在存储离散化后的数据时,不能对单独某一个位置的基因的所属存储,而是要对某一种基因,整体存储他在哪些变形虫中存在。
如何实现呢,我们可以使用一个\(01\)串来表示,第\(i\)个01串中第\(j\)位为\(1\)表示第\(j\)个变形虫中含有第\(i\)中基因。对于多个\(01\)串而言,我们可以很自然地想到\(bitset\),于是我们得到了1~M个01串,得以快捷存储相同基因关系序列。这个序列的作用会在下面说明。
此外,我们也要存储下第\(i\)个变形虫的第\(j\)个基因是哪一种,作用下面说。
预处理结束后,考虑本题数据为随机生成,不会有特殊构造的数据,于是想到了一种随机化做法。
对任意一个变形虫\(i\),考虑从L个基因中任选位置不同的\(k\)个,利用前面预处理出的虫子任意位置的基因种类,得到了\(k\)种基因,我们将这\(k\)种基因的\(bitset\)数组相与,最后得到的新\(bitset\)中值为\(1\)的位置的虫子即为同时拥有这\(k\)种基因的虫子,最后随机选取一个除第\(i\)个以外的虫子\(j\),暴力\(O(L)\)地比较每一种基因,判断是否同源,重复此过程,直到找到同源的。
这里的\(k\)取\(3\)就可以通过了。
之后补怎么得到的
Coding :
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
#define N 34000
#define L 150
#define ll long long
#define f1(i,n,m) for(int i=n;i<=m;++i)
#define f2(i,n,m) for(int i=n;i>=m;--i)
using namespace std;
int n,m,l,tot;
bitset<N>bit[N>>1];
unordered_map<int,int>mp;
mt19937 rnd(19937);
int son[N][L],to[N];
char s[N*L*4];
inline int get(char s[]){
int res=0;
f1(i,1,4)res=(res<<7)+(int)s[i];
return mp[res]?mp[res]:mp[res]=++tot;
}
inline int clac(int i,int j){
int res=0;
f1(k,0,l-1)res+=bit[son[i][k]][j];
return res;
}
signed main(){
freopen("dna.in","r",stdin);
freopen("dna.out","w",stdout);
int a,b,c,tmp;
bool p;
bitset<N>t;
scanf("%d%d%d %s",&n,&m,&l,s+1);
n<<=1;
f1(i,1,n)f1(j,1,l){
tmp=get(s+(i-1)*l*4+(j-1)*4);
bit[tmp][i]=1;//第tmp种基因在第i个虫子的的基因中存在
son[i][j-1]=tmp;//第i个虫子的第j-1种基因为tmp基因
//这里取j-1是因为在下面的取余中会出现0,所以下标从零开始
}
f1(i,1,n){
if(to[i])continue;
p=1;
while(p){
a=b=c=0;
while(a==b||b==c||c==a)a=rnd()%l,b=rnd()%l,c=rnd()%l;//在L中随机选取三个片段,此处a,b,c为第几个
a=son[i][a],b=son[i][b],c=son[i][c];//此处的a,b,c为基因标号
t=bit[a]&bit[b]&bit[c];//将三种基因的所属相与后,得到为三种基因的都有的原虫
tmp=t._Find_next(i);//选取除第i只虫子外的另一个虫子
if(tmp<=n&&clac(i,tmp)*2==l)to[i]=tmp,to[tmp]=i,p=0;//如果两只虫子的相似片段数等于l,则为同源虫,终止循环
}
}
f1(i,1,n)printf("%d\n",to[i]);
return 0;
}