OI补完计划——Day3模拟赛简述和Tarjan算法
Day3
{到郑州的第一天,才知道我们的集训是做10天的模拟赛,所以。。。先来总结一下第一天的情况吧,今天刚刚来,不太熟悉情况,所以各种手生啊,考试到最后程序竟然没有交上去。。。}
第一天题还是比较简单的,第一题是广搜,很裸。。但我用了双向的,更无语的是双广的步数记录并没有我想得那么简单,悲剧了。第二题是求强连通分量后的分层图最短路,忘记限制层数了,再次悲剧。第三题是动态规划,时间不够了,考场上想得不够完全,下来后改了很久才改对,看来我还是需要多多修炼啊。
放一下第三题吧。
【问题描述】
在24世纪,Z国将成为科技最发达的国家,特别是在生物工程领域,他们拥有这样的高新科技,比如使用基因剪,可以将基因链从任意一处断开,而使用基因胶,又可以将任意的基因片断粘到一个集成链上。我们知道,DNA是由一个核苷酸聚合链构成的,一共有4种类型的核苷酸,分别为"A","T","C","G"。
有一次,Z国的研究人员从一些原始的有机组织中提取出了一条DNA链,他们的任务是通过对旧的DNA进行重组,进而生成一条新的DNA链。他们投资了一个计划,用如下的方式使用基因剪和基因胶。
首先,他们将DNA链在某些连接点处断开,这样整个链就会就变成一些DNA片断,然后他们再决定是保留还是丢弃这些片断,如果保留这些片断,他们将利用基因胶把它们粘成一条新链,注意,粘的过程中不能打乱这些片断原来的顺序。在整个过程中他们不得不非常小心地确定这些片断的去留。
当然了,做这些操作是需要花费代价的,每一个“剪开”的操作代价为1,每一个“粘合”操作花费代价也为1。请你写一个程序计算:如果存在一种方式生成一个新的DNA链,那么所需操作的最小代价是多少?
请看下面的例子:
旧的DNA:A---T---A---C---C---G
剪开后: A
T A---C C---G
保留: T
C---G
新的DNA链: T---C---G
花费代价:4
【输入格式】
输入文件第一行有一个正整数T,表示接下来测试数据的个数。每一个测试数据包含两行,每行一个字符串,第一个字符串表示旧的DNA链,第二行为新的DNA链。每个字符串都由"A","T","C"和"G"组成,每一个字符串的长度均不超过3000。
【输出格式】
对于每一个测试数据,输出其最小花费,如果没办法生成新的DNA链,则输出“-1”。
【样例】
dna.in
2
ATACCG
TCG
ATACCG
CTG
dna.out
4
-1
这是一道很明显的动态规划,只是方程不太容易表示。第一思路当然是最长公共子序列,但是如何转化到这道题上呢?我的思路是在求LCS的同时求出最少将母序列切开几刀可以拼成目标串。(粘的次数可以由结果直接算出来,后面再说)
动归除了原来的F[i,j]数组记录到第一个串的i位置,第二个串的j位置的LCS外,还有两个数组con[i,j]和ncon[i,j]表示分别将母串的前i个字符切开后,最后一段保留的或不保留的最小切开次数。(同时保证切开后一定可以拼成目标串的前j个字符,否则为无穷大)。
这样就可以和F数组一起动归了。
方程如下:
If s[i]=s[j] then
If f[i-1,j]=j then
Ncon[i,j]=min(ncon[i-1,j],con[i-1,j]+1)
Else ncon[i,j]=INF
If f[i-1,j-1]=j-1 then
Con[i,j]=min(con[i-1,j-1],ncon[i-1,j-1]+1)
Else con[i,j]=INF
Else
If f[i-1,j]=j then
If f[i-1,j]=j then
Ncon[i,j]=min(ncon[i-1,j],con[i-1,j]+1)
Else ncon[i,j]=INF
Con[i,j]=INF
(显然边界是con[i,0]=INF)
方程比较好理解吧。
现在就要考虑一下粘的次数了,我们已经得到了最少切割次数,同时又知道保留段和非保留段一定是相间的,那么根据最后一段是否保留就可以得到最少保留的段数,这样分别计算两种情况的操作次数即可解决本题,时间复杂度为O(N^2)。
再写一点关于Tarjan算法来求有向图强连通分量和无向图割点割边。还是老规矩,这不是算法讲稿,学算法的同学可以参见ByVoid大牛的博客,这里是给出了我在学习时的直观理解。先说求SCC,Tarjan算法基于DFS,我们要用栈来记录访问到的点,我们可以想象属于一个强连通分量的点在栈中一定是连续的,我们要做的就是确定那些连续的点是强连通分量。
算法重点在于计算了DFN和LOW两个数组,分别用于记录该点的深搜遍历序号和其孩子节点情况,什么叫做孩子节点的情况呢?想像图其中一点被悬挂在一个竖直平面内,那么整个以他的孩子节点走来走去能达到的最小编号。也就是说看看该节点的所有孩子是否都在这个节点之下(最小编号等于遍历编号),如果是的话(我们称当前节点限制了他的孩子节点)那么他之下的所有点就是一个强连通分量。我们只要将栈中的点弹出至当前节点即可,可是怎么保证弹出的点均属相互可达的呢?我们考虑当前节点之下的那些节点,如果其中有些点无法到达该点,那么它们一定受到了当前点之下的点的限制,也就是说在未到达当前节点之前,已经产生了强连通分量(如果还有上述情况,则用上述讨论递归解决),这些点已经被弹栈。因此,栈中的点均为相互可达的。
而无向图中求割点与割边的方法本质上也是采用了节点限制的思想,具体可以考虑割点是图中块的交点,而割边则是桥。割点限制了不同的块,割边的不同两个顶点分别限制了两个连通分量(去掉割边后的)。求法与求强连通分量类似,只是算法结构和判断条件略有改变,具体可参见文末代码。
推荐链接:
http://www.byvoid.com/blog/scc-tarjan/
【欢迎大家指出文中的不足,我们再作进一步的讨论,如有错误我会及时修改】
【Snow Dancer原创,转载请注明,谢谢】
1 const 2 maxN=1000; maxE=10000; 3 var 4 dfn,low,stack:array[1..maxN] of longint; 5 h,x,next:array[1..maxE] of longint; 6 instack:array[1..maxN] of boolean; 7 n,i,j,k,l,SCC,top,index,m,t:longint; 8 procedure calSCC(v:longint); 9 var 10 p,u:longint; 11 begin 12 p:=h[v]; inc(index); 13 dfn[v]:=index; low[v]:=index; 14 inc(top); stack[top]:=v; instack[v]:=true; 15 while p<>0 do begin 16 u:=x[p]; 17 if dfn[u]=0 then begin 18 calSCC(u); 19 if low[u]<low[v] then low[v]:=low[u]; 20 end else 21 if instack[u] and (low[v]>dfn[u]) then 22 low[v]:=dfn[u]; 23 p:=next[p]; 24 end; 25 if dfn[v]=low[v] then begin 26 inc(scc); writeln(scc); 27 repeat 28 u:=stack[top]; dec(top); 29 instack[u]:=false; 30 write(u,' '); 31 until u=v; 32 writeln; 33 end; 34 end; 35 begin 36 readln(n,m); 37 for k:=1 to m do begin 38 readln(i,j); 39 inc(t); x[t]:=j; 40 next[t]:=h[i]; h[i]:=t; 41 end; 42 for i:=1 to n do 43 if dfn[i]=0 then calSCC(i); 44 end.
1 const 2 maxN=1000; maxE=10000; 3 var 4 dfn,low,stack:array[1..maxN] of longint; 5 h,x,next:array[1..maxE] of longint; 6 n,i,j,k,l,block,top,index,m,t:longint; 7 procedure calCutPoint(v:longint); 8 var 9 p,u:longint; 10 begin 11 p:=h[v]; inc(index); 12 dfn[v]:=index; low[v]:=index; 13 inc(top); stack[top]:=v; 14 while p<>0 do begin 15 u:=x[p]; 16 if dfn[u]=0 then begin 17 calCutPoint(u); 18 if low[u]<low[v] then low[v]:=low[u]; 19 if (dfn[v]<=low[u]) and (v<>i) then begin 20 inc(block); writeln(block); 21 repeat 22 k:=stack[top]; dec(top); 23 write(k,' '); 24 until u=k; 25 writeln(v); 26 end; 27 end else 28 if low[v]>dfn[u] then low[v]:=dfn[u]; 29 p:=next[p]; 30 end; 31 end; 32 begin 33 readln(n,m); 34 for k:=1 to m do begin 35 readln(i,j); 36 inc(t); x[t]:=j; 37 next[t]:=h[i]; h[i]:=t; 38 inc(t); x[t]:=i; 39 next[t]:=h[j]; h[j]:=t; 40 end; 41 for i:=1 to n do 42 if dfn[i]=0 then calCutPoint(i); 43 end.
1 const 2 maxN=1000; maxE=10000; 3 var 4 dfn,low,stack:array[1..maxN] of longint; 5 h,x,next:array[1..maxE] of longint; 6 n,i,j,k,l,Edge,top,index,m,t:longint; 7 procedure calCutEdge(v,fa:longint); 8 var 9 p,u:longint; 10 begin 11 p:=h[v]; inc(index); 12 dfn[v]:=index; low[v]:=index; 13 inc(top); stack[top]:=v; 14 while p<>0 do begin 15 u:=x[p]; p:=next[p]; 16 if u=fa then continue; 17 if dfn[u]=0 then begin 18 calCutEdge(u,v); 19 if low[u]<low[v] then low[v]:=low[u]; 20 if dfn[v]<low[u] then begin 21 inc(Edge); writeln(Edge); 22 repeat dec(top); until stack[top+1]=u; 23 writeln(v,' ',u); 24 end; 25 end else 26 if low[v]>dfn[u] then low[v]:=dfn[u]; 27 end; 28 end; 29 begin 30 readln(n,m); 31 for k:=1 to m do begin 32 readln(i,j); 33 inc(t); x[t]:=j; 34 next[t]:=h[i]; h[i]:=t; 35 inc(t); x[t]:=i; 36 next[t]:=h[j]; h[j]:=t; 37 end; 38 for i:=1 to n do 39 if dfn[i]=0 then calCutEdge(i,0); 40 end.