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


    数学

    图论

    最小树形图

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

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

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

    以下是这个算法的 (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)
    • 生成的新解需要在当前解的周围搜出一个点,注意新解的范围不要太小。
    • 毕竟是个概率算法,正式场合少用,除非你很会调参
  • 相关阅读:
    你不知道的CSS(二) (转载)
    sass 使用
    前端常用工具介绍
    web端调用打印
    sublime 插件:Emmet
    Sublime text 安装Package Control
    Sublime Text 常用的16 个 Sublime Text 快捷键
    js中如何快速获取数组中的最大值最小值
    web app变革之rem
    前端工程需要会的技能
  • 原文地址:https://www.cnblogs.com/crashed/p/14269183.html
Copyright © 2020-2023  润新知