虫逢——随机化数据的随机化处理

【清华集训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;
}
posted @ 2022-10-09 12:39  windf_风岚  阅读(98)  评论(0)    收藏  举报
1