算法竞赛入门经典第6章图论和树:竞赛选讲部分与习题

617 给定如下输入,输入每一组数据的表达式。

2
 A
 |
--------
B C   D
  |   |
----- -
  E F G
#
e
|
----
f g

这道题目就要直接在输入的字符数组里面递归遍历,如果想要将输入标准化之后再处理会发现特别的麻烦,各种小细节容易弄错,所以遇到这种情况就直接在原始输入里面递归就好,不需要建树。


buf = ['' for i in range(205)]
n = 0
def issym(x):return x!=' ' and x!='-' and x!='|' and x!='\n'
def dfs(r,c):
    print('{0}('.format(buf[r][c]),end='')
    if r+1<n and buf[r+1][c]=='|':
        j = c-1
        while j>=0 and buf[r+2][j]=='-':j-=1
        for i in range(j+1,min(len(buf[r+3]),len(buf[r+2]))):
            if  (buf[r+2][i]==' 'or buf[r+2][i])=='\n':break
            if  issym(buf[r+3][i]):dfs(r+3,i)


    print(')',end='')
def proc():
    global n
    T = int(input())
    while T:
        T-=1
        n = 0
        buf[0] = input()
        while buf[n]!='#':
            n+=1
            line = input()
            buf[n] = line

        print('(',end='')
        for i,each in enumerate(buf[0]):    
            if each!=' ':dfs(0,i);break
        print(')')
proc()

618 此题是一个较难的题,有几个难点

(a): 首先雕塑互相嵌套,要计算它的面积是很麻烦的,所以可以采取在格子外面加上一层空气,然后将所有的外部空气格子做一次floodfill,这样就可以计算得到表面积和体积。

(b): 然后要想到采用离散化技术,这样就可以避免过大的空间需求,但是这里是对三个维度同时进行离散,所以相对而言更加抽象。

(c): 最后是在采取dfs找连通的时候要注意,某一个立方体体积只需要计算一次,但是面积可能要计算多次。因此对于在dfs时访问到的雕塑立方体,不能标记为已经访问,因为还会从另外的方向来访问它的。

(d): 最后在初始化图的时候也要注意一些细节。

direction = [(-1,0,0,0),(1,0,0,0),(0,-1,0,1),(0,1,0,1),(0,0,-1,2),(0,0,1,2)]
x,y,z = set([1,500]),set([1,500]),set([1,500])
def add(s,u,v):s.add(u);s.add(u+v)
def setsort(s):return sorted(list(s))
def find(s,t):
    for i in range(len(s)):
        if s[i]>=t:return i
def midin(x,i,u,v):
    m=(x[i]+x[i+1])/2
    return m>u and m<v
def bound(x,l):return x>=0 and x<l
def df(s,i):return s[i+1]-s[i]
def floodfill(graph,l1,l2,l3):
    global x,y,z
    def dfs(i,j,k,adj_area):
        global direction
        nonlocal graph,vis,area,vol
        a,b,c = df(x,i),df(y,j),df(z,k)
        if (i,j,k) in vis:return
        if graph[i][j][k]==1:area+=adj_area;return
        vis.add((i,j,k))
        vol += a*b*c
        ar = [b*c,a*c,a*b]
        for m,n,t,h in direction:
            ni,nj,nk = i+m,j+n,t+k
            if bound(ni,l1) and bound(nj,l2) and bound(nk,l3):
                if (x[ni],x[nj],x[nk]) == (3,3,3):print('*****',i,j,k)
                dfs(ni,nj,nk,ar[h])
    vis = set()
    area,vol = 0,0
    dfs(0,0,0,0);
    print(area,501**3-vol)

def proc():
    global x,y,z
    T = int(input())
    for i in range(T):
        x,y,z = set([0,1,500,501]),set([0,1,500,501]),set([0,1,500,501])
        nodes = set()
        for j in range(int(input())):
            coor = input().split(' ')
            x0,y0,z0,x1,y1,z1 = [int(k) for k in coor]
            add(x,x0,x1);add(y,y0,y1);add(z,z0,z1)
            nodes.add((x0,y0,z0,x0+x1,y0+y1,z0+z1))
        x,y,z = setsort(x),setsort(y),setsort(z)
        l1,l2,l3 = len(x)-1,len(y)-1,len(z)-1
        graph = [[[0 for k in range(l3)]for j in range(l2)] for i in range(l1)]
        #init the graph
        for node in nodes:
            i0,j0,k0 = find(x,node[0]),find(y,node[1]),find(z,node[2])
            i1,j1,k1 = find(x,node[3]),find(y,node[4]),find(z,node[5])
            for i in range(i0,i1):
                for j in range(j0,j1):
                    for k in range(k0,k1):
                        graph[i][j][k]=1
        floodfill(graph,l1,l2,l3)

619: 此题也有一定难度,关键是图的构造过程不能将正方形想象成节点,而应该将边想象成节点。这一点我一开始也没有做出来,看了书上的分析才想到。如果将正方形看作节点,那么图的构建,空间需求难度都会大大增加。

(a): 将边看作是节点,那么每个边都代表可以从这个边出发,继续构造图形的状态,至于其他的三条边怎样没关系。所以每一个节点都对应实际的多种状态。
(b): 构造图的过程需要一点技巧性,每新加入一个正方形,都要想到它的每一条边作为节点,要和图中已有的节点相连接。假如某节点是a+,那么就要将a-和除了a+之外的三条边连接起来。这里有一点点反直觉!仔细思考一下,这是因为这个状态的转换代表可以从a-这条边开始构建图形的状态进入从相连接之后可以从其他三条边出发构建图形的状态。
(c): 注意这是一个有向图,注意到a-到其他个状态,但是不代表其他三个状态可以到a-.需要仔细思考。
(d): 最后就是利用拓扑排序寻找有向环。

graph = [[] for i in range(52)]
vertex = [0]*52
def ID(s):return ord(s[0])-ord('A')+(26 if s[1]=='+' else 0)
def MID(id):return id-26 if id>=26 else id+26
def next(id,k,j,s):
    m = (k+j)%8
    return ID(s[m:m+2]) if s[m:m+2]!='00' else -1

def proc():
    global graph,vertex
    while 1:
        T = input()
        graph,vertex = [set() for i in range(52)],[0]*52
        if T=='':break
        line = input().split(' ')
        for s in line:
            for k in range(0,len(s),2):
                if s[k:k+2]=='00':continue
                node = ID(s[k:k+2])
                for j in range(2,8,2):
                    graph[MID(node)].add(next(node,k,j,s))
                vertex[node]=1
        for each in range(52):
            if vertex[each]==0:graph[each]=set()
        print(graph)
        if(topsort()):print('unbounded')
        else:print('bounded')
def topsort():
    global graph,vertex
    def dfs(u):
        nonlocal vis
        vis[u]=-1
        for v in graph[u]:
            if vis[v]==-1:print(v);return True
            if vis[v]==0 and dfs(v):return True
        vis[u]=1
        return False
    vis = [0]*52
    for i in range(52):
        if vertex[i] and dfs(i):return True
    return False
proc()

620 此题还是一道有难度的题,最开始我想直接使用BFS+穷举来寻找,但是发现状态特别多,因为在bfs过程中每一个节点都对应多种状态(节点编号,到达路径的字典序)。它的状态比起单纯的bfs多太多了。但是可以发现,很多状态不能通向节点n,或者不是节点n的最短路径的状态,或者不是节点n上的最短字典序,所以可以进行优化。

(a): 本题引入的最重要的技巧就是反向BFS,从终点倒着来,这样就可以知道每一个节点到终点的距离d[i].这样在正向BFS的时候,我们可以知道每一个节点是不是从1->n的最短路径上的点。判断方法就是判断d[u]==d[v]+1(这里可以拓展到有权图),证明很简单:因为假设u是1-〉n上最短路的一个点,d[u]是u-〉n的最短距离,那么作为u的相邻节点,d[v]==d[u]-1就是所有邻接节点中的最小值。每次都选择最近的当然就是正确的道路了。
(b): 由于(a)的分析优化,我们可以从所有筛选的节点中找出当前字典序最小的一个,因为都是通向n的最短路,所以长度一样,所以只需要比较当前这一层的字典序就可以了。这里要用到一个小技巧:用两个数组轮换着计算每一的节点。

inf = 1<<20
d=[inf]*10000
def revbfs(graph,u):
    global d
    q = deque([u])
    d[u] = 0
    vis = [0]*10000
    vis[u]=1
    while len(q):
        t = q.popleft()
        for e in graph[t]:
            v,c = e[0],e[1]
            if vis[v]:continue
            d[v] = d[t]+1
            vis[v]=1
            q.append(v)
def bfs(graph,u):
    global d
    vis = [0]*10000
    vis[u]=1
    next = [u]
    for i in range(d[u]):
        min_color = 10**9+1
        for t in next:
            for e in graph[t]:
                v,c = e[0],e[1]
                if d[v]==d[t]-1:
                    min_color = min(c,min_color)
        print(min_color,end=' ')
        next_2 = []
        for t in next:
            for e in graph[t]:
                v,c = e[0],e[1]
                if vis[v]==0 and d[v]==d[t]-1 and c== min_color:
                    vis[v]=1
                    next_2.append(v)
        next=next_2
def proc():
    graph = [[]for i in range(10000)]
    line=input().split(' ')
    n,m = [int(i)for i in line]
    for j in range(m):
        line = input().split(' ')
        a,b,c = [int(i)for i in line]
        graph[a].append((b,c))
        graph[b].append((a,c))
    revbfs(graph,n)
    print(d[1])
    bfs(graph,1)
proc()

例题6-22:此题不是太难,但需要一定想象力。主要是注意到能否通过战场的关键是:

(a) 是否存在一条圆形障碍连通了上下边界。
(b) 找到最北的进出口的关键是:如果某个进出口被从上界开始的一条圆形障碍包围,那么就不能通过该进出口进入/出去。因此问题转化为可以找到从上界开始的连通块中与左右边界相交的点中最南边的点,这样从该点以后都是可以通行的,那么该点就是可以通行的点中最北口。
x,y,r = [],[],[]
_in,_out = 1000,1000
def connected(i,j):
    global x,y,r1
    return (x[i]-x[j])**2+(y[i]-y[j])**2<=(r[i]+r[j])**2
def solve(n):
    def dfs(u):
        global x,y,r,_in,_out
        nonlocal n,vis
        if y[u]-r[u]<=0:return True
        if x[u]-r[u]<=0:_in = min(_in,y[u]-(r[u]**2-x[u]**2)**(1/2))
        if x[u]+r[u]>=1000:_out=min(_out,y[u]-(r[u]**2 - (1000-x[u])**2)**(1/2))
        for i in range(n):
            if vis[i]==0 and connected(u,i):
                vis[i]=1
                if dfs(i):return True
    global x,y,r
    vis = [0]*n
    for i in range(n):
        if vis[i]==0 and y[i]+r[i]>=1000:
            vis[i]=1
            if dfs(i):print('impossible');return False
    print(_in,_out)
def proc():
    global x,y,r
    for i in range(int(input())):
        a,b,c = [int(k) for k in input().split(' ')]
        x.append(a);y.append(b);r.append(c);
    solve(i+1)
proc()

611 给定增序的BFS和DFS序列,求出一种可能的树的结构。

ps,
(a): 此题关键是要分析清楚DFS序列中每一个点和前一个的位置关系。对于DFS,每一个后继节点要么是前一个的第一个儿子,要么是它的父节点或祖先的某一个儿子。
(b): 对于BFS,每一个后继要么是后兄弟节点(同一层的相邻兄弟节点),要么是下一层的第一个节点。
(c): 利用以上两点,维护一个栈用来记录节点的父子关系,遍历DFS序列,如果一个节点是祖先的儿子,就弹出栈顶元素,比较更上一层的祖先。若是当前栈顶节点的儿子,就应该直接加入。
(d): 怎样确定它们的关系呢,根据a,b:利用在BFS的相对位置很容易确定大部分情况,惟有两个元素在BFS也是前后继的关系(继在DFS和BFS都是x,y),根据b,这时候有两种情况:下一层的第一个节点同时也是栈顶元素的第一个子节点,或者是后兄弟节点。这也是根据BFS和DFS不能确定一颗树的原因。
(e): 其实这里是问题的难点,但是很多题解都没有写清楚,如果不能确定到底是哪个位置,就将它放在后兄弟的位置!因为根据DFS的特性,后兄弟肯定比当前节点后访问,所以后兄弟的位置一定是可行的 ,但对于下一层的第一个子节点,它在DFS中可能已经被先访问了,这样这种情况有可能就不成立!经过这样的讨论,我们知道放在后兄弟节点的位置总是安全的,而且BFS和DFS的访问顺序也符合。
def reconstruct(bfs,dfs,bfs_index):
    stack = [dfs[0]]
    ans = [[] for i in range(len(bfs)+1)]
    for u in dfs[1:]:
        while len(stack):
            root = stack[-1]
            if (bfs_index[u] == bfs_index[root]+1 and len(stack)>1) or bfs_index[u]<bfs_index[root]:
                stack.pop()
            else:
                stack.append(u)
                ans[root].append(u)
                break
    print(ans)
def proc():
    for i in range(int(input())):
        bfs = [int(v) for v in input().split(' ')]
        dfs = [int(v) for v in input().split(' ')]
        bfs_index = [0]*(len(bfs)+1)
        for k,v in enumerate(bfs):bfs_index[v] = k
        reconstruct(bfs,dfs,bfs_index)

::

要解决这个问题:其实只要理解前一个问题的本质。然后将对DFS序列的遍历看作是一个拓展状态节点的过程,将不同的可能状态保存在一个队列里,对这些状态进行一次BFS,由于我们已经知道了目标状态:所有节点都放置好了,也知道到达目标状态一共要经历n次拓展,所以可以利用分层拓展状态的技术来进行拓展,类似于例题6-20。同时注意要保存的东西很多,比如当前节点的深度,每一个深度上是否存在节点,同时还要保存节点路径。所以对于空间的消耗是非常大的。

def reconstruct(bfs,dfs,bfs_index):
    def add_son_node(stack_2,ans_2,d_2,next_2):
        d_2[depth+1]=1
        stack_2.append((u,depth+1));ans_2[root]=ans[root]+[u]
        next_2.append((stack_2,ans_2,d_2))
    n = len(bfs)+1;_stack = [(dfs[0],0)]
    _ans = [[] for i in range(n)];_d = [-1]*n
    next = [(_stack,_ans,_d)]
    for u in dfs[1:]:
        next_2 = []
        for state in next:
            stack,ans,d = state
            while len(stack):
                root,depth = stack[-1][0],stack[-1][1]
                if bfs_index[u]<bfs_index[root]:
                    stack.pop()
                elif bfs_index[u]==bfs_index[root]+1:
                    if d[depth+1]==-1 or len(stack)<=1:#作为该层第一个儿子
                        add_son_node(stack.copy(),ans.copy(),d.copy(),next_2)
                    stack.pop()#作为兄弟
                else:#作为任意一个儿子
                    add_son_node(stack.copy(),ans.copy(),d.copy(),next_2)
                    break
        next = next_2
    for each in next:print(each[1])

proc()

1: 使用栈来判断括号表达式的正确性,so easy。只要注意一点,从栈里面pop出来的一定要对应到正确的符号。比如([)]这种就不对。

def test():
    def try_pop(ch):
        nonlocal stack,flag
        try:x = stack.pop();assert(x==ch)
        except:flag = False;
    T = int(input())
    for i in range(T):
        flag = True
        stack = []
        s = input()
        for ch in s:
            if not flag:break
            if ch=='(':stack.append('(')
            elif ch==')':try_pop('(')
            elif ch=='[':stack.append('[')
            elif ch==']':try_pop('[')
            else:print('unkonwon symbol');flag = False
        if flag:print('Yes')
        else:print('No')

614 :这道题考查欧拉道路与连通分量的运用。

(a): 首先可以想到给出的E条路径如果是一条欧拉道路,那么它就是最短的路径。否则需要探究最少要添加多少条道路才能将它变成一条欧拉道路。
(b): 无向图的欧拉道路两个条件分别是连通分量和度数,从这两个条件入手,首先找出所给的e条边有多少连通的分量,然后考虑将每一个连通分量变成欧拉道路,最后将所有欧拉道路连接起来就得到答案。
(c): 这里有一些点要注意,首先任何连通分量的奇数度数的点的数量总是偶数,因为图的总度数是偶数。其次,最少的解决办法是将图中奇数度数的点两两相连接,这样就可以成对的消灭,注意每一个连通分量都要留下两个奇数点作为端点(没有的除外),最后将n个分量通过留下的端点串起来。其次,我们要注意最后得到的图可能是一个多重图,这多少与直觉有些违背,因为欧拉道路是不能重复走的,但是在题意中的高速路可以反复走,所以需要添加重边来表达重复走的路径。这里就是原始的图和最后我们抽象出来的图示不一样的,不要认为两个点已经连了就不能再连了。

下面通过两种方式来求解,

(1): DFS法:
road = []
vis = [0]*1005
deg = [0]*1005
def dfs(i):
    global road,vis,deg
    if vis[i]==1:return 0
    vis[i]=1
    k=len(road[i])&1
    for v in road[i]:
        k+= dfs(v)
    return k
def solve(vertexs):
    res = 0
    for i in vertexs:
        if vis[i]==0:
            num = dfs(i)
            assert(num%2==0)
            if num>2:res+= (num-2)>>1
            res+=1
    return res-1
def proc():
    global road,deg,vis
    while 1:
        line = input().split(' ')
        v,e,t = [int(i) for i in line]
        if not v|e|t:break
        vertexs,road,vis = set(),[[] for i in range(1005)],[0]*1005
        for j in range(e):
            line = input().split(' ')
            a,b = [int(i) for i in line]
            vertexs.add(a);vertexs.add(b)
            road[a].append(b);road[b].append(a)
        print(t*(e+solve(vertexs)))

(2):

pa = [i for i in range(1005)]
def findset(x):return x if x==pa[x] else findset(pa[x])
def proc():
    global road,deg,vis,pa
    while 1:
        line = input().split(' ')
        v,e,t = [int(i) for i in line]
        if not v|e|t:break
        odd,c,deg, = [0]*1005,0,[0]*1005
        pa = [i for i in range(1005)]
        for j in range(e):
            line = input().split(' ')
            a,b = [int(i) for i in line]
            s1,s2=findset(a),findset(b)
            if s1!=s2:pa[s1] = s2;c+=1
            deg[a]+=1;deg[b]+=1
            vertexs.add(a);vertexs.add(b)
        c = len(vertexs)-c#连通块数量
        for i in vertexs:
            odd[findset(i)] += deg[i]&1
        num = 0
        for  i in vertexs:
            if i==pa[i] and odd[i]>2:num+=odd[i]-2 
        assert(num%2==0)
        print(t*(e+num//2+c-1))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值