• 不常用算法(模板)合集


    数学

    图论

    最小树形图

    问题直接顾名思义,略去。该问题常用朱刘算法,下面这张图很好地展示了它的流程,虽然确实丑得有点离谱了

    本质上朱刘算法就是一个反悔贪心:

    • 初始,贪心选择最小入边。
    • 没有出现环,我们必然找到了树形图。
    • 覅原则,出现了环,我们必须要拆掉环上的一条边,这个过程通过修改环上所有点的入边边权(减去终点的最小边权)实现,这样在之后选中某条指向环的边的时候,我们就相当于用新边替换了旧边,使得树形图合法;可以发现这个过程一定会发生。
    • 最后,收缩环,迭代。

    以下是这个算法的 (O(|V||E|)) 实现:

    int inE[MAXN], pre[MAXN], id[MAXN], vis[MAXN];
    int fr[MAXM], to[MAXM], wei[MAXM];
    int N, M; // N: 点数; M: 边数
    
    LL ZhuLiu( int rt )
    {
    	LL ret = 0; 
    	int n = N, cnt = 0;
    	while( true )
    	{
    		cnt = 0;
    		rep( i, 1, n ) 
    			vis[i] = id[i] = 0, inE[i] = INF, pre[i] = -1;
    		rep( i, 1, M )
    			if( fr[i] ^ to[i] && wei[i] < inE[to[i]] )
    				inE[to[i]] = wei[i], pre[to[i]] = i;
            // 寻找最小入边;注意我们必须忽略 " 自环 " ,因为这里的自环其实也有可能是缩环之前的环边。
    		rep( i, 1, n ) 
    		{
    			if( i == rt ) continue; 
    			if( inE[i] == INF ) return -1;
    			else ret += inE[i];
    		}
    		rep( i, 1, n )
    		{
    			if( i == rt ) continue; int cur = i;
    			for( ; vis[cur] ^ i && ! id[cur] && cur ^ rt ; cur = fr[pre[cur]] ) vis[cur] = i;
    			if( ! id[cur] && cur ^ rt )
    			{
    				id[cur] = ++ cnt;
    				for( int u = fr[pre[cur]] ; u ^ cur ; u = fr[pre[u]] )
    					id[u] = cnt;
                   	//注意我们可能并非一开始就在环上,而是走着走着掉进了一个环里
    			}
    		}
            //找环,缩环
    		if( cnt == 0 ) break;
    		rep( i, 1, n ) if( ! id[i] ) id[i] = ++ cnt;
    		rep( i, 1, M )
    		{
    			int u = fr[i], v = to[i];
    			fr[i] = id[u], to[i] = id[v], wei[i] -= inE[v];
                //保证忽略环边,因此可以对所有边进行修改
                //对于不指向环的边,最小边权已经加入答案,且之后的最小边权必然为 0 
    		}
            // 修改边权,重新构图
    		rt = id[rt], n = cnt;
    	}
    	return ret;
    }
    

    朱刘算法还有其更加高效的实现。具体而言,我们考虑朱刘算法的过程:

    1. 对于一个点,找出最小入边
    2. 如果出现了合并环上的所有入边,接着回到 1

    假如我们想用数据结构维护,不妨分析上述材料

    • " 最小入边 " 体现了我们需要使用一种可以快速查询最小值的数据结构,应该是不错的选择。

    • " 环 " 说明了我们需要快速判断连通性,并查集可以很方便地解决。

    • " 合并入边 " 说明了我们需要一种可以快速合并的数据结构。

    • 总之,我们可以使用可并堆维护每个点的出边集合,并查集判断连通性。

      如果出现了环,我们就暴力把环上的出边收缩起来(总收缩次数是 (O(n)) 的)。在算法过程中我们需要始终维护连通性。

    具体可以参考代码:

    struct DSU
    {
    	int fa[MAXN];
    	
    	void MakeSet( const int siz ) { rep( i, 1, siz ) fa[i] = i; }
    	int FindSet( const int u ) { return fa[u] = ( fa[u] == u ? u : FindSet( fa[u] ) ); }
    	void UnionSet( const int u, const int v ) { fa[FindSet( u )] = FindSet( v ); }
    }cir, link;
    
    int fr[MAXM], to[MAXM];
    int lch[MAXM], rch[MAXM], dist[MAXM], w[MAXM], tag[MAXM];
    int rt[MAXN], pre[MAXN];
    
    int N, M, R, ans = 0;
    
    void Add( const int x, const int v ) { if( x ) w[x] += v, tag[x] += v; }
    void Normalize( const int x ) { Add( lch[x], tag[x] ), Add( rch[x], tag[x] ), tag[x] = 0; }
    
    int Merge( int u, int v )
    {
    	if( ! u || ! v ) return u | v;
    	if( w[u] > w[v] ) swapp( u, v );
    	Normalize( u ); rch[u] = Merge( rch[u], v );
    	if( dist[lch[u]] < dist[rch[u]] ) swapp( lch[u], rch[u] );
    	dist[u] = dist[rch[u]] + 1; return u;
    }
    
    int Pop( const int u )
    {
    	Normalize( u );
    	return Merge( lch[u], rch[u] );
    }
    
    bool Get( const int u )
    {
    	for( ; cir.FindSet( fr[rt[u]] ) == cir.FindSet( u ) ; 
    		 rt[u] = Pop( rt[u] ) );
       	//类似于朴素朱刘,这里不能取到环内边
        //一定注意 判连通性 和 判环内 需要两个并查集,因为在环的最后一条边连上之前环其实已经连通了
    	if( ! rt[u] ) return false;
    	ans += w[pre[u] = rt[u]]; return true;
    }
    
    int main()
    {
    	rep( i, 1, M )
            rt[to[i]] = Merge( rt[to[i]], i );
    	cir.MakeSet( N ), link.MakeSet( N );
    	rep( i, 1, N ) 
    		if( i ^ R ) //每个点可以顺序寻找入边
    		{
    			if( ! Get( i ) ) return puts( "-1" ), 0;
    			while( link.FindSet( fr[pre[i]] ) == link.FindSet( i ) )
                //假如出现环,则需要缩环
    			{
    				Add( rt[i], - w[pre[i]] );
    				for( int u = cir.FindSet( fr[pre[i]] ) ; u ^ i ; u = cir.FindSet( fr[pre[u]] ) )
    				{
    					Add( rt[u], - w[pre[u]] );
    					rt[i] = Merge( rt[i], rt[u] );
    					cir.UnionSet( u, i );
    				}
                    //暴力打标记合并,利用环的并查集跳转
    				if( ! Get( i ) ) return puts( "-1" ), 0;
    			}
    			link.UnionSet( i, fr[pre[i]] );
    		}
    	write( ans ), putchar( '
    ' );
    	return 0;
    }
    

    一般图最大匹配

    常用带花树算法。主要思想是处理一般图上特殊的奇环,通过 balabala (出题人这只鸽子决定期末考完了再补)从而将说明可以将奇环缩成一个点,然后就可以像二分图匹配一样直接跑增广路增广。

    具体可以参考代码:

    int q[MAXN], h, t;
    
    int head[MAXN], fa[MAXN], pre[MAXN], match[MAXN], vis[MAXN], travel[MAXN];
    int N, M, cnt, tims = 0;
    
    int LCA( int u, int v )
    {
    	u = fa[u], v = fa[v], tims ++;
    	for( ; travel[u] ^ tims ; u ^= v ^= u ^= v )
    		if( u ) travel[u] = tims, u = fa[pre[match[u]]];
    	return u;
    }
    
    void Blossom( int u, int v, int lca )
    {
    	while( fa[u] ^ lca )
    	{
    		pre[u] = v, v = match[u];
    		if( vis[v] == 1 ) vis[v] = 2, q[++ t] = v;
    		fa[u] = fa[v] = lca, u = pre[v];
    	}
    }
    
    bool Augment( const int sta )
    {
    	int u, v; h = 1, t = 0;
    	rep( i, 1, N ) fa[i] = i, pre[i] = vis[i] = 0;
    	vis[q[++ t] = sta] = 2;
    	while( h <= t )
    	{
    		u = q[h ++];
    		for( int i = head[u] ; i ; i = Graph[i].nxt )
    			if( ! vis[v = Graph[i].to] )
    			{
    				vis[v] = 1, pre[v] = u;
    				if( ! match[v] )
    					for( int cur = v ; ; )
    					{
    						int nxt = match[pre[cur]];
    						match[cur] = pre[cur], match[pre[cur]] = cur;
    						if( pre[cur] == sta ) return true;
    						cur = nxt;
    					}
    				else vis[q[++ t] = match[v]] = 2;
    			}
    			else if( vis[v] == 2 && fa[u] ^ fa[v] )
    			{ 
    				int lca = LCA( u, v ); 
    				Blossom( u, v, lca ), Blossom( v, u, lca ); 
    			}
    	}
    	return false;
    }
    
    int main()
    {
        int ans = 0;
        rep( i, 1, N )
            ans += ! match[i] && Augment( i );
    }
    

    数据结构

    字符串

    其他

    模拟退火

    具体实现细节就不写了,提醒几点在下面:

    • 退火其实还是类似于爬山,只不过是增加了跳出的概率。因此一次退火的过程中需要使用过程解进行迭代,如果用最优解迭代其实是没啥用的。
    • 概率函数: (e^{frac{Delta}{T}}) ,注意 (Delta) 的正负。个人认为一个简单的判别方法是:作为一个概率值,函数值应该落在 ([0,1]) 里面,所以应该有 (Delta<0)
    • 生成的新解需要在当前解的周围搜出一个点,注意新解的范围不要太小。
    • 毕竟是个概率算法,正式场合少用,除非你很会调参
  • 相关阅读:
    golang删除数组某个元素
    golang用通道实现信号量,控制并发个数
    什么是ScaleIO中的forwards rebuild和backwards rebuild?
    SQL Server中的database checkpoint
    如何将thick provision lazy zeroed的VMDK文件转换为thick provision eager zeroed?
    LoadTestAgentResultsLateException in VS2010
    SQL Server Instance无法启动了, 因为TempDB所在的分区没有了, 怎么办?
    VMware vCenter中, 如何辩认虚机上Raw Device Mapping过了的一块物理磁盘?
    SQL Server AlwaysOn Setup Step-By-Step Guide
    TPC-E在populate测试Database时需要注意的一些事项
  • 原文地址:https://www.cnblogs.com/crashed/p/14269183.html
Copyright © 2020-2023  润新知