A - Network of Schools POJ - 1236
思路:容易看出,处于同一个强连通分量里的点可以看成一个点,所以我们求出所有强连通分量然后计算出每个强连通分量的出度和入度,入度位0的个数就是最少要发的邮件的个数,第二问的答案是入度为0的个数和出度为0的个数的最大值。。。不会证明
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
const int maxn = 2e5 + 50;
typedef long long LL;
struct Edge
{
int to, next;
} edge[maxn];
int k, head[maxn];
void add(int a, int b){
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
};
int dfn[maxn], low[maxn], in_stack[maxn];
int s[maxn], top = 0;
int tim, scc_cnt, scc_id[maxn];
int in[maxn], out[maxn];
void tarjan(int u){
dfn[u] = low[u] = ++tim;
s[++top] = u;
in_stack[u] = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(!dfn[to]){
tarjan(to);
low[u] = min(low[u], low[to]);
} else if(in_stack[to]){
low[u] = min(low[u], dfn[to]);
}
}
if(dfn[u] == low[u]){
scc_cnt++;
int x;
do{
x = s[top--];
scc_id[x] = scc_cnt;
in_stack[x] = 0;
} while(x != u);
}
}
int main(int argc, char const *argv[])
{
int n;
scanf("%d", &n);
memset(head, -1, sizeof(head));
for(int i = 1; i <= n; i++){
int x;
while(1){
scanf("%d", &x);
if(x == 0){
break;
}
add(i, x);
}
}
for(int i = 1; i <= n; i++){
if(scc_id[i] == 0){
tarjan(i);
}
}
if(scc_cnt == 1){
printf("1\n0\n\n");
return 0;
}
for(int u = 1; u <= n; u++){
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(scc_id[to] != scc_id[u]){
in[scc_id[to]]++;
out[scc_id[u]]++;
}
}
}
int cnt1 = 0, cnt2 = 0;
for(int i = 1; i <= scc_cnt; i++){
if(in[i] == 0){
cnt1++;
}
if(out[i] == 0){
cnt2++;
}
}
printf("%d\n%d\n", cnt1, max(cnt1, cnt2));
return 0;
}
B - Network UVA - 315
思路:求割点模板题
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
const int maxn = 2e5 + 50;
typedef long long LL;
struct Edge
{
int to, next;
} edge[maxn * 2];
int k, head[maxn];
void add(int a, int b){
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
};
int dfn[maxn], low[maxn];
int tim;
int ans = 0;
int iscut[maxn];
void tarjan(int fa, int u){
dfn[u] = low[u] = ++tim;
int son = 0;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(!dfn[to]){
tarjan(fa, to);
if(low[to] >= dfn[u] && u != fa){
iscut[u] = 1;
}
low[u] = min(low[u], low[to]);
son++;
} else{
low[u] = min(low[u], dfn[to]);
}
}
if(u == fa && son > 1){
iscut[fa] = 1;
}
}
void init(){
ans = 0, tim = 0;
k = 0;
memset(head, -1, sizeof(head));
memset(dfn, 0, sizeof(dfn));
memset(iscut, 0, sizeof(iscut));
memset(low, 0, sizeof(low));
}
int main(int argc, char const *argv[])
{
int n;
int u, v;
while (~scanf("%d", &n) && n)
{
init();
while (scanf("%d", &u) && u)
{
while (getchar() != '\n')
{
scanf("%d", &v);
add(u, v);
add(v, u);
}
}
for(int i = 1; i <= n; i++){
if(!dfn[i]){
tarjan(i, i);
}
}
for(int i = 1; i <= n; i++){
if(iscut[i]){
ans++;
}
}
printf("%d\n", ans);
}
return 0;
}
D - Network POJ - 3694
思路:边双联通分量缩点+lca,因为处于同一边双联通的边不可能是桥,缩完点之后剩下的边就是桥了,而且形成一棵树,我们发现,每次建新边的时候,可以消去的桥就是新边的两个端点到他们最近公共祖先经过的边,但注意我们不能重复消去同一条边,所以我们要给每一条边大哥标记,消去过后就不再消去了
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
const int maxn = 2e5 + 40;
typedef long long LL;
int n, m;
struct Edge
{
int to, next;
} edge[maxn * 2], tree[maxn * 2]; // edge存原图, tree存之后缩点生成的树
int is_bridge[maxn * 2]; // 标记是桥的边,注意要开大,不然会越界,因为这里wa了一下午
int k, head[maxn]; // 前向星建图
void add(int a, int b){
is_bridge[k] = 0;
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
}
int tk, thead[maxn];
void addt(int a, int b){
tree[tk].to = b;
tree[tk].next = thead[a];
thead[a] = tk++;
}
int dfn[maxn], low[maxn]; // 求边双联通分量的基本操作
int tim, scc_cnt, scc_id[maxn];
int in[maxn];
int ans = 0;
int s[maxn], in_stack[maxn], top;
void tarjan(int u, int pre){
dfn[u] = low[u] = ++tim;
s[++top] = u;
in_stack[u] = 1;
int flag = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(to == pre && flag){
flag = 0;
continue;
}
if(!dfn[to]){
tarjan(to, u);
low[u] = min(low[u], low[to]);
if(low[to] > dfn[u]){ // 标记桥
is_bridge[i] = is_bridge[i ^ 1] = 1;
ans++;
}
} else if(in_stack[to]){
low[u] = min(low[u], dfn[to]);
}
}
if(low[u] == dfn[u]){
scc_cnt++;
int x;
do{
x = s[top--];
scc_id[x] = scc_cnt;
in[x] = 0;
} while(x != u);
}
}
int depth[maxn], fa[maxn];
int vis[maxn];
void dfs(int u, int pre, int dep){ // 算出树上每个节点一个深度
depth[u] = dep;
fa[u] = pre;
for(int i = thead[u]; i != -1; i = tree[i].next){
int to = tree[i].to;
if(to == pre){
continue;
}
vis[to] = 1; // 直接标记边不好标记,所以我们标记桥的一个端点
dfs(to, u, dep + 1);
}
}
void LCA(int x, int y){ // 最朴素的LCA
if(depth[x] < depth[y]){
swap(x, y);
}
while(depth[x] > depth[y]){
if(vis[x]){
ans--;
vis[x] = 0;
}
x = fa[x];
}
while(x != y){
if(vis[x]){
vis[x] = 0;
ans--;
}
if(vis[y]){
vis[y] = 0;
ans--;
}
x = fa[x];
y = fa[y];
}
}
void init(){ // 初始化
ans = 0, tim = top = 0;
k = 0, tk = 0;;
scc_cnt = 0;
for(int i = 0; i <= n; i++){
head[i] = thead[i] = -1;
dfn[i] = low[i] = depth[i] = fa[i] = 0;
vis[i] = 0, in[i] = scc_id[i] = 0;
}
}
int main(int argc, char const *argv[])
{
int ca = 1;
while(scanf("%d%d", &n, &m)){
init();
if(!n && !m){
break;
}
for(int i = 1; i <= m; i++){
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
tarjan(1, -1);
for(int u = 1; u <= n; u++){
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(is_bridge[i]){
addt(scc_id[u], scc_id[to]);
}
}
}
dfs(1, -1, 0);
int q;
scanf("%d", &q);
printf("Case %d:\n", ca++);
while(q--){
int x, y;
scanf("%d%d", &x, &y);
if(scc_id[x] != scc_id[y]){
LCA(scc_id[x], scc_id[y]);
}
printf("%d\n", ans);
}
printf("\n");
}
return 0;
}
E - Redundant Paths POJ - 3177
题意:你有一张连通图(题意好像没保证,但数据好像都是连通图),问你最少加几条边能让这张图上任意两点至少有两条不同路径可达,路径可以点重复,边不能重复。
思路:看了好几篇博客,都是只给了公式**(叶子节点数 + 1) / 2 **, 自己想了想好像是只要保证每个点的度数之少为2就好了(不确定对不对)
#include<iostream>
#include<cstdio>
#include <string.h>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<vector>
#include<cmath>
#include <math.h>
#include<algorithm>
using namespace std;
const int maxn = 2e5 + 50;
int n, m;
struct Edge
{
int to, next;
} edge[maxn * 2], tree[maxn * 2];
int k, head[maxn];
int is_bridge[maxn * 2];
void add(int a, int b){
is_bridge[k] = 0;
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
}
int dfn[maxn], low[maxn], tim;
int in_stack[maxn], scc_id[maxn], scc_cnt;
int s[maxn], top;
void tarjan(int u, int pre){
dfn[u] = low[u] = ++tim;
s[++top] = u;
in_stack[u] = 1;
int flag = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(flag && to == pre){
flag = 0;
continue;
}
if(!dfn[to]){
tarjan(to, u);
low[u] = min(low[u], low[to]);
if(low[to] > dfn[u]){
is_bridge[i] = is_bridge[i ^ 1] = 1;
}
} else if(in_stack[to]){
low[u] = min(low[u], dfn[to]);
}
}
if(low[u] == dfn[u]){
int x;
scc_cnt++;
do{
x = s[top--];
scc_id[x] = scc_cnt;
in_stack[x] = 0;
} while(x != u);
}
}
int in[maxn];
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
head[i] = -1;
}
for(int i = 0; i < m; i++){
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
for(int i = 1; i <= n; ++i){
if(!dfn[i]){
tarjan(i, -1);
}
}
for(int u = 1; u <= n; u++){
for(int i = head[u]; i != -1; i = edge[i].next){
if(is_bridge[i]){
int to = edge[i].to;
in[scc_id[u]]++, in[scc_id[to]]++;
is_bridge[i] = is_bridge[i ^ 1] = 0;
}
}
}
int ans = 0;
for(int i = 1; i <= n; i++){
if(in[i] == 1){
ans++;
}
}
printf("%d\n", (ans + 1) / 2);
return 0;
}
F - Warm up HDU - 4612
题意:给你一个连通图,让你加一条边,求能加完边后图上桥的最小数量
思路:tarjan缩点,因为原图是连通图,所以最后一定得到一棵树,这颗树的边的数量就是原图敲的个数,然后我们再求出树的直径,减一下就是答案了
#include<iostream>
#include<cstdio>
#include <string.h>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<vector>
#include<cmath>
#include <math.h>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn = 1e6 + 50;
int n, m;
int is_bridge[maxn * 2];
struct Edge
{
int to, next;
} edge[maxn * 2], tree[maxn * 2];
int k, head[maxn];
void add(int a, int b){
is_bridge[k] = 0;
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
}
int tk, thead[maxn];
void addt(int a, int b){
tree[tk].to = b;
tree[tk].next = thead[a];
thead[a] = tk++;
}
int dfn[maxn], low[maxn], tim;
int s[maxn], in_satck[maxn], top;
int scc_id[maxn], scc_cnt;
int ans = 0;
void tarjan(int u, int pre){
dfn[u] = low[u] = ++tim;
s[++top] = u, in_satck[u] = 1;
int flag = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(flag && to == pre){
flag = 0;
continue;
}
if(!dfn[to]){
tarjan(to, u);
low[u] = min(low[u], low[to]);
if(low[to] > dfn[u]){
is_bridge[i] = is_bridge[i ^ 1] = 1;
ans++;
}
} else if(in_satck[to]){
low[u] = min(low[u], dfn[to]);
}
}
if(low[u] == dfn[u]){
scc_cnt++;
int x;
do{
x = s[top--];
scc_id[x] = scc_cnt;
in_satck[x] = 0;
} while(x != u);
}
}
queue<int> que;
int dis[maxn], node, madis;
void bfs1(int u){
madis = 0;
for(int i = 1; i <= n; i++){
dis[i] = 0;
}
while(que.size()){
que.pop();
}
dis[u] = 1;
que.push(u);
madis = 1, node = u;
while(que.size()){
u = que.front();
que.pop();
for(int i = thead[u]; i != -1; i = tree[i].next){
int to = tree[i].to;
if(!dis[to]){
dis[to] = dis[u] + 1;
que.push(to);
if(madis < dis[to]){
madis = dis[to];
node = to;
}
}
}
}
}
void bfs2(){
for(int i = 1; i <= n; i++){
dis[i] = 0;
}
while(que.size()){
que.pop();
}
que.push(node);
dis[node] = 1;
madis = 1;
while(que.size()){
int u = que.front();
que.pop();
for(int i = thead[u]; i != -1; i = tree[i].next){
int to = tree[i].to;
if(!dis[to]){
dis[to] = dis[u] + 1;
if(dis[to] > madis){
madis = dis[to];
}
que.push(to);
}
}
}
}
void init(){
for(int i = 1; i <= n; i++){
low[i] = dfn[i] = in_satck[i] = scc_id[i] = 0;
head[i] = thead[i] = -1;
}
tim = top = k = tk = ans = scc_cnt = 0;
}
int main() {
while(~scanf("%d%d", &n, &m)){
if(!n && !m){
break;
}
init();
for(int i = 0; i < m; i++){
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
tarjan(1, -1);
for(int u = 1; u <= n; u++){
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(scc_id[u] != scc_id[to]){
addt(scc_id[u], scc_id[to]);
}
}
}
bfs1(1);
bfs2();
printf("%d\n", ans - madis + 1);
}
return 0;
}
G - Strongly connected HDU - 4635
题意:给你一个简单图,让你求你最多能加多少边,使这个图仍是简单图并且非强连通
思路:看巨巨博客吧https://blog.csdn.net/su20145104009/article/details/70854526
#include<iostream>
#include<cstdio>
#include <string.h>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<vector>
#include<cmath>
#include <math.h>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn = 2e5 + 50;
int m;
LL n;
struct Edge
{
int to, next;
} edge[maxn * 2], tree[maxn * 2];
int k, head[maxn];
void add(int a, int b){
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
}
int tk, thead[maxn];
int dfn[maxn], low[maxn], tim;
int s[maxn], in_stack[maxn], top;
int scc_id[maxn], scc_cnt;
LL num[maxn], sum;
void tarjan(int u, int pre){
dfn[u] = low[u] = ++tim;
in_stack[u] = 1;
s[++top] = u;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(!dfn[to]){
tarjan(to, u);
low[u] = min(low[u], low[to]);
} else if(in_stack[to]){
low[u] = min(low[u], dfn[to]);
}
}
if(low[u] == dfn[u]){
scc_cnt++;
int x;
do{
x = s[top--];
num[scc_cnt]++;
scc_id[x] = scc_cnt;
in_stack[x] = 0;
} while(x != u);
}
}
int in[maxn], out[maxn];
void init(){
for(int i = 1; i <= n; i++){
in[i] = out[i] = dfn[i] = low[i] = scc_id[i] = num[i] = 0;
head[i] = -1;
}
scc_cnt = top = k = 0;
}
int main() {
int t;
scanf("%d", &t);
int ca = 1;
while(t--){
printf("Case %d: ", ca++);
scanf("%I64d%d", &n, &m);
init();
for(int i = 0; i < m; i++){
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
for(int i = 1; i <= n; i++){
if(!dfn[i]){
tarjan(i, -1);
}
}
if(scc_cnt == 1){
printf("-1\n");
continue;
}
for(int u = 1; u <= n; u++){
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(scc_id[u] != scc_id[to]){
out[scc_id[u]]++, in[scc_id[to]]++;
}
}
}
LL ans = 0;
for(int i = 1; i <= n; i++){
if(in[scc_id[i]] == 0 || out[scc_id[i]] == 0){
ans = max(ans, n * n - n - num[scc_id[i]] * (n - num[scc_id[i]]) - m);
}
}
printf("%I64d\n", ans);
}
return 0;
}