acwing提高最小生成树

最小生成树

1146. 新的开始(最小生成树源点成本 - > 建立虚拟源点)

在这里插入图片描述

输入样例:

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

输出样例:

9

代码模板

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 305, M = 2e6;

int p[N],n,cnt;
struct edge{
	int a,b,w;
	
	bool operator < (const edge & e) const
	{
		return w < e.w;
	}
}edges[M];

int find(int x)
{
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}

int main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++ )
	{
		int x;
		cin >> x; 
		edges[cnt++] = {i,0,x};
	}
	for(int i = 1; i <= n; i ++ )
		for(int j = 1; j <= n; j ++ )
		{
			int x;
			cin >> x;
			edges[cnt ++] = {i,j,x};
		}
	
	for(int i = 1; i <= n; i ++ ) p[i] = i; 
	sort(edges, edges + cnt);
	
	int res = 0;
	for(int i = 0; i < cnt; i ++ )
	{
		int a = edges[i].a, b = edges[i].b, w = edges[i].w;
		
		a = find(a), b = find(b);
		if(a != b)
		{
			res += w;
			p[a] = b;
		}
	}
	
	cout << res << endl; 
	
	return 0;
}

 

1145. 北极通讯网络(森林树根个数限制)

在这里插入图片描述
在这里插入图片描述
思路:

先求最小生成树,在用卫星设备代替最小生成树中中权重最大的边

输入样例:

3 2
10 10
10 0
30 0

输出样例:

10.00

代码模板

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 510, M = N * N / 2;

int n, k, m;
struct Edge
{
    int a, b;
    double w;
    bool operator< (const Edge &t) const
    {
        return w < t.w;
    }
}e[M];
PII q[M];
int p[N];

double get_dist(PII a, PII b)
{
    int dx = a.x - b.x;
    int dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
    //手动建立边
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < i; j ++ )
            e[m ++ ] = {i, j, get_dist(q[i], q[j])};

    sort(e, e + m);
    for (int i = 0; i < n; i ++ ) p[i] = i;

    int cnt = n;
    double res = 0;
    for (int i = 0; i < m; i ++ )
    {
        if (cnt <= k) break;

        int a = find(e[i].a), b = find(e[i].b);
        double w = e[i].w;
        if (a != b)
        {
            p[a] = b;
            cnt -- ;
            res = w;
        }
    }

    printf("%.2lf\n", res);

    return 0;
}

 

346. 走廊泼水节(扩展完全图)

在这里插入图片描述
思路:

步骤:把首先每个点看成一个连通块,一边连接两个连通块时,一边求得增加边使得两个连通块成完全图的权值总和。 注意:为使得满足扩展为完全图后最小生成树仍然是这棵树的条件,增加的边权值一定大于连接两个连通块的边(最小生成树的边权w),即每次都加上两个连通块个数的乘积-1乘上(w + 1)

输入样例:

2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5 

输出样例:

4
17 

代码模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 6005, M = N * N;

int n;
int p[N], size1[N];
struct edge{
	int a,b,w;
	
	bool operator <(const edge &e) const
	{
		return w < e.w;
	}
}edges[N];

int find(int x)
{
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}

int main()
{
	int T;
	cin >> T;
	while(T -- )
	{
		cin >> n;
		for(int i = 0; i < n - 1; i ++ )
		{
			int a, b, w;
			cin >> a >> b >> w;
			edges[i] = {a,b,w}; 
		}
		
		for(int i = 1; i <= n; i ++ ) p[i] = i, size1[i] = 1;
		
		sort(edges, edges + n - 1);
		int res = 0;
		for(int i = 0; i < n - 1; i ++ )
		{
			int a,b,w;
			a = edges[i].a, b = edges[i].b, w = edges[i].w;
			a = find(a), b = find(b);
			if(a != b)
			{
				res += (size1[a] * size1[b] - 1) * (w + 1);
				p[a] = b;
				size1[b] += size1[a]; 
			}
		}
		cout << res << endl;
	}
	
	return 0;
} 



 

1148. 秘密的牛奶运输(求次小生成树)

在这里插入图片描述
思路

步骤:先求最小生成树,然后枚举每条非树边,然后讲该边加入树中,同时从树中去掉一条边,是的最终的图仍是一课树。则可以求出次最小生成树
注意:
w :为加入树的边权, dist1[a,b]: 为a到b的路径中边的最大值,dist2[a,b]: 为a到b的路径中边的次大值
1.从树中去掉一条边,从树中去除的边不可能大于w(可用反证法证明), 但是可能等于w, 所以当 w 大于dist1[a,b]直接替代最大值即可,当w 等于a到b的最大值 需要替代a到b的次大值即dist2[a,b]
2.预处理dist1[a,b],dist2[a,b] 从定义出发,dfs!
3.数据范围比较大,需开LL,1e18

输入样例:

4 4
1 2 100
2 4 200
2 3 250
3 4 100

输出样例:

450

代码模板

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 510, M = 10010;

int n, m;
struct Edge
{
    int a, b, w;
    bool f;
    bool operator< (const Edge &t) const
    {
        return w < t.w;
    }
}edge[M];
int p[N];
int dist1[N][N], dist2[N][N];
int h[N], e[N * 2], w[N * 2], ne[N * 2], idx;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void dfs(int u, int fa, int maxd1, int maxd2, int d1[], int d2[])
{
    d1[u] = maxd1, d2[u] = maxd2;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j != fa)
        {
            int td1 = maxd1, td2 = maxd2;
            if (w[i] > td1) td2 = td1, td1 = w[i];
            else if (w[i] < td1 && w[i] > td2) td2 = w[i];
            dfs(j, u, td1, td2, d1, d2);
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i ++ )
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edge[i] = {a, b, w};
    }

    sort(edge, edge + m);
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    LL sum = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = edge[i].a, b = edge[i].b, w = edge[i].w;
        int pa = find(a), pb = find(b);
        if (pa != pb)
        {
            p[pa] = pb;
            sum += w;
            add(a, b, w), add(b, a, w);
            edge[i].f = true;
        }
    }

    for (int i = 1; i <= n; i ++ ) dfs(i, -1, -1e9, -1e9, dist1[i], dist2[i]);

    LL res = 1e18;
    for (int i = 0; i < m; i ++ )
        if (!edge[i].f)
        {
            int a = edge[i].a, b = edge[i].b, w = edge[i].w;
            LL t;
            if (w > dist1[a][b])
                t = sum + w - dist1[a][b];
            else if (w > dist2[a][b])
                t = sum + w - dist2[a][b];
            res = min(res, t);
        }

    printf("%lld\n", res);

    return 0;
}

 

负环

904. 虫洞(裸负环问题)

在这里插入图片描述
输入样例:

2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8

输出样例:

NO
YES

代码模板

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510, M = 5210;

int n, m1, m2;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int q[N], cnt[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

bool spfa()
{
    memset(dist, 0, sizeof dist);
    memset(cnt, 0, sizeof cnt);
    memset(st, 0, sizeof st);

    int hh = 0, tt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        q[tt ++ ] = i;
        st[i] = true;
    }

    while (hh != tt)
    {
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = false;

        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true;
                if (!st[j])
                {
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        scanf("%d%d%d", &n, &m1, &m2);
        memset(h, -1, sizeof h);
        idx = 0;
        for (int i = 0; i < m1; i ++ )
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c), add(b, a, c);
        }
        for (int i = 0; i < m2; i ++ )
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, -c);
        }

        if (spfa()) puts("YES");
        else puts("NO");
    }

    return 0;
}

 

361. 观光奶牛(01分数规划->二分)

在这里插入图片描述

思路:01分数规划

\(\frac{\sum_{}f[i]}{\sum_{}t[i]} > mid\) -> \(\sum_{}f[i]-mid * \sum_{}t[i] > 0\) -> \(\sum_{}(f[i] - mid * t[i]) > 0\)
判断图中是否存在正环

输入样例:

5 7
30
10
10
5
10
1 2 3
2 3 2
3 4 5
3 5 2
4 5 5
5 1 3
5 2 2

输出样例:

6.00

代码模板

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;

const int N = 10010;

int n, m;
int h[N], ne[N], e[N], w[N], idx;
int f[N];
bool st[N];
double dist[N];
int cnt[N];
 
void add(int a, int b, int c)
{
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

//判断正环 
bool spfa(double x)
{
	memset(st, false, sizeof st);
	memset(dist, 0, sizeof dist);
	memset(cnt, 0, sizeof cnt);
	queue<int> q;
	for(int i = 1; i <= n; i ++ )
		q.push(i);
	  
		
	while(q.size())
	{
		int t = q.front();
		q.pop();
		st[t] = false;
		for(int i = h[t]; ~i; i = ne[i])
		{
			int j = e[i];

			double tmp = f[j] - x * w[i];
			if(dist[j] < dist[t] + tmp) //判断正环走最长路
			{
				cnt[j] = cnt[t] + 1;
				if(cnt[j] >= n) return true;				
				dist[j] = dist[t] + tmp;
				if(!st[j])
				{
					q.push(j);
					st[j] = true; 					
				}
			}
		}
	}
	
	return false;
}

int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i ++ ) 
		cin >> f[i];
	
	memset(h, -1, sizeof h);
	for(int i = 0; i < m; i ++ )
	{
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
	}
	
	double l = 0, r = 1000;
	while(r - l >= 1e-4)
	{
		double mid = (l + r) /2;
		if(spfa(mid)) l = mid;
		else r = mid;
	}
	
	printf("%.2lf\n", l);
	
	return 0;
}

 

1165. 单词环(01分数规划)

在这里插入图片描述
思路:
01分数规划

\(\frac{\sum{}w[i]}{\sum{}1} > M\) - > \(\sum{}w[i] - M > 0\)
问题转换为图中是否存在正环
1.建图以每个单词的前两个字母和后两个字母为点,以单词为边,边长为权值。点为676个,边1e5
2.优化在用SPFA求正环的过程中,可以采取一种比较取巧的方法:当求最长路时,经过的点大于某一个数时,我们就可以武断地认为当前图中存在一个正环.也可以将队列换成栈也是一种优化方式。
3.建点的方式----->TLE

输入样例:

3
intercommunicational
alkylbenzenesulfonate
tetraiodophenolphthalein
0

输出样例:

21.66

代码模板

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 700, M = 100010;

int n;
int h[N], e[M], w[M], ne[M], idx;
double dist[N];
int q[N], cnt[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
//判断正环
bool check(double mid)
{
    memset(st, 0, sizeof st);
    memset(cnt, 0, sizeof cnt);

    int hh = 0, tt = 0;
    for (int i = 0; i < 676; i ++ )
    {
        q[tt ++ ] = i;
        st[i] = true;
    }

    int count = 0;
    while (hh != tt)
    {
        int t = q[hh ++ ];
        if (hh == N) hh = 0;  //循环队列
        st[t] = false;

        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] < dist[t] + w[i] - mid)
            {
                dist[j] = dist[t] + w[i] - mid;
                cnt[j] = cnt[t] + 1;
                if ( ++ count > 10000) return true; // 经验上的trick
                if (cnt[j] >= N) return true;
                if (!st[j])
                {
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

int main()
{
    char str[1010];
    while (scanf("%d", &n), n)
    {
        memset(h, -1, sizeof h);
        idx = 0;
        for (int i = 0; i < n; i ++ )
        {
            scanf("%s", str);
            int len = strlen(str);
            if (len >= 2)
            {
                int left = (str[0] - 'a') * 26 + str[1] - 'a';
                int right = (str[len - 2] - 'a') * 26 + str[len - 1] - 'a';
                add(left, right, len);
            }
        }

        if (!check(0)) puts("No solution");
        else
        {
            double l = 0, r = 1000;
            while (r - l > 1e-4)
            {
                double mid = (l + r) / 2;
                if (check(mid)) l = mid;
                else r = mid;
            }

            printf("%lf\n", r);
        }
    }

    return 0;
}

 

posted @ 2022-03-22 14:55  panse·  阅读(15)  评论(0)    收藏  举报