• 算法竞赛入门经典 写题笔记(第五章 图论算法与模型1)


    因为篇幅问题,该章分了4节来发。
    本节内容为——

    • 5.1基础题目选讲(简单的BFS,最短路,欧拉回路,拓扑排序)
    • 5.2 深度优先遍历(DFS,无向图的割点与桥,无向图的双联通分量,有向图的强联通分量)
      知识点的话,可以看 这个(虽然不全,但是只要不退役,我会持续更新)

    例题1 大火弥漫的迷宫

    就是一个有限制的bfs,我们要先通过一个预处理来判断在每个时间该格子是否能走。然后再通过一个bfs来求最短路。
    原先写的一直都在T,原因是加的标记数组done是在每次判断该点从队列里pop了之后才标记成1的,它能走到的节点判断之后并没有标记,这导致如果一个节点不合法,每次到它都要再判断一遍。
    改进方法是,在遍历到一个格子之后,不管它合法与否,都要标记为1.

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<ctime>
    #define MAXN 1010
    using namespace std;
    int n,m,T;
    int done[MAXN][MAXN],tim[MAXN][MAXN],now[MAXN][MAXN];
    int move_x[4]={0,0,1,-1},move_y[4]={1,-1,0,0};
    double c2;
    char a[MAXN][MAXN],s[MAXN];
    struct Node{int x,y;};
    queue<Node>fff;
    inline void init()
    {
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			done[i][j]=0;
    	while(!fff.empty())
    	{
    		Node u=fff.front();fff.pop();
    		done[u.x][u.y]=1;
    		for(int i=0;i<=3;i++)
    		{
    			int xx=u.x+move_x[i];
    			int yy=u.y+move_y[i];
    			if(xx<1||xx>n||yy<1||yy>m||done[xx][yy]) continue;
    			if(a[xx][yy]!='#') done[xx][yy]=1,fff.push((Node){xx,yy}),tim[xx][yy]=tim[u.x][u.y]+1;
    		}
    	}
    }
    inline int search(int x,int y)
    {
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			done[i][j]=0,now[i][j]=0;
    	queue<Node>q;
    	q.push((Node){x,y});
    	done[x][y]=1;
    	while(!q.empty())
    	{
    		Node u=q.front();q.pop();done[u.x][u.y]=1;
    		for(int i=0;i<=3;i++)
    		{
    			int xx=u.x+move_x[i];
    			int yy=u.y+move_y[i];
    			if(xx<1||xx>n||yy<1||yy>m) return now[u.x][u.y]+1;
    			if(done[xx][yy]) continue;
    			now[xx][yy]=now[u.x][u.y]+1;
    			if(a[xx][yy]!='#'&&now[xx][yy]<tim[xx][yy]) 
    				done[xx][yy]=1,q.push((Node){xx,yy});
    		}
    	}
    	return -1;
    }
    int main()
    {
    	#ifndef ONLINE_JUDGE
    	freopen("ce.in","r",stdin);
    	freopen("ce.out","w",stdout);
    	#endif
    	scanf("%d",&T);
    	for(int maomao=1;maomao<=T;maomao++)
    	{
    		scanf("%d%d",&n,&m);
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=m;j++)
    				tim[i][j]=0x3f3f3f3f;
    		while(!fff.empty()) fff.pop();
    		int tx,ty,x,y;
    		for(int i=1;i<=n;i++)
    		{
    			scanf("%s",s+1);
    			for(int j=1,len=strlen(s+1);j<=len;j++)
    			{
    				a[i][j]=s[j];
    				if(a[i][j]=='J') x=i,y=j;
    				if(a[i][j]=='F') fff.push((Node){i,j}),tim[i][j]=0;
    			}
    		}
    		init();
    		int cur_ans=search(x,y);
    		if(cur_ans==-1) printf("IMPOSSIBLE
    ");
    		else printf("%d
    ",cur_ans);
    	}
    	return 0;
    }
    

    例题2 独轮车

    每一个点的状态不仅仅是横纵坐标,还有颜色,朝向方向。所以我们记录四个参数,继续dfs求最短路即可。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<algorithm>
    #include<cmath>
    #define MAXN 100010
    using namespace std;
    int R,C;
    int vis[30][30][5][5];
    int done[30][30];
    int sx,sy,ex,ey;
    int dir[][2]={{-1,0},{0,1},{1,0},{0,-1}};
    struct t1{int x,y,d,c,t;};
    int bfs(t1 a)
    {
        queue<t1>q;
        q.push(a);
        memset(vis,0,sizeof vis);
        while(!q.empty())
    	{
            t1 u=q.front();
            q.pop();
            if(u.x==ex&&u.y==ey&&u.c==0) return u.t;
            vis[u.x][u.y][u.d][u.c]=1;
            int nd=u.d+1;
            if(nd>3) nd=0;
            t1 nx=u;
            nx.d=nd;
            nx.t=u.t+1;
            if(!vis[nx.x][nx.y][nx.d][nx.c]) q.push(nx);
            vis[nx.x][nx.y][nx.d][nx.c]=1;
            nd=u.d-1;
            if(nd<0) nd=3;
            nx=u; nx.d=nd; nx.t=u.t+1;
            if(!vis[nx.x][nx.y][nx.d][nx.c]) q.push(nx);
            vis[nx.x][nx.y][nx.d][nx.c]=1;
            int xx=u.x+dir[u.d][0];
            int yy=u.y+dir[u.d][1];
            if(xx<0||yy<0||xx>=R||yy>=C) continue;
            if(done[xx][yy]==0) continue;
            int nc=u.c+1;
            if(nc>4) nc=0;
            t1 b=(t1){xx,yy,u.d,nc,u.t+1};
            if(!vis[b.x][b.y][b.d][b.c]) q.push(b);
            vis[b.x][b.y][b.d][b.c]=1;
        }
        return -1;
    }
    int main()
    {
        #ifndef ONLINE_JUDGE
    	freopen("ce.in","r",stdin);
    	#endif
    	char ch;
        int kase=0;
        while (scanf("%d%d",&R,&C))
    	{
            if (R==0) break;
            getchar();
            memset(done,0,sizeof done);
            for (int i=0;i<R;i++)
    		{
                for (int j=0;j<C;j++)
    			{
                    ch=getchar();
                    if (ch!='#') done[i][j]=1;
                    if (ch=='S') sx=i,sy=j;
                    if (ch=='T') ex=i,ey=j;
                }
                getchar();
            }
            t1 a=(t1){sx,sy,0,0,0};
           int ans=bfs(a);
           if(kase) puts("");
           printf("Case #%d
    ",++kase);
           if(ans==-1)puts("destination not reachable");
           else printf("minimum time = %d sec
    ",ans);
        }
        return 0;
    }
    

    例题3 项链

    有一种由彩色珠子连接成的项链。每个珠子的两半由不同的颜色组成。相邻的两个珠子在接触的地方颜色相同。现在有一些零碎的珠子,需要确认它们是否可以复原成完整的项链。如果有解,输出任意一组即可。
    一个模型转换——考虑到相邻的珠子在接触的地方颜色相同,我们可以想到每个颜色看成一个节点,每个珠子的两半连一条有向边。因为是环,所以就是欧拉回路了。
    有向图的欧拉回路有解,必须每个点的度数为偶数。输出方案的时候在回溯之后输出。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    #define MAXN 55
    using namespace std;
    int n,T,kase;
    int done[MAXN][MAXN],du[MAXN];
    inline void search(int x)
    {
    	for(int i=1;i<=50;i++)
    	{
    		if(done[x][i])
    		{
    			done[x][i]--,done[i][x]--;
    			search(i);
    			printf("%d %d
    ",i,x);
    		}
    	}
    }
    int main()
    {
    	#ifndef ONLINE_JUDGE
    	freopen("ce.in","r",stdin);
    	#endif
    	scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d",&n);
    		printf("Case #%d
    ",++kase);
    		memset(done,0,sizeof(done));
    		memset(du,0,sizeof(du));
    		for(int i=1;i<=n;i++)
    		{
    			int x,y;
    			scanf("%d%d",&x,&y);
    			done[x][y]++,done[y][x]++;
    			du[x]++,du[y]++;
    		}
    		bool flag=true;
    		for(int i=1;i<=50;i++)
    			if(du[i]%2)
    			{
    				printf("some beads may be lost
    ");
    				flag=false;
    				break;
    			}
    		if(flag==true)
    			for(int i=1;i<=50;i++)
    				search(i);
    		if(T) printf("
    ");
    	}
    	return 0;
    }
    

    例题4 猜序列

    将连续和化成前缀和之差。设(B[i]=a_1+a_2+...+a_i),规定(B_0=0),则矩阵中任意一项都等价于两个B的差,这样子我们就可以得到B数组两两之间的大小关系。然后就可以将大小关系转化先后关系,拓扑排序构造一组解。
    至于如何处理(B_0=0)的情况,我们在拓扑排序的时候先正常按照tim进行标号,之后再给每个点减去(B_0)的tim就好了。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define MAXN 20
    using namespace std;
    int T,t,n,tot;
    int du[MAXN],head[MAXN*MAXN*2],a[MAXN],id[MAXN],tim[MAXN];
    char s[MAXN][MAXN];
    struct Edge{int nxt,to;}edge[MAXN*MAXN*2];
    inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
    inline void topo_sort()
    {
    	// for(int i=0;i<=n;i++) printf("du[%d]=%d
    ",i,du[i]);
    	queue<int>q;
    	for(int i=0;i<=n;i++)
    		if(du[i]==0&&id[i]==i)
    			q.push(i);
    	while(!q.empty())
    	{
    		int u=q.front();q.pop();
    		tim[u]=++tot;
    		for(int i=head[u];i;i=edge[i].nxt)
    		{
    			int v=edge[i].to;
    			du[v]--;
    			if(du[v]==0) q.push(v);
    		}
    	}
    	for(int i=0;i<=n;i++)
    		if(id[i]!=i)
    			tim[i]=tim[id[i]]; 
    	int cur=tim[0];
    	for(int i=0;i<=n;i++) tim[i]-=cur;;
    	// for(int i=0;i<=n;i++) printf("tim[%d]=%d
    ",i,tim[i]);
    	for(int i=1;i<=n;i++) a[i]=tim[i]-tim[i-1];
    	// for(int i=1;i<=n;i++) printf("a[%d]=%d
    ",i,a[i]);
    }
    int main()
    {
    	#ifndef ONLINE_JUDGE
    	freopen("ce.in","r",stdin);
    	#endif
    	scanf("%d",&T);
    	while(T--)
    	{
    		memset(head,0,sizeof(head));
    		memset(tim,0,sizeof(tim));
    		memset(du,0,sizeof(du));
    		t=tot=0;                          
    		scanf("%d",&n);
    		for(int i=1;i<=n;i++) id[i]=i;
    		for(int i=1;i<=n;i++)
    			for(int j=i;j<=n;j++)
    			{
    				cin>>s[i][j];
    				if(s[i][j]=='0') id[j]=id[i-1];
    			}
    		for(int i=1;i<=n;i++)
    			for(int j=i;j<=n;j++)
    			{
    				if(s[i][j]=='+') add(id[i-1],id[j]),du[id[j]]++;
    				else if(s[i][j]=='-') add(id[j],id[i-1]),du[id[i-1]]++;
    			}
    		topo_sort();
    		for(int i=1;i<=n;i++) printf("%d ",a[i]);
    		if(T) puts("");
    	}
    	return 0;
    }
    

    例题5 圆桌骑士

    有n个骑士经常举行圆桌会议,每次圆桌会议至少应有3个骑士参加,且相互憎恨的骑士不能做在圆桌旁的相邻位置。如果发生意见分歧,则需要举手表决。因此参加会议的骑士数目必须是奇数,一方值赞同和反对的票一样多。知道哪些骑士相互憎恨之后,输出无法参加任何会议的骑士的人数。
    以骑士为节点建立无向图G。如果两个骑士可以相邻,在它们之间连接一条无向边。然后题目就转化成了求不在任何一个简单奇圈上的节点个数。如果图G不联通,则应该对每个联通分量分别求解。
    定理:二分图没有奇圈,不是二分图一定能构造出来每个节点都在奇圈上
    后者的证明:对于一个二分图B,它一定包含一个奇数圈C,现在我们想要节点v也被包含进来。根据联通性,C中应当还存在两个节点u1,u2,使得从v出发有两条不相交路径(除起点外无公共节点),分别到u1,u2。由于在C中,从u1,u2之间的两条路的长度一奇一偶,总能构造出一条经过v的奇圈。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<vector>
    #include<queue>
    #include<stack>
    #define MAXN 1000010
    using namespace std;
    int n,m,cnt,tot,t;
    int done[1010][1010],odd[1010],color[1010];
    int dfn[1010],iscut[1010],head[MAXN<<1],id[1010];
    struct Edge{int nxt,to;}edge[MAXN<<1];
    struct Line{int u,v;}line[MAXN<<1];
    stack<Line>s;
    vector<int>bcc[1010];
    inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
    inline int tarjan(int x,int fa)
    {
    	int lowx=dfn[x]=++tot;
    	int child=0;
    	for(int i=head[x];i;i=edge[i].nxt)
    	{
    		int v=edge[i].to;
    		Line e=(Line){x,v};
    		if(!dfn[v])
    		{
    			s.push(e);
    			child++;
    			int lowv=tarjan(v,x);
    			lowx=min(lowx,lowv);
    			if(lowv>=dfn[x])
    			{
    				iscut[x]=1;
    				bcc[++cnt].clear();
    				for(;;)
    				{
    					Line cur=s.top();s.pop();
    					if(id[cur.u]!=cnt) bcc[cnt].push_back(cur.u),id[cur.u]=cnt;
    					if(id[cur.v]!=cnt) bcc[cnt].push_back(cur.v),id[cur.v]=cnt;
    					if(cur.u==x&&cur.v==v) break;
    				}
    			}
    		}
    		else if(dfn[v]<dfn[x]&&v!=fa)
    		{
    			s.push(e);
    			lowx=min(lowx,dfn[v]);
    		}
    	}
    	if(fa<0&&child>1) iscut[x]=1;
    	return lowx;
    }
    inline bool paint(int x,int b)
    {
    	for(int i=head[x];i;i=edge[i].nxt)
    	{
    		int v=edge[i].to;
    		if(id[v]!=b) continue;
    		if(color[v]==color[x]) 
    			return false;
    		if(!color[v])
    		{
    			color[v]=3-color[x];
    			if(!paint(v,b)) 
    				return false;
    		}
    	}
    	return true;
    }
    inline void init()
    {
    	for(int i=1;i<=n;i++) bcc[i].clear();
    	memset(done,0,sizeof(done));
    	memset(dfn,0,sizeof(dfn));
    	memset(odd,0,sizeof(odd));
    	memset(iscut,0,sizeof(iscut));
    	memset(dfn,0,sizeof(dfn));
    	memset(id,0,sizeof(id));
    	memset(head,0,sizeof(head));
    	cnt=0,tot=0;
    }
    int main()
    {
    	#ifndef ONLINE_JUDGE
    	freopen("ce.in","r",stdin);
    	#endif
    	while(scanf("%d%d",&n,&m)==2)
    	{
    		if(n==0&&m==0) break;
    		init();
    		for(int i=1;i<=m;i++)
    		{
    			int x,y;
    			scanf("%d%d",&x,&y);
    			done[x][y]=done[y][x]=1;
    		}
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=n;j++)
    				if(done[i][j]==0&&i!=j)
    					add(i,j);
    		for(int i=1;i<=n;i++)
    			if(dfn[i]==0)
    				tarjan(i,-1);
    		for(int i=1;i<=cnt;i++)
    		{
    			memset(color,0,sizeof(color));
    			for(int j=0;j<bcc[i].size();j++)
    				id[bcc[i][j]]=i;
    			int u=bcc[i][0];
    			color[u]=1;
    			if(!paint(u,i))
    				for(int j=0;j<bcc[i].size();j++)
    					odd[bcc[i][j]]=1;
    		}
    		int ans=n;
    		for(int i=1;i<=n;i++)
    			if(odd[i])
    				ans--;
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    例题6 井下矿工

    这个题一直RE也不知道为什么......明明在洛谷上是过了的QAQ
    HNOI2012 矿场搭建

    例题7 等价性证明

    在数学中,我们常常需要完成若干个命题的等价性证明。比如,有4个命题a,b,c,d,我们证明a<->b,b<->c,最后c<->d。注意每次证明都是双向的,因此一共完成了6次推到。另一种方法是证明a->b,b->c,c->d,d->a。只需要4次就可以证明全部等价。现在你的任务是证明n个命题全部等价,且现在已经做完了m次推导(已知每次推导的内容),你至少还需要做几次推导才能完成整个证明?

    其实就是找出强联通分量,然后缩点,得到一个DAG。现在需要补边让它强联通,那么我们找出入度为0的节点数量a,找出出度为0的节点数量b。然后max(a,b)就是答案。

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<cstdio>
    #define MAXN 20010
    using namespace std;
    int n,m,tot,t,top,cnt,T;
    int in0[MAXN],out0[MAXN];
    int dfn[MAXN],low[MAXN],head[50010*4],st[MAXN],in[MAXN],c[MAXN];
    struct Edge{int nxt,to;}edge[50010*4];
    struct Line{int x,y;}line[50010*4];
    inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
    inline void tarjan(int x)
    {
    	dfn[x]=low[x]=++tot;
    	st[++top]=x;
    	in[x]=1;
    	for(int i=head[x];i;i=edge[i].nxt)
    	{
    		int v=edge[i].to;
    		if(!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]);
    		else if(in[v]) low[x]=min(low[x],dfn[v]);
    	}
    	if(dfn[x]==low[x])
    	{
    		int v;
    		++cnt;
    		do{v=st[top--];in[v]=0;c[v]=cnt;}while(v!=x);
    	}
    }
    inline void init()
    {
    	memset(c,0,sizeof(c));
    	memset(dfn,0,sizeof(dfn));
    	memset(low,0,sizeof(low));
    	memset(in,0,sizeof(in));
    	memset(head,0,sizeof(head));
    	tot=cnt=top=t=0;
    }
    int main()
    {
    	#ifndef ONLINE_JUDGE
    	freopen("ce.in","r",stdin);
    	#endif
    	scanf("%d",&T);
    	while(T--)
    	{
    		
    		scanf("%d%d",&n,&m);
    		init();
    		for(int i=1;i<=m;i++)
    		{
    			scanf("%d%d",&line[i].x,&line[i].y);
    			add(line[i].x,line[i].y);
    		}
    		for(int i=1;i<=n;i++)
    			if(!dfn[i])
    				tarjan(i);
    		if(cnt==1) printf("0
    ");
    		else
    		{
    			for(int i=1;i<=cnt;i++) in0[i]=out0[i]=1;
    			for(int i=1;i<=m;i++)
    			{
    				if(c[line[i].x]!=c[line[i].y])
    					in0[c[line[i].x]]=0,out0[c[line[i].y]]=0;
    			}	
    			int ans1=0,ans2=0;
    			for(int i=1;i<=cnt;i++)
    			{
    				if(in0[i]) ans1++;
    				if(out0[i]) ans2++;
    			}
    			printf("%d
    ",max(ans1,ans2));
    		}
    	}
    	return 0;
    }
    

    例题8 最大团

    给一张有向图G,求一个节点数量最大的节点集,使得该节点集中任意两个节点u,v满足:要么u可以到达v,要么v可以到达u,相互可达也可以。(别被题目名字误导了,不是让你求最大团QAQ)
    显然在一个强联通分量上的节点要么都选,要么都不选。那么直接缩点然后跑DP就行了......
    就是在DAG上找一条最长的链,然后输出它的长度即可。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define MAXN 1010
    using namespace std;
    int n,m,tot,top,cnt,t,T;
    int ex[MAXN],st[MAXN],dp[MAXN],a[MAXN];
    int sum[MAXN],head[100010],color[MAXN],dfn[MAXN],low[MAXN];
    struct Edge{int nxt,to;}edge[100010];
    struct Line{int u,v;}line[100010];
    inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
    inline void tarjan(int x)
    {
        dfn[x]=low[x]=++tot;
        st[++top]=x;
        ex[x]=1;
        for(int i=head[x];i;i=edge[i].nxt)
        {
            int v=edge[i].to;
            if(!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]); 
            else if(ex[v]) low[x]=min(low[x],dfn[v]);
        }
        if(dfn[x]==low[x])
        {
            if(st[top]==x) top--,ex[x]=0;
            else
            {
                int v;cnt++;
                do{v=st[top--];color[v]=cnt;sum[cnt]+=a[v];ex[v]=0;}while(v!=x);
            }
        }
    }
    inline int solve(int x)
    {
        dp[x]=sum[x];
        int maxx=0;
        for(int i=head[x];i;i=edge[i].nxt)
        {
            int v=edge[i].to;
            solve(v);
            maxx=max(maxx,dp[v]);
        }
        dp[x]+=maxx;
        return dp[x];
    }
    int main()
    {
        #ifndef ONLINE_JUDGE
        freopen("ce.in","r",stdin);
        freopen("ce.out","w",stdout);
        #endif
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&m);
            memset(head,0,sizeof(head));
            memset(color,0,sizeof(color));
            memset(dfn,0,sizeof(dfn));
            memset(low,0,sizeof(low));
            memset(ex,0,sizeof(ex));
            memset(dp,0,sizeof(dp));
            memset(sum,0,sizeof(sum));
            tot=t=cnt=top=0;
            for(int i=1;i<=n;i++) a[i]=1;
            for(int i=1;i<=m;i++)
            {
                scanf("%d%d",&line[i].u,&line[i].v);
                add(line[i].u,line[i].v);
            }
            for(int i=1;i<=n;i++)
                if(!dfn[i])
                    tarjan(i);
            for(int i=1;i<=n;i++) 
                if(color[i]==0)
                    color[i]=++cnt,sum[cnt]=a[i];
            t=0;
            memset(head,0,sizeof(head));
            for(int i=1;i<=m;i++)
            {
                if(color[line[i].u]!=color[line[i].v])
                    add(color[line[i].u],color[line[i].v]);
            }
            int ans=0;
            for(int i=1;i<=n;i++)
                if(!dp[i])
                    ans=max(ans,solve(i));
            printf("%d
    ",ans);
        }
        return 0;
    }
    
  • 相关阅读:
    微信小程序退款【证书的使用】
    生成随机位数的UUID
    弹出层-layui
    load加载层-layui
    form-layui
    table-layui
    下拉列表模仿placeholder效果
    .net core 2.0 Unable to convert MySQL date/time to System.DateTime
    .net core Include问题
    .net core 2.0 配置Session
  • 原文地址:https://www.cnblogs.com/fengxunling/p/10851059.html
Copyright © 2020-2023  润新知