1. 程式人生 > 其它 >劍指 Offer II 016. 不含重複字元的最長子字串

劍指 Offer II 016. 不含重複字元的最長子字串

題意:

求簡單圖(無向、無重邊、無自環)中簡單環(不重複經過點/邊)的數量

\(1\le n\le 19\)

思路:

能不能暴力嗯dp?\(dp(i,j)\) 表示起點為 \(i\),終點為 \(j\) 的方案數。這樣會把非簡單環算進去。

考慮狀壓,\(f(S,i)\) 表示路徑上的所有點組成點集 \(S\),起點是 \(S\) 中編號最小的點(記為 \(start\))(思想類似 lowbit),終點是 \(i\) 的方案數。

\(i\) 擴充套件到點 \(j\)

  • 如果 \(j\)\(start\) 還小,忽略之

  • 對於 \(j\in S\),如果 \(j\) 就是 \(start\)

    ,說明找到了一個環,更新答案;否則就走到了路徑上走過的點,不是簡單環了,忽略之

  • 對於 \(j\notin S\),更新 \(f(S,i)\to f(S+'j',i)\)

更新的順序:從小到大列舉所有可能的 \(S\) 就好了。這樣迴圈到 \(S\) 之前,\(S\) 一定被所有能擴充套件到 \(S\) 的狀態擴充套件過

注意每個簡單環被算了兩次(順時針和逆時針);另外每個 \(x\to y\to x\) 的只有兩條邊的環也是非法的,這種有 \(m\)

所以輸出 \((ans-m)/2\)

const signed N = 19; //開到21會MLE
int n, m; bool g[N][N];
ll f[1<<N][N], ans;

int get(int S) { //非空集S的起點
    for(int i = 0; ; i++)
        if(S>>i&1) return i;
}
bool in(int S, int x) { //u是否在集合S中
    return (S>>x&1);
}

signed main() {
    iofast;
    cin >> n >> m;
    for(int i = 1; i <= m; i++) {
        int u, v; cin >> u >> v;
        u--, v--; //二進位制,-1比較方便
        g[u][v] = g[v][u] = 1;
    }

    for(int i = 0; i < n; i++) f[1<<i][i] = 1; //初始化

    for(int S = 1; S < (1<<n); S++) {
        int start = get(S);
        for(int i = 0; i < n; i++) {
            if(!f[S][i]) continue; //不存在的狀態
            if(g[i][start]) ans += f[S][i];
            for(int j = start+1; j < n; j++)
                if(g[i][j] && !in(S,j))
                    f[S|(1<<j)][j] += f[S][i];
        }
    }

    cout << (ans-m)/2;
}