洛谷 P4306 [JSOI2010]連通數
算法標籤: 圖論
,統計
題目
題目描述
度量一個有向圖聯通情況的一個指標是連通數,指圖中可達頂點對個的個數。
如圖
頂點 1 可達 1, 2, 3, 4, 5
頂點 2 可達 2, 3, 4, 5
頂點 3 可達 3, 4, 5
頂點 4, 5 都只能到達自身。
所以這張圖的連通數爲 14。
給定一張圖,請你求出它的連通數
輸入格式
輸入數據第一行是圖頂點的數量,一個正整數N。 接下來N行,每行N個字符。第i行第j列的1表示頂點i到j有邊,0則表示無邊。
輸出格式
輸出一行一個整數,表示該圖的連通數。
輸入輸出樣例
輸入 #1
3
010
001
100
輸出 #1
9
說明/提示
對於100%的數據,N不超過2000。
題解:
某機房巨佬JZYshuraK_彧 推薦的題,貌似說有很多做法,不過作爲一個蒟蒻,我搬出了許久沒有用到的 毒瘤 SPFA,思路是將SPFA中的鬆弛更新最短路改爲連通計數,最終統計\(ans\)即可。
大致實現如下(鏈式前向星存圖):
void spfa(int s)
{
memset(vis, 0, sizeof vis);
queue <int> q;
q.push(s);
vis[s] = 1;
while(!q.empty())
{
int x, y;
x = q.front();
q.pop();
for (int i = head[x]; i; i = nex[i])
{
y = to[i];
if (!vis[y])
{
q.push(y);
vis[y] = 1;
ans ++ ;
}
}
}
}
之後按照操作統計答案即可,不過本題有毒瘤坑點。
本題坑點:
- 所讀入矩陣之中沒有空格,需要按照字符串讀入在處理(被瘋狂卡)
- 在輸出的時候要記錄\(ans + n\),原因在於這道題當中,自身與自身被算作連通,而在跑SPFA時候是沒有記錄自己的。
由於一個01串,假設爲\(011111000001\) ,在這道題的題目下可以很簡單的處理爲\(0101\)這樣一個串,在進行兩種操作取最小值。
仔細分析這兩種操作的時候,我們可以得到以下結論:
我們可以將所有中操作找出兩種最優的:
- 將0放在一邊,1放在一邊,最終一次轉換
- 每一個都單獨轉換
將字符串第0位設爲一個1,這樣我們只需要統計由某一位爲0而前一位爲1的個數,這樣的答案就是操作的總次數(可以推得無論如何進行操作總次數都一致)。而且我們可以發現無論如何進行操作,最後一次都是轉換,所以我們可以得出
\(ans = (cnt - 1) * min(x, y) + y;\)
由此此題得解,注意本題數據需要開long long
。
AC代碼
#include <bits/stdc++.h>
using namespace std;
const int N = 2020;
const int M = N * N;
int n, ans, vis[N];
int tot, to[M], nex[M], head[N];
void add(int x, int y)
{
to[++tot] = y;
nex[tot] = head[x];
head[x] = tot;
}
void spfa(int s)
{
memset(vis, 0, sizeof vis);
queue <int> q;
q.push(s);
vis[s] = 1;
while(!q.empty())
{
int x, y;
x = q.front();
q.pop();
for (int i = head[x]; i; i = nex[i])
{
y = to[i];
if (!vis[y])
{
q.push(y);
vis[y] = 1;
ans ++ ;
}
}
}
}
int main()
{
scanf("%d", &n);
char ch[2020];
for (int i = 1; i <= n; i ++ )
{
scanf("%s", ch + 1);
for (int j = 1; j <= n; j ++ )
{
if (ch[j] == '1')
add(i, j);
}
}
for (int i = 1; i <= n; i ++ )
spfa(i);
printf("%d", ans + n);
return 0;
}