Update: with your new example, using networkx
from numpy.lib.stride_tricks import sliding_window_view as swv
import networkx as nx
import pandas as pd
data = {'A': ['A1', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A1', 'A7', 'A8'],
        'B': ['B1', 'B2', 'B2', 'B3', 'B4', 'B4', 'B4', 'B4', 'B4', 'B4'],
        'C': ['C1', 'C2', 'C3', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9']}
dft = pd.DataFrame(data)
cols = ['A', 'B', 'C']
edges = pd.DataFrame(swv(dft[cols].values.ravel(), 2),
                     columns=['source', 'target'])
G = nx.from_pandas_edgelist(edges, create_using=nx.DiGraph)
uniq = nx.weakly_connected.number_weakly_connected_components(G)
Output:
>>> uniq
1
>>> edges
   source target
0      A1     B1
1      B1     C1
2      C1     A1
3      A1     B2
4      B2     C2
5      C2     A2
6      A2     B2
7      B2     C3
8      C3     A3
9      A3     B3
10     B3     C3
11     C3     A4
12     A4     B4
13     B4     C4
14     C4     A5
15     A5     B4
16     B4     C5
17     C5     A6
18     A6     B4
19     B4     C6
20     C6     A1
21     A1     B4
22     B4     C7
23     C7     A7
24     A7     B4
25     B4     C8
26     C8     A8
27     A8     B4
28     B4     C9
Old answer
You can use boolean masks:
m = pd.concat([df[col].duplicated() for col in ['A', 'B', 'C']], axis=1)
uniq = sum(~m.any(axis=1))  # number of unique rows
out = df[~m.any(axis=1)]  # you can also extract unique rows
Output:
>>> uniq
1
>>> m
       A      B      C
0  False  False  False  # never duplicated
1   True  False  False  # duplicated on A
2  False   True  False  # duplicated on B
3  False  False   True  # duplicated on C
>>> out
   row num   A   B   C
0        1  A1  B1  C1