3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ABC 157 D - Friend SuggestionsをPythonで解く!

Last updated at Posted at 2020-03-04

最近のD問題は骨があって解けないことが多く、悲しみを感じる今日この頃です。
終わった後もどう解こうかもやもやしていたので、頑張って解いてみました。

今回のキーワードは、「無向グラフ」の「連結成分」
ポイントは__「DFS」「set」__!

問題へのリンク ABC 157 D - Friend Suggestions

コンテスト中の回答

無向グラフであることに気づいて、すぐさまDFSで解くことに。
各頂点からDFSを使って連結成分から友達でもブロックでもない、点をカウントしていきます。
2重ループであきらかに間に合わないだろうなと思っていたら、案の定TLEでした。
コンテスト中はこれでタイムアップ。

from collections import deque

N,M,K = map(int,input().split())
friend = [list(map(int,input().split())) for _ in range(M)]
block = [list(map(int,input().split())) for _ in range(K)]

g = [[False]*(N+1) for _ in range(N+1)]
for i in friend:
    g[i[0]][i[1]] = True
    g[i[1]][i[0]] = True


b = [[False]*(N+1) for _ in range(N+1)]
for i in block:
    b[i[0]][i[1]] = True
    b[i[1]][i[0]] = True
    
stack=deque()
ans = []

for i in range(1,N+1):
    cnt = 0
    visited = [0] * (N+1)
    visited[i] = 1 
    stack.append(i)
    while stack:
        n = stack.pop()
        for j in range(1,N+1):
            if g[n][j] and visited[j]==0:
                stack.append(j)
                visited[j] = 1
                if g[i][j] == False and b[i][j]==False:
                    cnt+=1
    ans.append(cnt)
print(*ans)

コンテスト後の回答

inを使う場合は、ListよりSetのほうが早いと聞いて、使ってみることにしました。
いろいろ試行錯誤しながらTLEと戦った結果、以下でACをいただきました。

linkに連結成分をためていき、連結成分を求める。
連結成分の探索が終わったら、
連結成分の各点に関して引くべき友達関係とブロック関係の数を引くことで求めています。

from collections import deque

N,M,K = map(int,input().split())
friend = [list(map(int,input().split())) for _ in range(M)]
block = [list(map(int,input().split())) for _ in range(K)]

f = [set() for _ in range(N+1)]
b = [set() for _ in range(N+1)]

for i,j in friend:
    f[i].add(j)
    f[j].add(i)
for i,j in block:
    b[i].add(j)
    b[j].add(i)

stack=deque()
ans = [0]*(N+1)
visited = [0]*(N+1)

for i in range(1,N+1):
    if visited[i]:
        continue
    link = {i}
    visited[i] = 1 
    stack.append(i)
    while stack:
        n = stack.pop()
        for j in f[n]:
            if visited[j]==0:
                stack.append(j)
                visited[j] = 1
                link.add(j)
    for i in link:
        ans[i] = len(link) - len(link & f[i]) - len(link & b[i]) - 1
print(*ans[1:])

 

工夫ポイント

探索済みを調べるのが明らかに無駄なので、探索済みは探索しないように追加しました。

if visited[i]:
    continue

 
ある点に対してすべての頂点が友達関係であるかを確認していたところを、
その点に対して友達関係にあるものだけから探索するように変更しました。

for j in range(1,N+1):
# ↓↓↓
for j in f[n]:

 
連結成分のサイズから、友達関係またはブロック関係の数を引くのに一工夫しました。

ans[i] = len(link) - len(link & f[i]) - len(link & b[i]) - 1

 
つまり、どういうことかというと…
積集合を使うことで、連結成分の中から友達関係・ブロック関係の数を求めています。

f = [set for _ in range(10)]
b = [set for _ in range(10)]

# 連結成分
link = {1,2,4,9,10}
# 1と4・10が友達関係
f[1] = {4,10}
# 1は5・6・9をブロックしている
b[1] = {5,6,9}

print(link&f[1], link&b[1])
print(len(link&f[1]), len(link&b[1]))
{10, 4} {9}
2 1
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?