代码很长但大部分是模板,只要有个倍增求路径中权值最大点是要手写的,思路清晰的话很好写
题目链接:F. Drivers Dissatisfaction
题目大意
无向图有n个节点,m条边,每条边有
wi
w
i
(边的权值),
ci
c
i
(让
wi
w
i
减少1需要花费
ci)
c
i
)
两个属性
现在有预算S,可以用这些钱让一些边的权值减小(边的权值可以减少到0甚至负数)
设计一个方案,减小某些边的权值,使得这张图的最小生成树的权重和最小
输出最小生成树的权值和,以及最小生成树的每条边及其权值
思路
很明显,最终的树如果要最小,那么肯定将所有预算S花在让这棵树所有边中 ci c i 最小的边上,这样权重减少的最多,所以我们可以不用考虑将S花在多条边上的情况,因为最优结果是S都花在一条边上的
因为只改变一条边的权值,所以最后的最小生成树和没有改变之前的最小生成树可能是:
1. 所有边的相同,但是某条边权值减小
2. 原来有一条边被去掉,添加了另一条边(这条边的权值减小了)
所以我们可以先求出原始图的最小生成树,然后遍历每一条边,改变这条边,计算新的最小生成树的权值,求出最小值
但如果每次都求一次最小生成树的话,复杂度是
O(mnlogm)
O
(
m
n
l
o
g
m
)
太大了
因为每次只改变一条边,所以我们可以利用最开始的生成树的权值快速的求出新的生成树的权值
考虑两种情况
sum为原图最小生成树的权值
边为最小生成树上的边
可以直接求出,新的权值 = sum−Sci s u m − S c i边不是原来最小生成树的边
设新边i两端点为u,v,将边(u,v)加入最小生成树后,将出现一个环,将环上权值最大的边删除
设root=LCA(u, v),要删除的就是路径path(root, u) 和 path(root,v)中权值最大的点
新的权值 = sum+wi−Sci−wmax,wmax为path(root,u)path(root,v)中最大权值 s u m + w i − S c i − w m a x , w m a x 为 p a t h ( r o o t , u ) p a t h ( r o o t , v ) 中 最 大 权 值
求LCA复杂度是 O(logn) O ( l o g n ) ,但查找path(root,u) 和 path(root, v)时,由于路径长度最坏为m,复杂度为 O(n) O ( n ) ,还是太高
所以我们在求LCA时使用倍增法,求par[k][v](节点v向上爬2^k步到达的点)
时,顺便求一个MAX[k][v](节点v向上爬2^k步,途中遇到的最大权值)
,这样就可以用倍增法 O(logn) O ( l o g n ) 地求出path(u, v)中最大权值
记录得到最小的新生成树权值减小的那条边i,讲边i的权值减小,在求一次最小生成树,就是答案
树链剖分
与上面类似,但利用树链剖分+RMQ快速求出路径中最大权值
代码
GNU C++17 Accepted 608 ms 78200 KB
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxv = 2e5 + 100, maxlog = 20, inf = 0x3f3f3f3f;
struct edge
{
int from, to, w, c, roadid;
edge(int from = 0, int to = 0, int w = 0, int c = 0, int roadid = 0)
{
this->from = from;
this->to = to;
this->w = w;
this->c = c;
this->roadid = roadid;
}
};
int n, m;
vector<edge> G[maxv];
int w[maxv], c[maxv];
void addEdge(int u, int v, int w, int c, int r)
{
G[u].push_back(edge(u, v, w, c, r));
G[v].push_back(edge(v, u, w, c, r));
}
int s;
//union find set******************************
int fa[maxv], rk[maxv];
void Init(int n)
{
for (int i = 1; i <= n; ++i)
{
fa[i] = i;
rk[i] = 0;
}
}
int find(int x)
{
if (fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void unite(int x, int y)
{
x = find(x);
y = find(y);
if (x == y) return;
if (rk[x] < rk[y]) fa[x] = y;
else
{
fa[y] = x;
if (rk[x] == rk[y]) rk[x]++;
}
}
bool same(int x, int y)
{
return find(x) == find(y);
}
//union find set******************************
//MST kruskal******************************
//求生成树是使用kruskal,可以更方便的记录哪些边在最小生成树上而不是只求一个总权值,用prim也可以但麻烦一些
edge es[maxv];
vector<edge> nG[maxv];
bool used[maxv];
bool cmp(const edge&e1, const edge&e2)
{
return e1.w < e2.w;
}
//第一次求最小生成树时我们将得到的最小生成树放到一个新图nG中, 方便之后的操作
//第二次则不需要建一个新图
//所以第一次flag=1是将生成树放到nG,第二次flag=0时不用,节约一点内存
ll kruskal(bool flag)
{
sort(es, es + m, cmp);
Init(n);
ll res = 0;
for (int i = 0; i < m; ++i)
{
edge &e = es[i];
if (!same(e.from, e.to))
{
unite(e.from, e.to);
int u = e.from, v = e.to, w = e.w, c = e.c, r = e.roadid;
if(flag)
{
nG[u].push_back(edge(u, v, w, c, r));
nG[v].push_back(edge(v, u, w, c, r));
}
used[i] = 1;
res += e.w;
}
}
return res;
}
//MST kruskal******************************
//LCA based on binary search******************************
int root, par[maxlog][maxv], mx[maxlog][maxv];
int depth[maxv];
void dfs(int v, int p, int d)
{
par[0][v] = p;
depth[v] = d;
for(auto &ite : nG[v])
{
if(ite.to!=p)
{
dfs(ite.to, v, d+1);
mx[0][ite.to] = ite.w;
}
}
}
void init(int V)
{
dfs(root, -1, 0);
for(int k=0; k+1<maxlog; ++k)
{
for(int v=1; v<=V; ++v)
{
if(par[k][v] < 0)
{
par[k+1][v] = -1;
mx[k+1][v] = mx[k][v];
}
else
{
par[k+1][v] = par[k][par[k][v]];
mx[k+1][v] = max(mx[k][v], mx[k][par[k][v]]);
}
}
}
}
int query(int u, int v)//find the maximum w in path(u, v), depth[u]<depth[v]
{
int ret = 0;
for(int k=0; k<maxlog; ++k)
{
if(((depth[v]-depth[u]) >> k) & 1)
{
ret = max(ret, mx[k][v]);
v = par[k][v];
}
}
return ret;
}
int lca(int u, int v)
{
if(depth[u] > depth[v]) swap(u, v);
for(int k=0; k<maxlog; ++k)
{
if(((depth[v]-depth[u]) >> k) & 1) v = par[k][v];
}
if(u == v) return u;
for(int k=maxlog-1; k>=0; --k)
{
if(par[k][u] != par[k][v])
{
u = par[k][u];
v = par[k][v];
}
}
return par[0][u];
}
//LCA based on binary search******************************
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; ++i) scanf("%d", w + i);
for (int i = 0; i < m; ++i) scanf("%d", c + i);
for (int i = 0; i < m; ++i)
{
int u, v;
scanf("%d%d", &u, &v);
addEdge(u, v, w[i], c[i], i+1);
es[i] = edge(u, v, w[i], c[i], i+1);
}
scanf("%d", &s);
ll sum = kruskal(1);
root = 1;
init(n);
ll ans = sum;
int rid = -1;//road index
for(int i=0; i<m; ++i)
{
if(used[i])
{
if(sum-s/es[i].c < ans)
{
ans = sum-s/es[i].c;
rid = i;
}
}
else
{
int root = lca(es[i].from, es[i].to);
int t = max(query(root, es[i].from), query(root, es[i].to));
if(sum+es[i].w-t-s/es[i].c < ans)
{
ans = sum+es[i].w-t-s/es[i].c;
rid = i;
}
}
}
cout << ans << endl;
if(rid == -1)
{
for(int i=0; i<m; ++i)
{
if(used[i]) printf("%d %d\n", es[i].roadid, es[i].w);
}
return 0;
}
es[rid].w -= s/es[rid].c;
memset(used, 0, sizeof(used));
kruskal(0);
for(int i=0; i<m; ++i)
{
if(used[i]) printf("%d %d\n", es[i].roadid, es[i].w);
}
return 0;
}
树链剖分
GNU C++17 Accepted 655 ms 55500 KB
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxv = 2e5 + 100, maxlog = 20, inf = 0x3f3f3f3f;
struct edge
{
int from, to, w, c, roadid;
edge(int from = 0, int to = 0, int w = 0, int c = 0, int roadid = 0)
{
this->from = from;
this->to = to;
this->w = w;
this->c = c;
this->roadid = roadid;
}
};
int n, m;
vector<edge> G[maxv];
int w[maxv], c[maxv];
void addEdge(int u, int v, int w, int c, int r)
{
G[u].push_back(edge(u, v, w, c, r));
G[v].push_back(edge(v, u, w, c, r));
}
int s;
int fa[maxv], rk[maxv];
void Init(int n)
{
for (int i = 1; i <= n; ++i)
{
fa[i] = i;
rk[i] = 0;
}
}
int find(int x)
{
if (fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void unite(int x, int y)
{
x = find(x);
y = find(y);
if (x == y) return;
if (rk[x] < rk[y]) fa[x] = y;
else
{
fa[y] = x;
if (rk[x] == rk[y]) rk[x]++;
}
}
bool same(int x, int y)
{
return find(x) == find(y);
}
edge es[maxv];
vector<edge> nG[maxv];
bool used[maxv];
bool cmp(const edge&e1, const edge&e2)
{
return e1.w < e2.w;
}
ll kruskal(bool flag)
{
sort(es + 1, es + m + 1, cmp);
Init(n);
ll res = 0;
for (int i = 1; i <= m; ++i)
{
edge &e = es[i];
if (!same(e.from, e.to))
{
unite(e.from, e.to);
int u = e.from, v = e.to, w = e.w, c = e.c, r = e.roadid;
if (flag)
{
nG[u].push_back(edge(u, v, w, c, r));
nG[v].push_back(edge(v, u, w, c, r));
}
used[i] = 1;
res += e.w;
}
}
return res;
}
int mx[maxv << 2];
int cost[maxv];
#define ls l, m, rt<<1
#define rs m+1, r, rt<<1|1
void pushUp(int rt)
{
mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);
}
void build(int l, int r, int rt)
{
if (l == r)
{
mx[rt] = cost[l];
return;
}
int m = (l + r) / 2;
build(ls);
build(rs);
pushUp(rt);
}
void update(int p, int v, int l, int r, int rt)
{
if (l == r)
{
mx[rt] = v;
return;
}
int m = (l + r) / 2;
if (p <= m) update(p, v, ls);
else update(p, v, rs);
pushUp(rt);
}
int query(int L, int R, int l, int r, int rt)
{
if (L <= l && r <= R) return mx[rt];
if (l > R || r < L) return -inf;
int m = (l + r) / 2;
return max(query(L, R, ls), query(L, R, rs));
}
int siz[maxv];
int top[maxv];
int son[maxv];
int dep[maxv];
int faz[maxv];
int id[maxv];
int rid[maxv];
int dfs_clocks;
void init(int n)
{
fill(son, son + n + 1, -1);
dfs_clocks = 1;
}
void dfs1(int u, int fa, int depth)
{
dep[u] = depth;
faz[u] = fa;
siz[u] = 1;
for (edge& e : nG[u])
{
int v = e.to;
if (v == fa) continue;
dfs1(v, u, depth + 1);
siz[u] += siz[v];
if (son[u] == -1 || siz[v] > siz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int tp)
{
top[u] = tp;
id[u] = dfs_clocks;
rid[dfs_clocks++] = u;
if (son[u] == -1) return;
dfs2(son[u], tp);
for (edge& e : nG[u])
{
int v = e.to;
if (v != son[u] && v != faz[u]) dfs2(v, v);
}
}
int query_path(int x, int y)
{
int ret = -inf;
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x, y);
ret = max(ret, query(id[top[x]], id[x], 1, n, 1));
x = faz[top[x]];
}
if (x != y)
{
if (id[x] > id[y]) swap(x, y);
ret = max(ret, query(id[x] + 1, id[y], 1, n, 1));
}
return ret;
}
int main()
{
scanf("%d%d", &n, &m);
init(n);
for (int i = 1; i <= m; ++i) scanf("%d", w + i);
for (int i = 1; i <= m; ++i) scanf("%d", c + i);
for (int i = 1; i <= m; ++i)
{
int u, v;
scanf("%d%d", &u, &v);
addEdge(u, v, w[i], c[i], i);
es[i] = edge(u, v, w[i], c[i], i);
}
scanf("%d", &s);
ll sum = kruskal(1);
dfs1(1, 1, 1);
dfs2(1, 1);
for (int i = 1; i <= n; ++i)
{
for (edge &e : nG[i])
{
int x = dep[e.from] > dep[e.to] ?
e.from : e.to;
cost[id[x]] = e.w;
}
}
build(1, n, 1);
ll ans = sum;
int rid = -1;
for (int i = 1; i <= m; ++i)
{
if (used[i])
{
if (sum - s / es[i].c < ans)
{
ans = sum - s / es[i].c;
rid = i;
}
}
else
{
int t = query_path(es[i].from, es[i].to);
if (sum + es[i].w - t - s / es[i].c < ans)
{
ans = sum + es[i].w - t - s / es[i].c;
rid = i;
}
}
}
cout << ans << endl;
if (rid == -1)
{
for (int i = 1; i <= m; ++i)
{
if (used[i]) printf("%d %d\n", es[i].roadid, es[i].w);
}
return 0;
}
es[rid].w -= s / es[rid].c;
memset(used, 0, sizeof(used));
kruskal(0);
for (int i = 1; i <= m; ++i)
{
if (used[i]) printf("%d %d\n", es[i].roadid, es[i].w);
}
return 0;
}