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.

转载于:https://www.cnblogs.com/Snow-Dancer/archive/2012/07/04/2576866.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值