12 October

次小生成樹

http://poj.org/problem?id=1679html

不可貴出,次小生成樹能夠由最小生成樹更換一條邊獲得。算法

首先構造原圖的最小生成樹,而後枚舉每一條不在最小生成樹中的邊 (u, v, w),嘗試將這條邊加入生成樹,由於直接加入邊會產生環,因此咱們須要在加邊以前刪去最小生成樹上 u 到 v 的路徑上權值最大的邊。在枚舉每一條邊時咱們都會獲得一棵生成樹,這些生成樹中邊權和最小的即爲要求的次小生成樹。函數

須要在構造最小生成樹時將完整的樹結構構造出來,而且使用樹上倍增算法查詢兩點間邊權值最大的值。優化

強連通份量

在一個有向圖中,若是某兩點間都有互相到達的路徑,那麼稱中兩個點強連通,若是任意兩點都強連通,那麼稱這個圖爲強連通圖;一個有向圖的極大強連通子圖稱爲強連通份量。ui

https://oi.men.ci/tarjan-scc-notes/spa

http://www.javashuo.com/article/p-tajucyol-gy.htmlcode

一個強連通份量中的點必定處於搜索樹中同一棵子樹中。htm

Tarjan 算法:blog

  • low[] 表示這個點以及其子孫節點連的全部點中dfn最小的值.
  • s[] 表示當前全部可能能構成是強連通份量的點.
  • col[] 對強連通份量進行染色.
  • v[to[k]]==false 說明不管如何這個點也不能與u構成強連通份量,由於它不能到達u.
  • low[x]==dfn[x] 說明u點及u點之下的全部子節點沒有邊是指向u的祖先的了,即u點與它的子孫節點構成了一個最大的強連通圖即強連通份量.
  • if (!dfn[i]) tarjan(i); Tarjan一遍不能搜完全部的點,由於存在孤立點. 因此咱們要對一趟跑下來尚未被訪問到的點繼續跑Tarjan.

均攤時間複雜度 \(O(n)\).ci

int dfn[N], low[N], t, s[N], st;
int col[N], ct;
bool v[N];

void tarjan(int x) {
    dfn[x]=low[x]=++t, s[++st]=x, v[x]=true;
    for (int k=head[x]; k; k=nex[k]) {
        if (dfn[to[k]]) {if (v[to[k]]) low[x]=min(low[x], dfn[to[k]]); }
        else tarjan(to[k]), low[x]=min(low[x], low[to[k]]);
    }
    if (low[x]==dfn[x]) {
        col[x]=++ct, v[x]=false;
        while (s[st]!=x) col[s[st]]=ct, v[s[st--]]=false;
        --st;
    }
}

for (int i=1; i<=n; ++i) if (!dfn[i]) tarjan(i);

[USACO06JAN] The Cow Prom:

for (rint i=1; i<=n; ++i) ++cnt[col[i]];
for (rint i=1; i<=ct; ++i) if (cnt[i]>1) ++ans;
printf("%d\n", ans);

縮點

縮點: 對於 貢獻具備傳遞性 的題,由於強連通份量中的每兩個點都是強連通的,能夠將一個強連通份量當作一個 超級點,而點權按題意來定.

POJ2186 Popular Cows: 告訴你有n頭牛,m個崇拜關係,而且崇拜具備傳遞性,若是a崇拜b,b崇拜c,則a崇拜c,求最後有幾頭牛被全部牛崇拜.

顯然一個強聯通份量內的全部點都是知足條件的,咱們能夠對整張圖進行縮點,而後就簡單了.

剩下的全部點都不是強連通的,如今整張圖就是一個DAG(有向無環圖).

那麼就變成一道水題了,由於這是一個有向無環圖,不存在全部點的出度都不爲零的狀況.

因此必然有1個及以上的點出度爲零,若是有兩個點出度爲零,那麼這兩個點確定是不相連的,即這兩圈牛不是互相崇拜的,因而此時答案爲零,若是有1個點出度爲0,那麼這個點就是被全體牛崇拜的.

這個點多是一個強聯通份量縮成的 超級點,因此應該輸出整個強聯通份量中點的個數.

stxy-ferryman

/* 以上同 Tarjan 求強連通份量. */

int deg[N], cnt[N];
int tot=0, ans=0;

for (int i=1; i<=n; ++i) {
    for (int k=head[i]; k; k=nex[k]) if (col[to[k]]!=col[i]) ++deg[col[i]];
    ++cnt[col[i]];
}
for (int i=1; i<=ct; ++i) if (!deg[i]) ++tot, ans=cnt[i];
if (!tot || tot>1) printf("0\n"); else printf("%d\n", ans);

割點、橋

無向圖.

割點

特判:根節點若是有兩個及以上的兒子,那麼他也是割點.

int dfn[N], low[N], t, root;
bool cut[N];

void tarjan(int x) {
    int flag=0;
    dfn[x]=low[x]=++t;
    for (int k=head[x]; k; k=nex[k]) {
        if (dfn[to[k]]) low[x]=min(low[x], dfn[to[k]]);
        else {
            tarjan(to[k]), low[x]=min(low[x], low[to[k]]);
            if (low[y]>=dfn[x]) {
                ++flag;
                if (x!=root || flag>1) cut[x]=true;
            }
        }
    }
}

for (int i=1; i<=n; ++i) if (!dfn[i]) tarjan(root=i);
for (int i=1; i<=n; ++i) if (cut[i]) printf("%d ", i);

鄰接表存圖編號從2開始. 即開頭 head[0]=1;.

int dfn[N], low[N], t;
bool bdg[N<<1];

void tarjan(int x, int last) {
    dfn[x]=low[x]=++t;
    for (int k=head[x]; k; k=nex[k]) {
        if (dfn[to[k]]) if (i!=(last^1)) low[x]=min(low[x], dfn[to[k]]);
        else {
            tarjan(to[k], k), low[x]=min(low[x], low[to[k]]);
            if (low[y]>=dfn[x]) bdg[k]=bdg[k^1]=true;
        }
    }
}

for (int i=1; i<=n; ++i) if (!dfn[i]) tarjan(i, 0);
for (int i=2; i<head[0]; i+=2) if (bdg[i]) printf("%d %d\n", to[i^1], to[i]);

Euler 函數

\[ n={\prod_{i=1}^{k}} \ {p_i}^{a_i} \]

\[ \varphi(n)=n\cdot \prod_{i=1}^{k} \left(1 - \frac{1}{p_i}\right) \]

int phi() {
    int m = floor(sqrt(n + 0.5)), ans = n;
    for (int i = 2; i <= m; i++) {
        if (n % i == 0) {
            ans = ans / i * (i - 1);
            while (n % i == 0) n /= i;
        }
    }

    if (n != 1) ans = ans / n * (n - 1); // 總體爲素數
    return ans;
}

https://oi.men.ci/euler-sieve/

模意義下的除法

要求模數爲素數。

Fermat 小定理:
\[a ^ {p - 1} \equiv 1 \pmod p \]

\[a \cdot a ^ {p - 2} \equiv 1 \pmod p \]

inline int pow(long long x, int y) {
    long long res=1;
    for (; y; x=x*x%mod, y>>=1) if (y&1) res=res*x%mod;
    return res;
}

inline int inv(int& x) {return pow(num, mod-2); }

拓展 Euclid 算法:
在對數時間內求出方程 \(ax + by = \gcd(a, b)\) 的一組解。當 b 爲素數時,\(\gcd(a, b)=1\),則

\[ax \equiv 1 \pmod b \]

式中 \(x\) 即爲所求。

void exgcd(int& a, int& b, int &g, int &x, int &y) {
    if (!b) g=a, x=1, y=0;
    else exgcd(b, a%b, g, y, x), y-=x*(a/b);
}

inline int inv(int& t) {
    register int g, x, y; exgcd(t, mod, g, x, y);
    retuurn ((x%mod)+mod)%mod;
}

全錯位排列遞推公式

\[f(n) = (n-1)\cdot \left[f(n-1)+f(n-2) \right]\]

常數優化

書寫優化:

Before After
x==0 !x
x!=-1 ~x
x!=y x^y
x*10 (x<<3) + (x<<1)
x*2+1 x<<1|1
x%2 x&1
(x+1)%2 x^1
x%2==0 ~(x&1)

函數參數儘可能取地址。手動內聯 inline

大循環分開來作。register。(手動 cache)

strlen() 函數提早求好值,避免重複調用。

表達式合併。(手動並行)

重載運算符:

struct mat {
    static const int ml=10;
    int m[ml][ml];
    mat(int x=0) {
        memset(m, 0, sizeof(m));
        for (int i=0; i<ml; i++) m[i][i]=x;
    }
    int* operator [] (int& p) {return m[p]; }
};
相關文章
相關標籤/搜索