5972. 【北大2019冬令营模拟12.1】 wang(2s,256MB)
Problem
-
给定一个定义域和值域都在 上的函数 ,且给定一个常数 ,且满足
-
现在给定你 个二元组 ,要求最小化
Data constraint
Solution
-
性质①
证明①
令
则
-
性质②:
-
若
-
则
-
证明②
- 令
- 则
-
然后根据这两个性质,把所有的 按照 取模之后可以分成 组,每一组当中只要一个对应的值确立,整个组的值便可以确立.
-
所以我们只需考虑组与组之间的对应关系。
-
并且根据题意,当 等于某个值 时,只有 与 有关系,所以,很明显只有奇偶性不同的组互相影响.
-
根据性质②,我们得知当 时,对应的 ,换言之,对于所有的 ,若其属于某组 (即 ),那么可以考虑与其奇偶性不同的所有组 ,组 与不同的组 进行影响会产生不同的代价,而我们需要做的,是让所有的奇数组与偶数组进行完美配对.
-
要明白这一点,首先要知道什么叫完美配对.
-
给定一个二分图,如果可以分成两个相等集合 ,并且 中每个点与且仅与 中一个点进行匹配,那么这个二分图就可以完美配对.
-
对于此题而言,因为每个 影响且仅能影响一个对应的 ,并且影响了 之后,既不能影响另一个 ,又不能被另一个 影响,所以这显然是一个二分图,显然是可以进行完美配对的。
-
为了更好地计算出两组 相互影响的代价,观察到实际上 只能变成 的形式,而 也是类似于这个形式,所以我们要计算 的代价,就可以想办法把式子转化成类似于 的形式,事实证明,这很容易做到.
-
考虑两组 ,不妨假设 ,那么如果一个 在对应的 组,我们就令 ,
-
即 .
-
反之,如果 在 组,我们就令 ,因为 在式子里是套上了绝对值的,所以我们可以把它变成 ,
-
即
-
这样一来,所有的 都可以变化成 .
-
我们取中位数,并适当调整,可以很容易确定一个最优的 .
-
这样子便可以计算出两组 进行配对的最优代价,接下来就是如何最小权匹配了.
-
对于这个算法,我们可以直接转化最小费用最大流.
-
最小费用最大流需要注意它是在保证最大流的前提下最小费用的,所以我们可以按照正常思维,从原点向左边一排点连容量为 ,费用为 的边,汇点同样向右边一排点这样连边,之后,对于中间的连边,流量为 ,费用就是匹配代价,之后用 算法跑一遍就可以得到答案了.
-
因为本人太菜,所以这里大致讲一讲费用流的实现.
-
求解费用流一般有两种算法:第一种就是刚刚提到的大名鼎鼎的 算法,也是实战中经常用到的算法,另外一种就是经典的 增广算法.
SPFA增广
- 一句话,每次找一条费用最小的流进行增广,直到不能增广为止.
#define I register int
void link(I x, I y, I z, I l) {
tov[++ tot] = y, len[tot] = z, nex[tot] = las[x], cost[tot] = l, las[x] = tot;
tov[++ tot] = x, len[tot] = 0, nex[tot] = las[y], cost[tot] = -l, las[y] = tot;
}
bool spfa() {
queue<int> q;
mem(d, 0xcf), mem(v, 0), q.push(S), d[S] = 0, v[S] = 1, incf[S] = 1 << 30;
while (q.size()) {
I x = q.front(); v[x] = 0; q.pop();
for (I i = las[x], y = tov[i]; i; i = nex[i], y = tov[i])
if (len[i] && d[y] < d[x] + cost[i]) {
d[y] = d[x] + cost[i], incf[y] = min(incf[x], len[i]), pre[y] = i;
if (!v[y]) v[y] = 1, q.push(y);
}
}
return d[T] > 0;
}
void update() {
for (I x = T; x ^ S;)
len[pre[x]] -= incf[T], len[pre[x] ^ 1] += incf[T], x = tov[pre[x] ^ 1];
maxflow += incf[T], ans += d[T] * incf[T];
}
while (spfa()) update();
ZKW算法
#define ll long long
void link(ll x, ll y, ll l, ll L) {
tv[++ tt] = y, c[tt] = L, nx[tt] = ls[x], ls[x] = tt, len[tt] = l;
tv[++ tt] = x, c[tt] = 0, nx[tt] = ls[y], ls[y] = tt, len[tt] = -l;
} //c[x]表示x这条边的流量,len[x]表示x这条边的费用
bool bfs() { //这里与spfa增广几乎是一模一样的,核心都是找出从原点到所有点的最小费用.
mem(vis, 0), mem(dis, 0x7f), dis[S] = 0, vis[S] = 1;
queue <ll> Q; Q.push(S);
while (!Q.empty()) {
ll k = Q.front(); Q.pop();
for (ll x = ls[k]; x ; x = nx[x])
if (c[x] && dis[tv[x]] > dis[k] + len[x]) {
dis[tv[x]] = dis[k] + len[x]; //更新最小费用
if (!vis[tv[x]])
vis[tv[x]] = 1, Q.push(tv[x]);
}
vis[k] = 0;
}
return dis[T] < 9187201950435737471;
}
ll dfs(ll k, ll flow) {
if (k == T) { vis[T] = 1; return flow; }
ll have = 0; vis[k] = 1;
//这里一定要注意这个vis标记的作用,因为dis[i]表示的是最小费用,而并非普通dinic的距离标号
//所以这是可能存在零环的,因此一定要用个标记来去掉零环带来的影响.
for (ll x = ls[k]; x ; x = nx[x])
if (!vis[tv[x]] && c[x] && dis[tv[x]] == dis[k] + len[x]) {
ll now = dfs(tv[x], min(flow - have, c[x]));
ans += now * len[x], c[x] -= now, c[x ^ 1] += now, have += now;
if (flow == have) return have;
}
return have;
}
int main() {
while (bfs())
for (vis[T] = 1; vis[T]; )
mem(vis, 0), dfs(S, inf);
}
-
实际上 的算法就是借鉴了普通的最大流 算法以及原始 算法。
-
在求出了 ,即最小花费之后每次多路增广,而不仅仅只是增广一条最优的路径(这是 增广),这样可以少进行很多次 ,提高效率.
-
所以综上,也不难发现,在稠密图上 一般是占据优势的,但在极稀疏的图上, 增广一般更优.