FJWC2019 子圖 (三元環計數、四元環計數)


給定 n 個點和 m 條邊的一張圖和一個值 k ,求圖中邊數為 k 的聯通子圖個數 mod 1e9+7。

\(n \le 10^5, m \le 2 \times 10^5, 1 \le k \le 4\)

觀察到 k 的值賊小,考慮分類討論

下面代碼中du[]代表點的度數。(度 找不到比較好的英文,而這個拼音比較巨,所以du是我的代碼習慣中里出現拼音的少數幾中情況之一)

觀察圖片

k = 1,輸出 m。 k = 2, 枚舉點 2,組合數一下即可。

對於 k = 3 我們分為三種情況(按照圖片順序從左到右)

圖(1) 我們枚舉邊 2--3 ,則答案為(du[2] - 1) * (du[3] - 1)。

圖(2) 我們枚舉點 2 ,組合數一下即可。

圖(3) 則轉化為三元環計數問題。

需要注意 圖(1) 中 可能出現 1 和 4 重合的情況,恰好是一個三元環。每個三元環會被統計三次。所以需要減去 3 * 三元環數量。

所以最后我們需要減去 2 * 三元環數量。

對於 k = 4,我們分為五種情況(按照圖片順序從左到右)

先考慮圖(3)是一個菊花比較簡單,枚舉點 2 直接組合數一下即可。

然后我們考慮圖(2), 我們考慮枚舉 邊 2--3 ,則答案為 (du[2] - 2) * (du[3] - 1) + (du[2] - 1) * (du[3] - 2)。

注意到可能存在 1 號點 和 (4號點 或 5號點) 重合的情況,那么就變成了 圖(4) 。每一種圖 (4) 都會被統計兩次,需要減去兩次 圖(4) 的方案總數。

然后我們考慮圖(1)。 我們考慮枚舉點 3, 再依次枚舉它的出邊。我們開個變量 tmp 維護 已經掃過的出邊的另一端點的出邊數量和,即 sum(du[i] - 1) (i 是掃過的出邊連接的點) 每次我們將本次掃的 (du[i] - 1) 與 tmp 乘一下累加到答案然后把 (du[i] - 1) 加到 tmp 里。

這里 我們會有三種重合情況:(假設點 2 是之前枚舉過的點, 點 4 是當前正在枚舉的點)

  1. 點 5 和 點 1 重合。 那么圖變成了四元環,即圖(5)。每個四元環會在這里被統計四次,接下來需要 -= 4 * 四元環方案數。
  2. 點 5 和 點 2 重合,點 1 和點 4 不重合。 那么圖變成了圖(4)。每個 圖(4) 會在這里被統計兩次, 接下來需要 -= 2 * 四元環方案數。
  3. 點 5 和 點 2 重合,點 1 和點 4 重合。 那么圖變成了一個三元環。每個三元環會在這里被統計三次,接下來需要 -= 3 * 三元環方案數。

然后考慮圖(4)。圖 4 是有一個三元環和一條邊組成。我們考慮枚舉點 2 , ans += 點 2 所在三元環數量 * (du[2] - 2) 。

考慮圖(5)。圖(5)是一個四元環計數。

現在我們把問題轉化成了給定圖求每個點所在三元環數量,以及四元環總數。三元環數量可以用給每個點三元環數量 / 3 來求出。

首先考慮求每個點所在三元環的數量。

這是常數比較大的 \(O(m \sqrt m)\) 的算法,另外還有 \(O(m \sqrt n)\) 的轉化為有向圖的常數較小的做法,沒看太懂這里不解釋

我們考慮把點分為兩種,第一種是度數 \(du[x] \le \sqrt m\) 的,第二種是度數 \(du[x] > \sqrt m\) 的 (一下簡稱第一種點、第二種點)。

然后我們三元環分為兩類,第一類是包含第一種點的三元環, 第二類是不包含第一種點的三元環(環上所有點都是第二種點)。

先考慮枚舉所有第一種點 x, 我們考慮枚舉所有無序出邊對,然后我們就可以得到三個頂點 (x, y, z) 。我們可以維護一個 set ,然后直接判斷這三個點是否構成三元環。 如果構成三元環我們考慮統計答案。 首先 對於所有第一類點,我們會枚舉所有三元環,直接在枚舉這個點時暴力統計貢獻即可。 對於第二類點,假設 y 和 z 都是第二類點, 那么 (x, y, z) 這個三元環只會在此時被統計,我們將 ans[y]++, ans[z]++。 如果 y 和 z 中只有一個第二類點(假設y),那么三元環 (x, y, z) 會被枚舉兩次,其中 x 和 z 是第一類點。而 y 只能被統計一次,我們可以考慮當 x 的標號 小於 z 的標號時候將 ans[y]++ 以避免重復統計。這一部分的時間復雜度為 \(O(m \sqrt m)\),因為每條邊最多只會作為第一條出邊出現兩次,而每個點 x 的出度是 \(O(\sqrt m)\) 級別的,所以第二條出邊最多為 \(O(\sqrt m)\) 個,所以復雜度為 \(O(m \sqrt m)\)

然后我們考慮枚舉第二類三元環。由於第二類點的個數 \(\le \sqrt m\) 個,我們考慮暴力 \(O({\sqrt m}^3) = O(m\sqrt m)\) 枚舉三個點判斷是否是三元環即可。

然后我們考慮四元環計數。

考慮枚舉枚舉點 1(設為 x), 並欽定它是標號最大的點(欽定2 3 4 號點的標號都比 1 號點小),然后我們枚舉 4 號點(設為y),再枚舉 與 4 號點相連的 3 號點(設為z, z != x)。我們考慮維護一個 cnt 數組,cnt[x] 表示 和 1 號點的距離為 2 的路徑數量。 我們每次枚舉到 z 的時候,前面已經有 cnt[z] 條路徑與 x 相連了, 我們讓 ans += cnt[z], 然后 cnt[z]++ 即可。

至於時間復雜度的證明,我們可以將點按照度數從小到大排序后重新標號跑一遍算法,時間復雜度為 \(O(m \sqrt m)\)。我並不會證。。。代碼里偷懶忘了排序了,復雜度可能會被卡掉

大毒瘤代碼

#include <cmath>
#include <cstdio>
#include <vector>
#include <unordered_set>
using namespace std;

const int xkj = 1000000007;

int n, m, k;
vector<int> out[100010];
unordered_set<int> hsh[100010];
int x[200010], y[200010], du[100010], swh[100010], fuck[100010], bucket[100010], cnt;

int Cx2(int x) { return x * (long long)(x - 1) % xkj * 500000004 % xkj; }
int Cx3(int x) { return x * (long long)(x - 1) % xkj * (x - 2) % xkj * 166666668 % xkj; }
int Cx4(int x) { return x * (long long)(x - 1) % xkj * (x - 2) % xkj * (x - 3) % xkj * 41666667 % xkj; }

void prepare_swh() //獲取每個點三元環數量
{
	for (int i = 1; i <= n; i++) du[i] = 0, swh[i] = 0;
	for (int i = 1; i <= m; i++) du[x[i]]++, du[y[i]]++;
	int sqm = sqrt(m + 0.233); cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		if (du[i] <= sqm) //暴力統計
		{
			int sz = out[i].size();
			for (int j = 0; j < sz; j++)
			{
				for (int k = j + 1; k < sz; k++)
				{
					if (hsh[out[i][j]].count(out[i][k]))
					{
						swh[i]++;
						if (du[out[i][j]] > sqm && du[out[i][k]] > sqm) swh[out[i][j]]++, swh[out[i][k]]++;
						else
						{
							int x = out[i][j], y = out[i][k];
							if (du[x] > sqm || du[y] > sqm)
							{
								if (du[x] <= sqm) swap(x, y);
								if (i < y) swh[x]++;
							}
						}
					}
				}
			}
		}
		else fuck[++cnt] = i;
	}
	for (int i = 1; i <= cnt; i++)
	{
		for (int j = i + 1; j <= cnt; j++)
		{
			if (hsh[fuck[i]].count(fuck[j]))
			{
				for (int k = j + 1; k <= cnt; k++)
				{
					if (hsh[fuck[j]].count(fuck[k]) && hsh[fuck[i]].count(fuck[k]))
					{
						swh[fuck[i]]++, swh[fuck[j]]++, swh[fuck[k]]++;
					}
				}
			}
		}
	}
}

int qcnt()
{
	int ans = 0;
	for (int i = 1; i <= n; i++) ans = (ans + swh[i]) % xkj;
	return ans * 333333336LL % xkj;
}

int qcnt2() //獲取 sigma 每個點在多少個三元環內*(這個點的度數-2)
{
	int ans = 0;
	for (int i = 1; i <= n; i++) ans = (ans + swh[i] * (long long)(du[i] - 2) % xkj) % xkj;
	return ans;
}

int qcnt3() //獲取正方形
{
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		for (int j : out[i]) if (j < i)
		{
			for (int k : out[j]) if (k < i)
			{
				ans = (ans + bucket[k]) % xkj;
				bucket[k]++;
			}
		}
		for (int j : out[i]) if (j < i)
		{
			for (int k : out[j]) if (k < i)
			{
				bucket[k]--;
			}
		}
	}
	return ans;
}

int main()
{
	freopen("subgraph.in", "r", stdin), freopen("subgraph.out", "w", stdout);
	scanf("%d%d%d", &n, &m, &k);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d", &x[i], &y[i]);
		out[x[i]].push_back(y[i]), out[y[i]].push_back(x[i]);
		hsh[x[i]].insert(y[i]), hsh[y[i]].insert(x[i]);
	}
	prepare_swh();
	if (k == 1) { printf("%d\n", m); }
	if (k == 2)
	{
		int ans = 0;
		for (int i = 1; i <= n; i++) ans = (ans + Cx2(out[i].size())) % xkj; // []---[]---[]
		printf("%d\n", ans);
	}
	if (k == 3)
	{
		int ans = 0;
		for (int i = 1; i <= n; i++) ans = (ans + Cx3(out[i].size())) % xkj; // 菊花型
		for (int i = 1; i <= m; i++) ans = (ans + (out[x[i]].size() - 1) * (long long)(out[y[i]].size() - 1) % xkj) % xkj; //鏈型+三元環*3
		ans = (ans - 2 * qcnt()) % xkj;//三元環數量
		ans = (ans + xkj) % xkj;
		printf("%d\n", ans);
	}
	if (k == 4)
	{
		int ans = 0;
		for (int i = 1; i <= n; i++) ans = (ans + Cx4(out[i].size())) % xkj; // 菊花型
		for (int i = 1; i <= m; i++) ans = (ans + Cx2(out[x[i]].size() - 1) * (long long)(out[y[i]].size() - 1) % xkj) % xkj,
		ans = (ans + Cx2(out[y[i]].size() - 1) * (long long)(out[x[i]].size() - 1) % xkj) % xkj; //箭頭形+陷阱型*2
		ans = (ans + xkj - 3 * (long long)qcnt2() % xkj) % xkj; //減去陷阱型
		for (int i = 1; i <= n; i++) //鏈型
		{
			int tmp = 0;
			for (int j : out[i])
			{
				int tmp1 = out[j].size() - 1;
				ans = (ans + tmp * (long long)tmp1 % xkj) % xkj;
				tmp = (tmp + tmp1) % xkj;
			}
		}
		ans = (ans + xkj - 3 * (long long)qcnt() % xkj) % xkj;
		ans = (ans + xkj - 3 * (long long)qcnt3() % xkj) % xkj;
		printf("%d\n", ans);
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM