这个专题实属虐心…各种debug搞到自闭
不过进步就是在自闭中产生的…
后面被反向边逼迫使用了链式前向星建图(双修?)
1.POJ 1236 Network of Schools
任务一:求出入度为0的连通分量数量
任务二:求加多少条边能让整个图强连通
对于第二问,因为要使整个图强连通,贪心的拿每一个出度为0的点对入度为0的点加边,这样下来最小的加边数就是max(入度,出度)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
const int maxn=1e3+5,maxm=1e3+5;
int n,m,chu[maxn];
int dfn[maxn],low[maxn],vis[maxn],z[maxn],color[maxn],cnt[maxn],ru[maxn],t,k,tot;
vector<int>e[maxm];
void tarjan(int u,int fa){
dfn[u]=low[u]=++tot;
z[++k]=u;//入栈
vis[u]=1;
for(int i=0;i<e[u].size();i++){
if(!dfn[e[u][i]]){
tarjan(e[u][i],fa);
low[u]=min(low[u],low[e[u][i]]);
}
else if(vis[e[u][i]]){
low[u]=min(low[u],dfn[e[u][i]]);
}
}
if(low[u]==dfn[u]){
t++;//连通分量的标号
do{
color[z[k]]=t; //属于这个连通分量
cnt[t]++; //记录这个环中有多少个点
vis[z[k]]=0;
k--; //出栈
}while(u!=z[k+1]);
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
while(1){
int v;
scanf("%d",&v);
if(v==0)break;
e[i].push_back(v);
}
}
for(int i=1;i<=n;i++){
if(!dfn[i])tarjan(i,i);
}
int ans=0,maxx=0;
for(int i=1;i<=n;i++){
for(int j=0;j<e[i].size();j++){
if(color[i]!=color[e[i][j]]){
ru[color[e[i][j]]]++;
chu[color[i]]++;
}
}
}
int x=0,y=0;
for(int i=1;i<=t;i++){
if(ru[i]==0)x++;
if(chu[i]==0)y++;
}
if(t==1)cout<<x<<endl<<"0";
else cout<<x<<endl<<max(x,y);
return 0;
}
2.UVA 315 Network
求割点
uva读入恶心人
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
const int maxn=2e4+5,maxm=2e4+5;
int n,m;
bool cut[maxn];
int dfn[maxn],low[maxn],vis[maxn],z[maxn],color[maxn],cnt[maxn],du[maxn],t,k,tot;
vector<int>e[maxm];
void tarjan(int u,int root){//root代表此树的根
int child=0;
dfn[u]=low[u]=++tot;
for(int i=0;i<e[u].size();i++){
int v=e[u][i];
if(!dfn[v]){
tarjan(v,root);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=root)cut[u]=true;
if(u==root)child++;
}
low[u]=min(low[u],dfn[v]);
}
if(u==root&&child>=2){
cut[root]=true;
}
}
void init(int n){
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(cut,false,sizeof(cut));
for(int i=0;i<=n;i++){
e[i].clear();
}
}
int main(){
int u,v;
char c;
while(cin>>n){
if(n==0)break;
init(n);
while (scanf("%d", &u), u) {
while (scanf("%c", &c), c != '\n') {
scanf("%d", &v);
e[u].push_back(v);
e[v].push_back(u);
}
}
for(int i=1;i<=n;i++){
if(!dfn[i])tarjan(i,i);
}
int ans=0;
for(int i=1;i<=n;i++){
if(cut[i])ans++;
}
cout<<ans<<endl;
}
return 0;
}
3.UVA 796 Critical Links
求桥
uva读入再次恶心人
对于求桥有不同的写法,有时候会出现玄学错误…
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
const int maxn=2e5+5,maxm=5e5+5;
int n,m;
int dfn[maxn],low[maxn],t,k,tot;
vector<int>e[maxm];
struct edge{
int u,v;
}qiao[maxm];
bool cmp(edge a,edge b){
if(a.u==b.u)return a.v<b.v;
return a.u<b.u;
}
void tarjan(int u,int root){//root代表此树的根
dfn[u]=low[u]=++tot;
for(int i=0;i<e[u].size();i++){
int v=e[u][i];
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]){
t++;
qiao[t].u=min(u,v);
qiao[t].v=max(u,v);
}
}
else if(v!=root)low[u]=min(low[u],dfn[v]);
}
return;
}
void init(int n){
t=0;
tot=0;
k=0;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
for(int i=0;i<=n;i++){
e[i].clear();
}
}
int main(){
while(cin>>n){
init(n);
int u,v,num;
char c1,c2;
for(int i=1;i<=n;i++){
cin>>u>>c1>>num>>c2;
u++;
while(num--){
cin>>v;
v++;
e[u].push_back(v);
}
}
for(int i=1;i<=n;i++){
if(!dfn[i])tarjan(i,0);
}
sort(qiao+1,qiao+1+t,cmp);
printf("%d critical links\n",t);
for(int i=1;i<=t;i++){
printf("%d - %d\n",qiao[i].u-1,qiao[i].v-1);
}
cout<<endl;
}
return 0;
}
4.POJ 3694 Network
求加边的过程中,桥的数量
正解应该是每次加边后求出lca,加入这条边之后这个环上面的边就都不是桥了
借鉴了大神的题解的方法,在求桥的过程中求出每个点的深度,然后用check函数将两个点逐渐跳到lca,并将路径上的桥消去
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
const int maxn=2e5+5,maxm=4e5+5,inf=0x3f3f3f3f;
int dfn[maxn],low[maxn],fa[maxn],dep[maxn],qiao[maxn],n,m,t,tot;
vector<int>e[maxm*2];
void tarjan(int u,int root){
dfn[u]=low[u]=++tot;
for(int i=0;i<e[u].size();i++){
int v=e[u][i];
if(v==root)continue;
if(!dfn[v]){
fa[v]=u;
dep[v]=dep[u]+1;
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]){
t++;
qiao[v]=1;
}
}
else low[u]=min(low[u],dfn[v]);
}
return;
}
void check(int u,int v){
while(dep[u]>dep[v]){
if(qiao[u])t--,qiao[u]=0;
u=fa[u];
}
while(dep[v]>dep[u]){
if(qiao[v])t--,qiao[v]=0;
v=fa[v];
}
while(u!=v){
if(qiao[u])qiao[u]=0,t--;
if(qiao[v])qiao[v]=0,t--;
u=fa[u];
v=fa[v];
}
}
void qibao(int n){
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(fa,0,sizeof(fa));
memset(dep,0,sizeof(dep));
memset(qiao,0,sizeof(qiao));
t=tot=0;
for(int i=0;i<=n;i++){
e[i].clear();
}
}
int main(){
int n,m,u,v,tt=1;
while(scanf("%d%d",&n,&m)!=EOF){
if(n==0&&m==0)break;
qibao(max(n,m));
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
cin>>m;
for(int i=1;i<=m;i++){
if(!dfn[i])tarjan(i,i);
}
printf("Case %d:\n",tt++);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
check(u,v);
cout<<t<<endl;
}
cout<<endl;
}
return 0;
}
5.POJ 3177 Redundant Paths
问加多少条边可以让图成为双连通图
先缩点
入度为1的点数量为ans
答案为(ans+1)/2
这是怎么来的呢?将入度为1的点两两相连就可以了,所以点数为奇数的话要+1
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
const int maxn=1e5+5,maxm=1e6+5,inf=0x3f3f3f3f;
int low[maxn],dfn[maxn],tot,k,t,z[maxn],head[maxm],len=1;
int color[maxm],ru[maxn];
struct edge{
int v,nex;
}e[maxm];
void add_edge(int u,int v){
e[++len].v=v;
e[len].nex=head[u];
head[u]=len;
}
void tarjan(int u,int root){
dfn[u]=low[u]=++tot;
z[++k]=u;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
}
else if(i!=(root^1))low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
t++;//连通分量的标号
do{
color[z[k]]=t; //属于这个连通分量
k--; //出栈
}while(u!=z[k+1]);
}
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
add_edge(u,v);
add_edge(v,u);
}
tarjan(1,0);
int ans=0;
for(int u=1;u<=n;u++){
for(int j=head[u];j;j=e[j].nex){
int v=e[j].v;
if(color[u]!=color[v])ru[color[v]]++;
}
}
for(int i=1;i<=t;i++){
if(ru[i]==1)ans++;
}
cout<<(ans+1)/2;
}
6.HDU 4612 Warm up
缩点建树+树的直径
缩点后得到一棵树
因为树上每条边都是桥,所以桥的数量=联通分量数-1
问加一条边能让桥的数量最少为多少
树的直径的上的桥最多
所以用桥的数量-树的直径即为答案
wa到哭,让我晚上整个人都清醒了
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
const int maxn=2e5+5,maxm=2e6+5,inf=0x3f3f3f3f;
int low[maxn],dfn[maxn],tot,top,t,z[maxn],head[maxn],len,len2,d;
int color[maxn],head2[maxn],vis[maxn],ans,n,m;
struct edge{
int v,nex;
}e[maxm],e2[maxm];
inline int read()
{
int x=0,k=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*k;
}
void init(){
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(dfn,0,sizeof(dfn));
tot=0,t=0,len=0,len2=0,top=0,d=0;
}
inline void add_edge(int u,int v){
e[len].v=v;
e[len].nex=head[u];
head[u]=len++;
}
inline void add_edge2(int u,int v){
e2[len2].v=v;
e2[len2].nex=head2[u];
head2[u]=len2++;
}
void build(){
len2=0;
memset(head2,-1,sizeof(head2));
for(int u=1;u<=n;u++){
for(int j=head[u];j!=-1;j=e[j].nex){
int v=e[j].v;
if(color[u]!=color[v]){
add_edge2(color[u],color[v]);
}
}
}
}
void tarjan(int u,int root){
low[u]=dfn[u]=++tot;
vis[u]=1;
z[top++]=u;
int flag=1;
for(int i=head[u];i!=-1;i=e[i].nex){
int v=e[i].v;
if(v==root&&flag){
flag=0;
continue;
}
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
}
else if(vis[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
t++;
int v;
while(1){
v=z[top-1];
top--;
color[v]=t;
vis[v]=0;
if(v==u)break;
}
}
}
int dfs(int u,int fa){
int d1=0,d2=0;
for(int i=head2[u];i!=-1;i=e2[i].nex){
int v=e2[i].v;
if(v==fa)continue;
int d=dfs(v,u)+1;
if(d>d1)d2=d1,d1=d;
else if(d>d2)d2=d;
}
ans=max(ans,d1+d2);
return d1;
}
int main(){
int u,v;
while(scanf("%d%d",&n,&m)&&(n+m)){
init();
for(int i=1;i<=m;i++){
u=read(),v=read();
if(u==v)continue;
add_edge(u,v);
add_edge(v,u);
}
tarjan(1,-1);
build();
ans=0;
dfs(1,-1);
printf("%d\n",t-ans-1);
}
return 0;
}
7.HDU 4635 Strongly connected
给定一个有向图,求最大可以增加多少条边使得这个仍然不是强连通
首先要知道一点:完全图的边数为n*(n-1)
然后减去图中本来就有的m条边
再减去入度或出度为0的点能跟其他点连的边or被其他点连的边,即为最终答案
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
int n,m,chu[maxn],qq,a[maxn],b[maxn];
int dfn[maxn],low[maxn],vis[maxn],z[maxn],color[maxn],cnt[maxn],ru[maxn],t,k,tot;
vector<int>e[maxm];
void tarjan(int u){
dfn[u]=low[u]=++tot;
z[++k]=u;//入栈
vis[u]=1;
for(int i=0;i<e[u].size();i++){
if(!dfn[e[u][i]]){
tarjan(e[u][i]);
low[u]=min(low[u],low[e[u][i]]);
}
else if(vis[e[u][i]]){
low[u]=min(low[u],dfn[e[u][i]]);
}
}
if(low[u]==dfn[u]){
t++;//连通分量的标号
do{
color[z[k]]=t; //属于这个连通分量
cnt[t]++; //记录这个环中有多少个点
vis[z[k]]=0;
k--; //出栈
}while(u!=z[k+1]);
}
}
void init(int n){
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(z,0,sizeof(z));
memset(vis,0,sizeof(vis));
memset(color,0,sizeof(color));
memset(cnt,0,sizeof(cnt));
memset(ru,0,sizeof(ru));
memset(chu,0,sizeof(chu));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int i=0;i<=n;i++){
e[i].clear();
}
t=0;
k=0;
tot=0;
}
signed main(){
int tt;
cin>>tt;
while(tt--){
qq++;
cin>>n>>m;
init(max(n,m));
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
a[i]=u;
b[i]=v;
e[u].push_back(v);
}
for(int i=1;i<=n;i++){
if(!dfn[i])tarjan(i);
}
for(int i=1;i<=n;i++){
for(int j=0;j<e[i].size();j++){
int u=color[i];
int v=color[e[i][j]];
if(u!=v){
chu[u]++;
ru[v]++;
}
}
}
int minv=n;
for(int i=1;i<=t;i++){
if(ru[i]==0||chu[i]==0)minv=min(minv,cnt[i]);
}
int ans=n*(n-1)-m-minv*(n-minv);
if(t==1)printf("Case %lld: -1\n",qq);
else printf("Case %lld: %lld\n",qq,ans);
}
return 0;
}
8.HDU 4685 Prince and Princess
夹带私货的题,二分图完美匹配+缩点,之后补(虽然会用最大流求最大匹配数)
9.HDU 4738 Caocao’s Bridges
又是一题坑的我爆炸的题
用之前的找桥的tarjan疯狂无敌爆炸wa
以后求桥都用这个 链式前向星+反向边来求
其实这个题很简单,求桥的过程中更新桥的最小权值就可以了,如果图本来就不连通就输出-1,特判最小权值为0,因为最少还是需要一个人去炸桥,所以为1
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
const int maxn=2e4+5,maxm=2e6+5,inf=0x3f3f3f3f;
int n,m;
int dfn[maxn],low[maxn],k,tot,ans,tcnt,head[maxn],cnt;
int qiao[maxm];
struct edge{
int v,w,nex;
}e[maxm];
inline void add(int u,int v,int w){
e[++cnt].nex=head[u];
e[cnt].v=v;
e[cnt].w=w;
head[u]=cnt;
}
void tarjan(int u,int root){//root代表此树的根
dfn[u]=low[u]=++tot;
for(int i=head[u];i!=-1;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]){
ans=min(ans,e[i].w);
}
}
else if(i!=(root^1))low[u]=min(low[u],dfn[v]);
}
return;
}
void init(){
tot=0;
k=0;
cnt=1;
tcnt=0;
ans=inf;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(head,-1,sizeof(head));
}
int main(){
while(cin>>n>>m){
if(n==0&&m==0)break;
init();
int u,v,w;
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i,0);
tcnt++;
}
}
if(tcnt>1)puts("0");
else if(ans==inf)puts("-1");
else if(ans==0)puts("1");
else printf("%d\n",ans);
}
return 0;
}
刷题记录不是题解,写的十分简略,见谅qwq
自闭自闭自闭自闭自闭自闭…