• 算法初探


    更新记录

    【1】2020.08.08-17:59

    • 1.完善内容

    正文

    在一些特殊的问题中,例如在一有向有环图中求最长路径,点(边)可以重复经过,但是点权(边权)只算一次
    (要是无限算不沿着环瞎就就彳亍嘛)
    容易想到,在遇到环的时候我们要将这个环走一遍以获取最大值

    那么缩点就是将环缩成一个点,然后进行DAG上的动态规划

    我们回想强连通分量的定义:

    在有向图G中,如果两个点u,v间有一条从u到v的有向路径,同时还有一条从v到u的有向路径,则称两个点强连通
    如果有向图G的每两个点都强连通,称G是一个强连通图
    有向非强连通图的极大强连通子图,称为强连通分量。

    也就是说强连通分量一定是一个环

    这样我们第一步就通过tarjan算法找到强连通分量就可以

    (dfn)代表搜索的(dfs)
    (low)代表在中所能追溯到的最小(早)的(dfs)

    那么这个栈是干啥呢,让我们按算法的顺序一步一步来

    [算法开始]

    遍历每个点的出边对应的点,如果没有搜索过就先搜索出边所对应的点
    搜索后,记最小的(low)

    如果搜索过且在栈中,就说明这两个点可以互相到达,记最小的(low)

    如果其(dfs)序和(low)相等,就说明遍历完毕,并且这个点不能从其他的地方走过来
    此时栈中的(n)上面的点(包括(n)就是一个强连通分量

    出栈,统计

    inline void tarjan(int n){
    	dfn[n]=++time;
    	low[n]=time;
    	stk[++stks]=n;
    	for(int i=head[n];i;i=e[i].na){
    		if(!dfn[e[i].np]){
    			tarjan(e[i].np);
    			low[n]=min(low[n],low[e[i].np]);
    		}
    		else if(!vis[e[i].np])
    			low[n]=min(low[n],dfn[e[i].np]);
    	}
    	if(dfn[n]==low[n]){
    		vis[n]=++tot;
    		while(stk[stks]!=n){
    			stnum[tot]+=1;
    			vis[stk[stks]]=tot;
    			stks-=1;
    		}
    		stnum[tot]+=1;
    		stks-=1;
    	}
    }
    

    【模板】缩点

    将环缩为点之后重建图

    此时的图是一个有向无环

    拓扑排序跑一个DAG上的dp就好

    缩点之前

    缩点之后

    #include<iostream>
    #include<queue>
    #include<cstring>
    #define N 200001
    using namespace std;
    struct Edge{
    	int na,np;
    }e[N],dag[N];
    int head[N],head2[N],num2,num,now,n,m,fr,to,w[N],vis[N],dfn[N],lian[N],ru[N],low[N],times,stnum[N],stk[N],stks,maxn;
    queue<int>q;
    inline void add(int f,int t){
    	e[++num].na=head[f];
    	e[num].np=t;
    	head[f]=num;
    }
    inline void add2(int f,int t){
    	dag[++num2].na=head2[f];
    	dag[num2].np=t;
    	head2[f]=num2;
    }
    inline void tarjan(int n){
    	dfn[n]=++times;
    	low[n]=times;
    	stk[++stks]=n;
    	for(int i=head[n];i;i=e[i].na){
    		if(!dfn[e[i].np]){
    			tarjan(e[i].np);
    			low[n]=min(low[n],low[e[i].np]);
    		}
    		else if(!vis[e[i].np]){
    			low[n]=min(low[n],dfn[e[i].np]);
    		}
    	}
    	if(dfn[n]==low[n]){
    		vis[n]=n;
    		while(stk[stks]!=n){
    			stnum[n]+=w[stk[stks]];
    			vis[stk[stks]]=n;
    			stks-=1;
    		}
    		stnum[n]+=w[stk[stks]];
    		stks-=1;
    	}
    }
    int main(){
    	cin>>n>>m;
    	for(int i=1;i<=n;i++) cin>>w[i];
    	for(int i=1;i<=m;i++){
    		cin>>fr>>to;
    		add(fr,to);
    	}
    	for(int i=1;i<=n;i++){
    		if(!dfn[i]) tarjan(i);
    	}
    	for(int i=1;i<=n;i++){
    		for(int o=head[i];o;o=e[o].na){
    			if(vis[i]!=i&&vis[e[o].np]!=e[o].np) continue;
    			if(vis[i]==vis[e[o].np]) continue;
    			add2(vis[i],vis[e[o].np]);
    			ru[vis[e[o].np]]+=1;
    		}
    	}
    	for(int i=1;i<=n;i++){
    		if(!ru[i]){
    			q.push(i),lian[i]=stnum[i];
    			maxn=max(maxn,stnum[i]);
    		}
    	}
    	while(q.size()){
    		now=q.front();
    		q.pop();
    		for(int i=head2[now];i;i=dag[i].na){
    			ru[dag[i].np]-=1;
    			lian[dag[i].np]=max(lian[dag[i].np],lian[now]+stnum[dag[i].np]);
    			maxn=max(maxn,lian[dag[i].np]);
    			if(!ru[dag[i].np]){
    				q.push(dag[i].np);
    			}
    		}
    	}
    	cout<<maxn;
    }
    

    [USACO03FALL][HAOI2006]受欢迎的牛 G

    首先读题,整理信息可知:

    • 环上的牛互相喜欢
    • 能被所有牛喜欢的牛肯定是没有出边

    由性质一可知我们可以进行缩点

    重建图之后是一个有向无环图
    由性质二可知能当明星的牛肯定是有向无环图的终点

    终点也可能是缩点而得来的,所以要检查终点所对应的原来的强连通分量

    #include<iostream>
    #include<queue>
    #include<cstring>
    #define N 200001
    using namespace std;
    struct Edge{
    	int na,np;
    }e[N],dag[N];
    int head[N],head2[N],num2,num,now,n,m,fr,to,w[N],vis[N],dfn[N],out,lian[N],chu[N],low[N],times,stnum[N],stk[N],stks,maxn;
    queue<int>q;
    inline void add(int f,int t){
    	e[++num].na=head[f];
    	e[num].np=t;
    	head[f]=num;
    }
    inline void add2(int f,int t){
    	dag[++num2].na=head2[f];
    	dag[num2].np=t;
    	head2[f]=num2;
    }
    inline void tarjan(int n){
    	dfn[n]=++times;
    	low[n]=times;
    	stk[++stks]=n;
    	for(int i=head[n];i;i=e[i].na){
    		if(!dfn[e[i].np]){
    			tarjan(e[i].np);
    			low[n]=min(low[n],low[e[i].np]);
    		}
    		else if(!vis[e[i].np]){
    			low[n]=min(low[n],dfn[e[i].np]);
    		}
    	}
    	if(dfn[n]==low[n]){
    		vis[n]=n;
    		while(stk[stks]!=n){
    			stnum[n]+=1;
    			vis[stk[stks]]=n;
    			stks-=1;
    		}
    		stnum[n]+=1;
    		stks-=1;
    	}
    }
    int main(){
    	cin>>n>>m;
    	for(int i=1;i<=m;i++){
    		cin>>fr>>to;
    		add(fr,to);
    	}
    	for(int i=1;i<=n;i++){
    		if(!dfn[i]) tarjan(i);
    	}
    	for(int i=1;i<=n;i++){
    		for(int o=head[i];o;o=e[o].na){
    			if(vis[i]==vis[e[o].np]) continue;
    			chu[vis[i]]+=1;
    		}
    	}
    	for(int i=1;i<=n;i++){
    		if(!chu[i]&&vis[i]==i){
    			if(out){
    				cout<<"0";return 0;
    			}
    			out=stnum[vis[i]];
    		}
    	}
    	cout<<out;
    }
    

    消息扩散

    这道题太水了所以就只写个思路:

    统计入度为0的点的个数

    结束了

    [Wind Festival]Running In The Sky

    这道题我给个好评
    首先亮度和是很简单的,跑个DAG dp就可以了

    重要的是路径上最大的亮度值

    不要以为跑dp的时候顺便取个(maxn)最大值就行了,看题

    如果有多条符合条件的路径,输出能产生最大单只风筝亮度的答案

    也就是说在保证了第一问的前提下才能选最大值

    我们很容易想到:在跑dp的时候顺便将每个点的最大值都记录下来
    然后呢?

    然后我们用一个变量将最大值答案位置记录下来

    这个位置不是随便更新的,只有在总和最大值更新的时候才能更新

    这里的更新是指:
    现在的总和值大于等于原来的总和值

    跑完了这些,答案也就呼之欲出了

    注意

    • 在初始化dp压队列的时候也要统计
    #include<iostream>
    #include<queue>
    #include<cstring>
    #define N 1000001
    using namespace std;
    struct Edge{
    	int na,np;
    }e[N],dag[N];
    int head[N],head2[N],num2,num,now,n,m,fr,to,w[N],vis[N],dfn[N],lian[N],lian2[N],maxst[N],ru[N],low[N],times,stnum[N],stk[N],stks,maxn,wei,maxn2;
    queue<int>q;
    inline void add(int f,int t){
    	e[++num].na=head[f];
    	e[num].np=t;
    	head[f]=num;
    }
    inline void add2(int f,int t){
    	dag[++num2].na=head2[f];
    	dag[num2].np=t;
    	head2[f]=num2;
    }
    inline void tarjan(int n){
    	dfn[n]=++times;
    	low[n]=times;
    	stk[++stks]=n;
    	for(int i=head[n];i;i=e[i].na){
    		if(!dfn[e[i].np]){
    			tarjan(e[i].np);
    			low[n]=min(low[n],low[e[i].np]);
    		}
    		else if(!vis[e[i].np]){
    			low[n]=min(low[n],dfn[e[i].np]);
    		}
    	}
    	if(dfn[n]==low[n]){
    		vis[n]=n;
    		while(stk[stks]!=n){
    			stnum[n]+=w[stk[stks]];
    			maxst[n]=max(maxst[n],w[stk[stks]]);
    			vis[stk[stks]]=n;
    			stks-=1;
    		}
    		stnum[n]+=w[stk[stks]];
    		maxst[n]=max(maxst[n],w[stk[stks]]);
    		stks-=1;
    	}
    }
    int main(){
    	cin>>n>>m;
    	for(int i=1;i<=n;i++) cin>>w[i];
    	for(int i=1;i<=m;i++){
    		cin>>fr>>to;
    		add(fr,to);
    	}
    	for(int i=1;i<=n;i++){
    		if(!dfn[i]) tarjan(i);
    	}
    	for(int i=1;i<=n;i++){
    		for(int o=head[i];o;o=e[o].na){
    			if(vis[i]!=i&&vis[e[o].np]!=e[o].np) continue;
    			if(vis[i]==vis[e[o].np]) continue;
    			add2(vis[i],vis[e[o].np]);
    			ru[vis[e[o].np]]+=1;
    		}
    	}
    	for(int i=1;i<=n;i++){
    		if(!ru[i]){
    			q.push(i);
    			lian[i]=stnum[i];
    			lian2[i]=maxst[i];
    			if(maxn<stnum[i]){
    				maxn=stnum[i];
    				wei=i;
    			}
    			else if(maxn==stnum[i]){
    				wei=(lian2[wei]>=lian2[i]?wei:i);
    			}
    		}
    	}
    	while(q.size()){
    		now=q.front();
    		q.pop();
    		for(int i=head2[now];i;i=dag[i].na){
    			ru[dag[i].np]-=1;
    			lian[dag[i].np]=max(lian[dag[i].np],lian[now]+stnum[dag[i].np]);
    			lian2[dag[i].np]=max(lian2[now],maxst[dag[i].np]);
    			if(maxn<lian[dag[i].np]){
    				maxn=lian[dag[i].np];
    				wei=dag[i].np;
    			}
    			else if(maxn==lian[dag[i].np]){
    				wei=(lian2[wei]>=lian2[dag[i].np]?wei:dag[i].np);
    			}
    			if(!ru[dag[i].np]) q.push(dag[i].np);
    		}
    	}
    	cout<<maxn<<" "<<lian2[wei];
    }
    

    技巧

    缩点其实没啥技巧,就是我发现(stnum)这个数组原本是记录某个强连通分量集合中强连通分量的点的个数
    但是在某些题目中这个数组是没用的
    我们可以用这个数组来记录这个强连通分量所有点权(边权)的和

  • 相关阅读:
    python--第十天总结(Select/Poll/Epoll使用 )
    python--第九天总结
    数据库文件*.sdf文件定时备份,但是大小的增量在不断增长的问题排查
    打开和创建SqlCe(.sdf文件)
    C# 在RichTextBox中滚动鼠标时滚动的是父窗口的滚动条
    Foxmail 7.0破解版,拷贝到新机器后,发送邮件乱码问题
    C# RichTextBox设置行间距
    winform在鼠标操作时要判断是否按下Ctrl键
    vs2015 制作安装包额外需要安装的软件VSI_bundle
    vs 2015工具栏添加Tab Order
  • 原文地址:https://www.cnblogs.com/zythonc/p/13423945.html
Copyright © 2020-2023  润新知