题目大意:给出n个学校,m条有向边,问最少向几个学校发送软件后其余的学校都能接受到软件。
解题思路:首先求强连通分量如果只有一个的话说明原图强连通。如果大于两个的话,我们就要缩点之后统计每个块的入度或者出度,因为我们要将出度为0的与入度为0的相连,所以就统计那个多。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
#include<map>
#include<algorithm>
using namespace std;
#define LL long long
#define sca(x) scanf("%d",&x)
#define lowb(x) (x&(-x))
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define pri(x) printf("%d\n",x);
#define N 405
#define inf 0x3f3f3f3f
const LL mod=1e9+7;
struct node
{
int to,nt;
}g[N*10];
int df[N],low[N];
int cnt,bel[N],clo;
int head[N];
int tot;
stack<int>S;
void addedg(int x,int y)
{
g[tot].to=y;
g[tot].nt=head[x];
head[x]=tot++;
}
void tarjan(int now)
{
df[now]=low[now]=++clo;
S.push(now);
for(int i=head[now];i+1;i=g[i].nt)
{
int to=g[i].to;
if(!df[to])
{
tarjan(to);
low[now]=min(low[now],low[to]);
}
else if(!bel[to])
{
low[now]=min(low[now],df[to]);
}
}
if(low[now]==df[now])
{
cnt++;
int tmp;
do
{
tmp=S.top();
S.pop();
bel[tmp]=cnt;
}while(tmp!=now);
}
}
int in[N],ou[N];
void solve(int n)
{
for(int i=1;i<=n;i++)
{
for(int j=head[i];j+1;j=g[j].nt)
{
if(bel[i]!=bel[g[j].to])in[bel[g[j].to]]++,ou[bel[i]]++;
}
}
int ans=0,ans2=0;
rep(i,1,cnt)
{
if(ou[i]==0)ans++;
if(in[i]==0)ans2++;
}
printf("%d\n",ans2);
printf("%d\n",cnt>=2?max(ans,ans2):0);
}
int main()
{
int n;
sca(n);
memset(head,-1,sizeof(head));
rep(i,1,n)
{
int tmp;
while(scanf("%d",&tmp))
{
if(tmp==0)break;
addedg(i,tmp);
}
}
rep(i,1,n)if(!df[i])tarjan(i);
solve(n);
}
题目大意:无向图求割点。注意输入格式即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
#include<map>
#include<algorithm>
using namespace std;
#define LL long long
#define sca(x) scanf("%d",&x)
#define lowb(x) (x&(-x))
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define pri(x) printf("%d\n",x);
#define N 405
#define inf 0x3f3f3f3f
const LL mod=1e9+7;
vector<int>G[N];
int df[N],low[N];
int vis[N];
int clo;
void init()
{
clo=0;
memset(df,0,sizeof(df));
memset(low,0,sizeof(low));
memset(vis,0,sizeof(vis));
rep(i,1,N)G[i].clear();
}
void tarjan(int u,int fa)
{
df[u]=low[u]=++clo;
int son=0;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
if(!df[v])
{
son++;
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=df[u])
{
vis[u]=1;
}
}
else if(df[v]<df[u] && v!=fa)
{
low[u]=min(low[u],df[v]);
}
}
if(fa==-1 && son==1)vis[u]=0;
}
int main()
{
int n;
int x,y;
char ch;
while(~scanf("%d",&n))
{
init();
if(n==0)break;
while(scanf("%d",&x)&&x){
while(scanf("%d%c",&y,&ch))
{
G[x].pb(y);
G[y].pb(x);
if(ch=='\n')break;
}
}
rep(i,1,n)if(!df[i])tarjan(i,-1);
int ans=0;
rep(i,1,n)if(vis[i])ans++;
pri(ans);
}
}
题目大意:给出一个无向图有重边,有q个询问,每次查询加完一条边后还剩下多少个割边。
解题思路:对原图求边双连通分量之后重新建图,每次加入一条边就找到着两个点的公共祖先,路径上边的边都是割边。
为了防止重复计算我们将统计过的边标记。。但是我缩点之后竟然超时了,可能这个题目有毒。。。。
于是就没有缩在统计边双连通分量的时候记录每个点的深度并且统计每个节点的父亲节点。这样每一个查询就找他们的公共祖先就好。。。
注意对重边的处理方法:
1.我们可以用邻接表建图后找到桥之后将桥标记,然后再一次dfs统计连通分量。这样即使有重边我们也得到正确答案。
2.记录与前驱节点相同的值有多少个(如果是反向边就跳过否则就执行)。
3.在dfs的过程中对走过的边记录保证每条边只走一次,一般对正向边以及反向边标号2*i 2*i+1(这个时候应该注意vis数组应该为边数的二倍)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
#include<map>
#include<algorithm>
using namespace std;
#define LL long long
#define sca(x) scanf("%d",&x)
#define lowb(x) (x&(-x))
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define pri(x) printf("%d\n",x);
#define N 210005
#define inf 0x3f3f3f3f
const LL mod=1e9+7;
struct node
{
int to,nt;
}g[N*2];
int head[N];
int df[N],low[N];
int clo,cnt,bridge,tot,tot1;
bool vis[N*2],visit[N*2];
int dep[N];
int bel[N],F[N];
stack<int>S;
void init(int n)
{
rep(i,1,n)head[i]=-1;
clo=0,cnt=0,bridge=0,tot=0;
memset(visit,false,sizeof(visit));
memset(df,0,sizeof(df));
memset(low,0,sizeof(low));
memset(vis,false,sizeof(vis));
memset(F,0,sizeof(F));
memset(dep,0,sizeof(dep));
}
void addedg(int x,int y)
{
g[tot].to=y;
g[tot].nt=head[x];
head[x]=tot++;
}
void dfs(int u,int fa,int de)
{
df[u]=low[u]=++clo;
if(!dep[u])dep[u]=de,F[u]=fa;
S.push(u);
int pre=0;
for(int i=head[u];i+1;i=g[i].nt)
{
int v=g[i].to;
if(v==fa&&pre==0)
{
pre++;
continue;
}
if(!df[v])
{
dfs(v,u,de+1);
low[u]=min(low[u],low[v]);
if(low[v]>df[u])vis[v]=1,bridge++;
}
else if(!bel[v]) low[u]=min(low[u],df[v]);
}
if(df[u]==low[u])
{
cnt++;
int tmp;
do
{
tmp=S.top(),S.pop();
bel[tmp]=cnt;
}while(tmp!=u);
}
}
void cal(int x)
{
if(vis[x])
{
vis[x]=0;
bridge--;
}
}
void LCA(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
while(dep[x]!=dep[y])
{
cal(x);
x=F[x];
}
while(x!=y)
{
cal(x);
cal(y);
x=F[x];y=F[y];
}
}
int main()
{
int n,m,q;
int cas=1;
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0)break;
init(n);
rep(i,1,m)
{
int x,y;
scanf("%d%d",&x,&y);
addedg(x,y);
addedg(y,x);
}
dfs(1,-1,1);
//rep(i,1,n)cout<<i<<" "<<dep[i]<<endl;
sca(q);
printf("Case %d:\n",cas++);
while(q--)
{
int x,y;
scanf("%d%d",&x,&y);
LCA(x,y);
printf("%d\n",bridge);
}
puts("");
}
}
题目大意:给定一个图求至少需要加多少条边后能使这个图为边双连通图。
解题思路:对原图求边连通分量后缩点,这时候是一棵树。只要将度数为1的节点互相连接就行。最后就是(叶子节点数+1)/2
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
#include<map>
#include<algorithm>
using namespace std;
#define LL long long
#define sca(x) scanf("%d",&x)
#define lowb(x) (x&(-x))
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define pri(x) printf("%d\n",x);
#define N 100005
#define inf 0x3f3f3f3f
const LL mod=1e9+7;
struct node
{
int to,nt,id;
}g[N];
int head[N];
int tot;
void addedg(int x,int y,int id)
{
g[tot].to=y;
g[tot].id=id;
g[tot].nt=head[x];
head[x]=tot++;
}
int low[N],df[N];
int bel[N],vis[N];
int du[N];
int clo,cnt;
void dfs(int now,int fa)
{
df[now]=low[now]=++clo;
for(int i=head[now];i+1;i=g[i].nt)
{
int to=g[i].to;
if(!df[to])
{
dfs(to,now);
low[now]=min(low[now],low[to]);
if(low[to]>df[now])
{
vis[g[i].id]=1;
}
}
else if(df[to]<df[now]&&to!=fa)
{
low[now]=min(low[now],df[to]);
}
}
}
void dfs1(int u)
{
bel[u]=cnt;
for(int i=head[u];i+1;i=g[i].nt)
{
if(!vis[g[i].id]&&!bel[g[i].to])
{
dfs1(g[i].to);
}
}
}
void solve(int n)
{
dfs(1,-1);
cnt=1;
rep(i,1,n)if(!bel[i])dfs1(i),cnt++;
rep(i,1,n)
{
for(int j=head[i];j+1;j=g[j].nt)
{
if(bel[i]!=bel[g[j].to])du[bel[i]]++,du[bel[g[j].to]]++;
}
}
int ans=0;
rep(i,1,cnt-1)if(du[i]==2)ans++;
cout<<(ans+1)/2<<endl;
}
int main()
{
int n,m;
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
rep(i,1,m)
{
int x,y;
scanf("%d%d",&x,&y);
addedg(x,y,i);
addedg(y,x,i);
}
solve(n);
}
题目大意:n个点m条边(有重边),问加一条边后最少还剩多少条割边。
解题思路:求边双联通分量后计算树的直径,原来的割边树减去树的直径就是答案。
莫名的wa了20多次。其实第二次就对了。。。。。。。后来发现vis数组开太小了 一个全局变量没有重新赋值。。心累
貌似这个会爆栈。。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
#include<map>
#include<algorithm>
using namespace std;
#define LL long long
#pragma comment(linker, "/STACK:102400000,102400000")
#define sca(x) scanf("%d",&x)
#define lowb(x) (x&(-x))
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define pri(x) printf("%d\n",x);
#define N 200005
#define inf 0x3f3f3f3f
const LL mod=1e9+7;
struct node
{
int to,nt,id,flag;
}g[N*10];
int head[N];
vector<int>G[N];
int tot;
void addedg(int x,int y,int id,bool tt)
{
g[tot].to=y;
g[tot].id=id;
g[tot].flag=tt;
g[tot].nt=head[x];
head[x]=tot++;
}
int low[N],df[N];
int bel[N],vis[N*10];
int clo,cnt;
int lef;
void dfs(int now,int fa)
{
df[now]=low[now]=++clo;
for(int i=head[now];i+1;i=g[i].nt)
{
int to=g[i].to;
if(g[i].flag==false)continue;
g[i].flag=g[i^1].flag=false;
if(!df[to])
{
dfs(to,now);
low[now]=min(low[now],low[to]);
if(low[to]>df[now])
{
vis[g[i].id]=1;
}
}
else low[now]=min(low[now],df[to]);
}
}
void dfs1(int u)
{
bel[u]=cnt;
for(int i=head[u];i+1;i=g[i].nt)
{
if(!vis[g[i].id]&&!bel[g[i].to])
{
dfs1(g[i].to);
}
}
}
int F[N],vis1[N];
int dia,beg;
void Find(int now,int tmp)
{
for(int i=0;i<G[now].size();i++)
{
int to=G[now][i];
if(vis1[to])continue;
vis1[to]=1;
F[to]=tmp+1;
if(F[to]>dia)
{
dia=F[to];
beg=to;
}
Find(to,F[to]);
}
}
void solve(int n)
{
dfs(1,-1);
cnt=1;
rep(i,1,n)if(!bel[i])dfs1(i),cnt++;
//rep(i,1,n)cout<<i<<" "<<bel[i]<<endl;
rep(i,1,n)
{
for(int j=head[i];j+1;j=g[j].nt)
{
if(bel[i]!=bel[g[j].to])
{
G[bel[i]].pb(bel[g[j].to]);
G[bel[g[j].to]].pb(bel[i]);
}
}
}
cnt--;
vis1[1]=1;
Find(1,0);
memset(vis1,0,sizeof(vis1));
dia=0;
vis1[beg]=1;
Find(beg,0);
//cout<<cnt-1<<" "<<dia<<endl;
printf("%d\n",cnt-1-dia);
}
void init(int n)
{
tot=0,lef=0;
clo=0,cnt=0;
dia=0;
memset(head,-1,sizeof(head));
rep(i,1,N)G[i].clear();
memset(df,0,sizeof(df));
memset(low,0,sizeof(low));
memset(bel,0,sizeof(bel));
memset(vis,0,sizeof(vis));
memset(F,0,sizeof(F));
memset(vis1,0,sizeof(vis1));
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0)break;
init(n);
rep(i,1,m)
{
int x,y;
scanf("%d%d",&x,&y);
addedg(x,y,i,true);
addedg(y,x,i,true);
}
solve(n);
}
}
还有一种解法是在求连通分量的时候树形dp求树的直径。
题目大意:给一个有向图问最少加几条边使原图依然是个简单图无重边和自环。
解题思路:考虑到加一些边之后原图最少剩下两个连通分量。设为x个点和y个点 x+y=n
x个点最多可以右x*x-1条边 同理y个点。同时x还能向y连x*y条边
解不等式之后发现x越小总边数越大,m是一定的,所以求出最大值减去m就行。x是一个 现有的集合,是缩点之后入度为0或者出度为0的强连通分量。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
#include<map>
#include<algorithm>
using namespace std;
#define LL long long
#define sca(x) scanf("%d",&x)
#define lowb(x) (x&(-x))
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define pri(x) printf("%d\n",x);
#define N 200005
#define inf 0x3f3f3f3f
const LL mod=1e9+7;
struct node
{
int to,nt;
}g[N];
int tot;
int head[N];
void addedg(int x,int y)
{
g[tot].to=y;
g[tot].nt=head[x];
head[x]=tot++;
}
int df[N],low[N];
int bel[N],sz[N];
int in[N];
stack<int>s;
int clo,cnt,ans;
void dfs(int u)
{
df[u]=low[u]=++clo;
s.push(u);
for(int i=head[u];i+1;i=g[i].nt){
int to=g[i].to;
if(!df[to])
{
dfs(to);
low[u]=min(low[u],low[to]);
}else if(!bel[to]) low[u]=min(low[u],df[to]);
}
if(low[u]==df[u])
{
cnt++;
int tmp;
do
{
tmp=s.top(),s.pop();
bel[tmp]=cnt;
sz[cnt]++;
}while(tmp!=u);
}
}
int ou[N];
void init(int n)
{
tot=0;
clo=0,cnt=0;
ans=inf;
memset(in,0,sizeof(in));
memset(ou,0,sizeof(ou));
memset(head,-1,sizeof(head));
memset(df,0,sizeof(df));
memset(low,0,sizeof(low));
memset(bel,0,sizeof(bel));
memset(sz,0,sizeof(sz));
}
int main()
{
int t;
sca(t);
int cas=1;
while(t--)
{
LL n,m;
scanf("%lld%lld",&n,&m);
init(n);
rep(i,1,m)
{
int x,y;
scanf("%d%d",&x,&y);
addedg(x,y);
}
rep(i,1,n)if(!df[i])dfs(i);
rep(i,1,n)
{
for(int j=head[i];j+1;j=g[j].nt)
{
int v=g[j].to;
if(bel[i]!=bel[v])
{
ou[bel[i]]++;
in[bel[v]]++;
}
}
}
rep(i,1,cnt)if(in[i]==0||ou[i]==0)ans=min(ans,sz[i]);
printf("Case %d: ",cas++);
LL res=ans*ans-n*ans+n*n-n-m;
if(cnt==1)printf("-1\n");
else printf("%lld\n",res);
}
}
H还没写。。
模板题。。求权值最小的割边。。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<stack>
#include<map>
#include<algorithm>
using namespace std;
#define LL long long
#define sca(x) scanf("%d",&x)
#define lowb(x) (x&(-x))
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define pri(x) printf("%d\n",x);
#define N 210005
#define inf 0x3f3f3f3f
const LL mod=1e9+7;
struct node
{
int to,nt,w;
}g[N*10];
int head[N];
int df[N],low[N];
int clo,bridge,tot,ans;
void init(int n)
{
rep(i,1,n)head[i]=-1;
ans=inf;
clo=0,bridge=0,tot=0;
memset(df,0,sizeof(df));
memset(low,0,sizeof(low));
}
void addedg(int x,int y,int w)
{
g[tot].to=y;
g[tot].w=w;
g[tot].nt=head[x];
head[x]=tot++;
}
void dfs(int u,int fa)
{
df[u]=low[u]=++clo;
int pre=0;
for(int i=head[u];i+1;i=g[i].nt)
{
int v=g[i].to;
if(v==fa&&pre==0)
{
pre++;
continue;
}
if(!df[v])
{
dfs(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>df[u])ans=min(ans,g[i].w),bridge++;
}
else low[u]=min(low[u],df[v]);
}
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0)break;
init(n);
rep(i,1,m)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
addedg(x,y,w);
addedg(y,x,w);
}
int num=0;
rep(i,1,n)if(!df[i])dfs(i,-1),num++;
if(num>1)
{
pri(0);
continue;
}
if(bridge==0)puts("-1");
else if(ans==0)puts("1");
else pri(ans);
}
}