【题目大意】
有一棵n个点的完全二叉树,边权均为1,每个点有小鸟容量c[i]
依次来了m只小鸟,第i只小鸟初始位置在pos[i]上,问来了x只小鸟的时候,怎样安排小鸟的路线可以使得小鸟移动的边权和最小,且每个点的小鸟个数不超过小鸟容量。
n,m<=3*10^5
【题解】
一眼看过去费用流
有两种方法
1. 我们对每次来小鸟都新建二分图,S->小鸟容量剩余的点,小鸟容量不够的点->T,互相连边即可。
复杂度O(n^3 + n * MinCostFlow)
2. 我们对于整棵树建一张图,每次相当于多连了一条边,跑一边spfa增广即可。
复杂度O(n * MinCostFlow)
考场写第一种啊。。qwq
那么考虑第二种,我们模拟spfa的增广过程,显然是选一条最短路来增广。
由于完全二叉树,树高log,我们可以枚举这只小鸟要迁到的点和pos的LCA,然后顺便维护子树内到这个点的最小值即可。
每次选出一条路,就把这条路上模拟退流、流边、流反向边等等操作
然后就能过了,由于完全二叉树,操作都是log的。
总复杂度O(nlogn)

# include <stdio.h> # include <string.h> # include <iostream> # include <algorithm> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef long double ld; # define RG register # define ST static const int M = 3e5 + 10; const int mod = 998244353, inf = 1e9; int n, m, d[M], id[M], f[M][2], c[M]; # define ls (x<<1) # define rs (x<<1|1) inline void gs(int x) { d[x] = inf; if(c[x]) d[x] = 0, id[x] = x; if(ls <= n && d[ls] + (f[ls][0] ? -1 : 1) < d[x]) d[x] = d[ls] + (f[ls][0] ? -1 : 1), id[x] = id[ls]; if(rs <= n && d[rs] + (f[rs][0] ? -1 : 1) < d[x]) d[x] = d[rs] + (f[rs][0] ? -1 : 1), id[x] = id[rs]; } int main() { // freopen("C.in", "r", stdin); // freopen("C.out", "w", stdout); cin >> n >> m; for (int i=1; i<=n; ++i) scanf("%d", &c[i]); for (int i=n; i; --i) gs(i); ll ans = 0; for (int i=1, x; i<=m; ++i) { scanf("%d", &x); int cnt = 0, pmi = inf, pid = 0; for (int par=x; par; par>>=1) { if(d[par] + cnt < pmi) pmi = d[par] + cnt, pid = par; cnt += (f[par][1] ? -1 : 1); } ans += pmi; // cout << "pid = " << pid << " pmi = " << pmi << ", id = " << id[pid] << endl; c[id[pid]] --; for (int y=id[pid]; y!=pid; y>>=1) if(f[y][0]) --f[y][0]; else ++f[y][1]; for (int y=x; y!=pid; y>>=1) if(f[y][1]) --f[y][1]; else ++f[y][0]; for (int y=id[pid]; y!=pid; y>>=1) gs(y); for (int y=x; y!=pid; y>>=1) gs(y); for (int y=pid; y; y>>=1) gs(y); printf("%lld ", ans); } return 0; }