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;
}

浙公网安备 33010602011771号