题目 :Bovine Genomics G奶牛基因组

传送门: 洛谷P3667


题目描述

Farmer John owns NN cows with spots and NN cows without spots. Having just completed a course in bovine genetics, he is convinced that the spots on his cows are caused by mutations in the bovine genome.

At great expense, Farmer John sequences the genomes of his cows. Each genome is a string of length MM built from the four characters A, C, G, and T. When he lines up the genomes of his cows, he gets a table like the following, shown here

for N=3N=3 and M=8M=8 :

Positions: 1 2 3 4 5 6 7 8

Spotty Cow 1: A A T C C C A T

Spotty Cow 2: A C T T G C A A

Spotty Cow 3: G G T C G C A A

Plain Cow 1: A C T C C C A G

Plain Cow 2: A C T C G C A T

Plain Cow 3: A C T T C C A T

Looking carefully at this table, he surmises that the sequence from position 2 through position 5 is sufficient to explain spottiness. That is, by looking at the characters in just these these positions (that is, positions 2 \ldots 52…5 ), Farmer John can predict which of his cows are spotty and which are not. For example, if he sees the characters GTCG in these locations, he knows the cow must be spotty.

Please help FJ find the length of the shortest sequence of positions that can explain spottiness.

FJ有一些有斑点和一些没有斑点的牛,他想搞清楚到底什么基因控制这个牛有没有斑点。

于是他找了n有斑点的牛和n头没有斑点的牛

这些牛的基因长度为m(基因中之包含ATCG四个字母)

求这个序列中的一个子串,可以确定是否有斑点。

子串需要符合要求:有斑点的牛这部分的子串,不能和无斑点的牛的这部分子串相同

求最短子串长度(就是说条件为任意相同范围的区间中,斑点牛群和无斑点牛群中任意一对牛的基因不相等。在此条件下要求最短的区间长度)

输入输出格式

输入格式:

The first line of input contains NN ( 1 \leq N \leq 5001≤N≤500 ) and MM ( 3 \leq M \leq 5003≤M≤500 ). The next NN lines each contain a string of MM characters; these describe the genomes of the spotty cows.

The final NN lines describe the genomes of the plain cows. No spotty cow has the same exact genome as a plain cow.

输出格式:

Please print the length of the shortest sequence of positions that is sufficient to explain spottiness. A sequence of positions explains spottiness if the spottiness trait can be predicted with perfect accuracy among Farmer John’s population of cows by looking at just those locations in the genome.

输入输出样例

输入样例#1:

3 8

AATCCCAT

ACTTGCAA

GGTCGCAA

ACTCCCAG

ACTCGCAT

ACTTCCAT

输出样例#1:

4

最近都在做trie树的题目,然而题目要么就是太bug要么就是没什么好讲的。but,but!我居然刷到了一道披着trie树皮最后要用差分A的无耻骗人trie树题!说好的trie树呢…TAT

一度分析

那么先来讲讲我是怎么“错”的吧。

首先看到这道题我果断用trie思考。首先二分答案,然后暴力枚举构造trie(整个就是错误的算法,与题意都不符然而就是A了一个点,因为题目中说的是相同范围的区间,我是全部区间弄一块儿找了,答案明显会偏大)。

然后代码如下我也不多废话,只能说,看看就好,看看就好。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=510;
inline int read(){
int x=0,f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
int n,m,cnt;
int a[M*2][M];
int t[M*M*10][5]; //一棵trie树 inline bool check(int mid){
//这是模板吧?trie树。
for(int i=1;i<=n;++i)//枚举每头有斑点的牛
for(int j=1;j+mid-1<=m;++j){//枚举左边界(同时右边界也定了)
for(int now=0,k=j;k<=j+mid-1;++k){ //然后是trie树
int tmp=a[i][k];
if(!t[now][tmp])
t[now][tmp]=++cnt;
now=t[now][tmp];
}
}
for(int i=n+1;i<=2*n;++i) //枚举无斑点的牛
for(int j=1;j+mid-1<=m;++j){//枚举左边界(同上)
int now=0,flag=1;
for(int k=j;k<=j+mid-1 && flag;++k){
int tmp=a[i][k];
if(!t[now][tmp]) flag=0;//如果说有不相同的了就flag标成false
now=t[now][tmp];
}
if(flag) return false;//如果是能搜到底的就直接返回false
}
return true; //没有一头无斑点牛基因能在trie树中完全匹配那就满足条件,返回true
} int main(){
n=read(); m=read();
for(int i=1;i<=n*2;++i)
for(int j=1;j<=m;++j){
char c=getchar(); while(!isupper(c)) c=getchar();
switch(c){
case 'A': a[i][j]=1; break;
case 'T': a[i][j]=2; break;
case 'C': a[i][j]=3; break;
case 'G': a[i][j]=4; break;
}
}
int l=1 , r=n;
while(l<=r){ //标准二分,搞得主函数很简洁
memset(t , 0 , sizeof(t));
int mid=l+r>>1; cnt=0;
if(check(mid)) r=mid-1; //满足条件则答案可以更小
else l=mid+1;//否则答案要更大
}
printf("%d\n",l);
return 0;
}

对于该程序的正确性,不用我说,洛谷显示是这样的:

看到了这样的结果后,本人表示…emmm…不服!不A此题誓不罢休!

然后看了看某dalao的解题报告之后,发现这道题。。。原来可以不用trie树做…(心中一万匹草泥马在奔腾)。

那么用什么算法呢?这里先留个悬念。我们先来看看一道题:

题目:???(记不清了_(:з」∠)_

题意:这个记得清(不然也没法讲)。

题意就是给你一个长度为n的数字序列,让你选出一段最短的区间,使该区间内所有数字的和为给定的一个整数k。
然后输出该最短区间的长度。

输入格式:

第一行是一个n和k。
第二行是n个数。
第三行。。。。没了

输出格式

一行一个数,表示最短的区间长度

数据范围

还是不知道着就n<=1e5,然后k<=1e5, too吧!
另外当然也保证数字序列中的每一个数都是正整数咯。

分析

相信大多数人瞬间想到的就是枚举左右区间,前缀和再减一减。
但是一看到数据范围的时候就会懵逼了:1e5平方一下瞬间就T了。可见标算不会是暴力的。
那么如果用差分的方法呢,时间复杂度瞬间就会达到一个可怕的地步:O(n)。然后稳过。
那么怎么差分呢?首先我们考虑:在我们暴力枚举左右边界时,其实有很多趟循环是无意义的。
何来浪费一说?比如,有一个序列是(2,5,3,2,2,1,3),k为7,
那么假如我们已经做过了左区间i=1,右区间j=3的情况,发现sum已经大于k了,
那么此时我们还需要考虑i=1 ,j=4、5、6的情况么?
答案很显然(抱歉用了一个我很讨厌的词_(:з」∠)_)
于是我们要在这个方面对程序进行优化。(然而我还并没有讲如何优化)
如何优化? 首先我们可以先试着随意枚举一个区间,那么如果这个区间的sum是小于k的,
我们就需要将这个区间变大,然后才会使sum变大吧?那么向左还是向右扩大区间呢?
我们先不着急,这里还有一种情况,就是说sum已经大于k了,也就说明我们的区间取大了,
那么这两种情况很类似吧?于是我们不妨做个约定:sum 大 l 加,sum 小 r 加。
这里的l便代表了左边界,而r则代表了右边界。
那么我们还有一个疑问没有解决:初始的区间在哪里?中间?两边?还是随便取吗?
其实我们大概想想也就知道了,初始区间要在整个数字序列的最左边,即l和r重合。
emmmm...不对,应该是l>r,因为l=r的时候还是有一个数字被框在区间内的。
当然为了代码方便,我们也可以用l和r表示当前区间为l+1 ~ r,这样计算区间长的时候,
我们只需要将 r-l 就行了。
but,如果你是一名素质较高的OIer,那你在看完算法思路之后肯定会立刻抛出一个疑问,
那便是算法的正确与否,在这道题中就是说,有没有任意一个符合条件的区间会被遗漏?
那么这个问题这里就不解释了,童鞋们自个儿验证去吧! _(:з」∠)_
(然后学了莫队之后,现在想想,两种算法在某种程度上来讲真的还是有点像的呢...)

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+100;
inline int read(){
int x=0,f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
int n,k,sum,res=M;
int a[M]; int main(){
n=read(); k=read(); //因为k没有爆int,sum也不会爆int
for(int i=1;i<=n;++i)
a[i]=read();
int lef=1,rig=1; //初始l、r指针都在1,当前区间为l+1 ~ r
while(rig<=n){
if(sum==k) res=min(res , rig-lef); //满足条件则维护最小区间长
if(sum<k){ //小了就让rig++,区间变大
sum+=a[rig];
++rig;
}
else{ //不然lef++,区间变小
sum-=a[lef];
++lef;
}
}
//特判无解(当然我也不知道题目中有没有说数据保证有解)
printf("%d\n",res==M ? -1 : res);
return 0;
}

时间复杂度:O(n),很稳能A(当然由于程序是现码的,题目也记不清所以并没有在 OJ 上测过,但总的来说算法思路还是正确的)。另外没题目这事儿…抱歉啦同志们,很遗憾我真的记不起来啦…


然后对于这道题同学们有了点赶脚了么?我不妨先说了,这道 t 就是可以用以上的差分算法+随机数hash 给A掉的…

二度分析

然后再讲讲我是怎么“做”的。

那么 随机数hash 我在这里也不打算说什么,不是很难懂的吧?就是一直莫名担心这随机数太随机用起来有点虚啊(万一…万一…取到的数字…太偶然呢?),然而落谷的数据是告诉我不会发生那种不好的事情的啦~

那么差分怎么弄?其实这玩意儿可以说是有个模板吧,就是说满足条件 l++,不满足条件 r++ ,上面那道题就是以sum > k 作为条件的。

这道题很显然啊,就是以没有字符串重合为条件的。

于是我也不啰嗦,直接上代码不浪费口舌了吧!

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=2333333;
set<ll> S;
int n,m,res;
int s[2][510][510];
int val[510],hash[2][510]; int main(){
res=0x7f7f7f7f;
srand(time(NULL));
scanf("%d%d",&n,&m);
for(int k=1;k>=0;--k)
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
char c=getchar(); while(!isupper(c)) c=getchar();
s[k][i][j]=c-'A'+1;
}
for(int i=1;i<=m;++i)
val[i]=rand()%mod; //无脑 rand hash 。
int lef=1,rig=1;
bool flag=false;
while(rig<=m){
S.clear();
if(flag) res=min(res , rig-lef); // 当前满足条件的子串为lef+1~rig
//假如此刻有斑点的奶牛群和无斑点的奶牛群中有奶牛hash值相等则将rig向后移
if(!flag){ //不满足条件
flag=true;
for(int i=1;i<=n;++i){
hash[1][i]=(hash[1][i]+val[rig]*s[1][i][rig])%mod;
S.insert(hash[1][i]);
}
for(int i=1;i<=n;++i){
hash[0][i]=(hash[0][i]+val[rig]*s[0][i][rig])%mod;
if(S.count(hash[0][i])) flag=false;
}
++rig;
}
//否则贪心一点将lef后移查看是否有更优解
else{ //满足条件
flag=true;
for(int i=1;i<=n;++i){
hash[1][i]=(hash[1][i]+mod-val[lef]*s[1][i][lef]%mod)%mod;
S.insert(hash[1][i]);
}
for(int i=1;i<=n;++i){
hash[0][i]=(hash[0][i]+mod-val[lef]*s[0][i][lef]%mod)%mod;
if(S.count(hash[0][i])) flag=false;
}
++lef;
}
}
printf("%d\n",res); //这里应该是保证有答案的吧。。。(从题目没有说无解的情况上来说)
return 0;
}

ok,Judge 课堂就这样愉快的结束了喜欢别忘点个赞哈~_(:з」∠)_

洛谷 [USACO17OPEN]Bovine Genomics G奶牛基因组(金) ———— 1道骗人的二分+trie树(其实是差分算法)的更多相关文章

  1. 洛谷 P3670 [USACO17OPEN]Bovine Genomics S奶牛基因组(银)

    P3670 [USACO17OPEN]Bovine Genomics S奶牛基因组(银) 题目描述 Farmer John owns NN cows with spots and NN cows wi ...

  2. 【题解】洛谷P3119 Grass Cownoisseur G

    题面:洛谷P3119 Grass Cownoisseur G 本人最近在熟悉Tarjan的题,刷了几道蓝题后,我飘了 趾高气扬地点开这道紫题,我一瞅: 哎呦!这不是分层图吗? 突然就更飘了~~~ 用时 ...

  3. BZOJ1563/洛谷P1912 诗人小G 【四边形不等式优化dp】

    题目链接 洛谷P1912[原题,需输出方案] BZOJ1563[无SPJ,只需输出结果] 题解 四边形不等式 什么是四边形不等式? 一个定义域在整数上的函数\(val(i,j)\),满足对\(\for ...

  4. 洛谷 P2986 [USACO10MAR]伟大的奶牛聚集Great Cow Gat…(树规)

    题目描述 Bessie is planning the annual Great Cow Gathering for cows all across the country and, of cours ...

  5. 【洛谷1345】 [USACO5.4]奶牛的电信(最小割)

    传送门 洛谷 Solution emmm,直接对于每一个点拆点就好了. 然后边连Inf,点连1,跑最小割就是答案. 代码实现 #include<bits/stdc++.h> using n ...

  6. 洛谷CF1071E Rain Protection(计算几何,闵可夫斯基和,凸包,二分答案)

    洛谷题目传送门 CF题目传送门 对于这题,我无力吐槽. 虽然式子还是不难想,做法也随便口胡,但是一些鬼畜边界情况就是判不对. 首先显然二分答案. 对于每一个雨滴,它出现的时刻我们的绳子必须落在它上面. ...

  7. 洛谷.5283.[十二省联考2019]异或粽子(可持久化Trie 堆)

    LOJ 洛谷 考场上都拍上了,8:50才发现我读错了题=-= 两天都读错题...醉惹... \(Solution1\) 先求一遍前缀异或和. 假设左端点是\(i\),那么我们要在\([i,n]\)中找 ...

  8. 洛谷P4382 [八省联考2018]劈配(网络流,二分答案)

    洛谷题目传送门 说不定比官方sol里的某理论最优算法还优秀一点? 所以\(n,m\)说不定可以出到\(1000\)? 无所谓啦,反正是个得分题.Orz良心出题人,暴力有70分2333 思路分析 正解的 ...

  9. 洛谷P3104 Counting Friends G 题解

    题目 [USACO14MAR]Counting Friends G 题解 这道题我们可以将 \((n+1)\) 个边依次去掉,然后分别判断去掉后是否能满足.注意到一点, \(n\) 个奶牛的朋友之和必 ...

随机推荐

  1. Spring+Mybatis+SpringMVC+Atomikos多数据源共存+不同数据库事物一致性处理

    网上找了一大堆的例子,没一个跑通的,都是copy转发,哎,整理得好辛苦..做个笔记,方便正遇到此问题的猿们能够得到帮助....废话不多说,贴代码..... 项目结构说明: 1.dao层的admin.w ...

  2. IDEA之debug的坑

    IDEA是一款火热的开发工具.debug谁都会,很简单?NO 一次不正常的关机,导致第二条上班debug失效,浪费两个小时.特做此记录. 1.如下图点击View Breakpoints进入可以到你设置 ...

  3. hdu 6383

    题意是说给定一个序列,能否通过任意次对部分数字 +1,对部分数字 -2的操作使得序列在满足全部非负且任意两元素的差值不超过1的前提下最小值最大,求最大值. 一开始的时候没有注意到整个序列全是非负数,还 ...

  4. SQL Server进阶(十一)存储过程

    存储过程和函数的区别 存储过程是第一次编译之后就会被存储的下来的预编译对象,之后无论何时调用它都会去执行已经编译好的代码.而函数每次执行都需要编译一次.总结下来有下面几个区别: 基本不同: 函数必须有 ...

  5. 【C++】reference parameter-引用参数

    1.reference parameter 以下两个函数等效,只调用方式不同: 1> 1 int reset(int i){ 2 i = 13; 3 return i; 4 } 5 6 int ...

  6. python基础 range()与np.arange()

    range()返回的是range object,而np.nrange()返回的是numpy.ndarray() range尽可用于迭代,而np.nrange作用远不止于此,它是一个序列,可被当做向量使 ...

  7. mysql数值运算符和函数

    mysql> |+------------+1 row in set (0.00 sec) mysql> SELECT FLOOR(3.99);  # 舍1取整+------------- ...

  8. dbms_redefinition在线重定义表结构

    dbms_redefinition在线重定义表结构 (2013-08-29 22:52:58) 转载▼ 标签: dbms_redefinition 非分区表转换成分区表 王显伟 在线重定义表结构 在线 ...

  9. 数组B:我想我需要一艘船屋

    Fred Mapper is considering purchasing some land in Louisiana to build his house on. In the process o ...

  10. 第19月第17天 uitextview 文本垂直居中 uiimage中间不拉伸

    1. open class VericalCenteringScrollView: UIScrollView { override open var contentOffset: CGPoint { ...