wqs 二分

1.1 wqs 二分简介

现在有一个函数 f(x) 是凸的,需要求出其在 x=x0 处的点值。

我们先假设它是一个下凸壳。
我们考虑二分斜率 k,用一条斜率为 k 的直线去切这个凸包,也即求 minxf(x)kx 以及取到这个点的 x

image

如上图,发现取到该点的 x 是比 x0 大的,于是减小二分的斜率 k,直到找到 x0

我们把我们要求的东西变成了 logVminxf(x)kx。这个式子在有些情况下比较好求:

考虑这样的问题:有 n 个物品,求恰好选择 x0 个的情况下的最小代价。如果我们二分的时候将每一个物品的代价减少 k,再求一个最优解,那么就可以得到 minxf(x)kx。可能加上 x0 的限制之后不好做,但是去掉之后就很水,这时候可以用 wqs 二分。

wqs 二分就是这样的过程。在上面的问题中,凸性就是 ansiansi1ansi+1ansi 一定成立。这个条件满足的场景就是例如你选择两个物品,如果从 ii+1 选择了某一个物品那么不如在 i1i 之间选择。

1.2 一些细节

正常情况下,如果 fi 是整数,那么 fifi1i(i1) 是整数,因此二分斜率只需在整数域内二分即可。对于小数运算,可能需要在实数域内二分。实数域内二分,还是写 100 次二分好一些,而不是 l<r3eps。后者容易死循环。

对于三点共线需要认真考虑。我们考虑这样的情形:
image

虽然我们不一定切到 x0,但是我们一定会切到这条直线上的某一个点。这条直线都对应着唯一的截距,所以我们得到 d,x 的时候只需要令答案为 d+midx0(就是这条直线的截距经过 x0 坐标的点)即可,而不是 d+midx

但是你要注意二分到实际斜率的时候程序会切到哪一个点。例如,如果我们钦定相同答案取最小个数的操作方案(也即,取最小的 x 使得经过 x 的某一个线段斜率为 k)那么当你二分到等于直线斜率的 k0 的时候,你会得到点 A。而你二分到 k1 的时候你会得到点 B。因此如果你得到的 xx0,那么你需要把答案更新(赋值)为 d+midx0;否则你不能更新答案。

最后,wqs 二分的条件是 lr 而不是 l<r,否则取不到 k0 处的答案!

  • 如果你的 dp 写挂了导致没法保证贴任何一边,还有一种方法:实数域上二分,如果 k 是小数不和任何一条直线相交,那么一定会贴在某一边上。这样你可以二分到确切的 k0±eps
  • 一般用一个 pair 来存答案和用了几个,贴左边的话要重定义一下 cmax,但是如果你不小心写了个 max,那就寄了

P2619 Tree I

【题意】
有一张无向图,有一些黑边和白边,要求选择一个生成树,使得恰好有 k 个白边的前提下,边权和最小。

【分析】

考虑令 ansi 为有 i 个白边的情况下的边权和。ans 其实是一个下凸壳。

考虑解决 minxf(x)kx 的问题,其实就是给每条白边边权减去 k 之后求一个最小生成树,再取其白边个数作为 x。这个是好做的。

如果我们钦定黑边比白边先选,那么就可以钦定相同答案取最小个数的操作方案。


struct edge {
    int s, t, c; 
}e[2][100010]; 
int ans; int cnt[2]; int fa[50010]; int n, m, k; 
int get(int x) {if(fa[x] == x) return x; else return fa[x] = get(fa[x]); }
void merge(int x, int y) {x = get(x); y = get(y); fa[x] = y; }
pii getans(int mid) {
    f(i, 0, n) fa[i] = i; 
    int sum = 0, dot = 0;
    for(int l = 1, r = 1; l <= cnt[0] || r <= cnt[1]; ) {
        if(l <= cnt[0] && (r > cnt[1] || e[0][l].c - mid < e[1][r].c)) {
            if(get(e[0][l].s) == get(e[0][l].t)) ;
            else { merge(e[0][l].s, e[0][l].t); sum += e[0][l].c - mid; dot++; }
            l ++; 
        }
        else {
            if(get(e[1][r].s) == get(e[1][r].t)) ;
            else {merge(e[1][r].s, e[1][r].t); sum += e[1][r].c; }
            r ++; 
        }
    }
    return {sum, dot}; 
}
signed main() {
    cin >> n >> m >> k; 
    f(i, 1, m) {
        int s, t, c, col; cin >> s >> t >> c >> col; 
        e[col][++cnt[col]] = {s, t, c};
    }
    auto cmp = [=](edge th, edge op) {return th.c < op.c; };
    f(i, 0, 1) sort(e[i] + 1, e[i] + cnt[i] + 1, cmp);
    int l = -100, r = 100; 
    while(l <= r) {
        int mid = (l + r) >> 1; 
        pii res = getans(mid); 
        int p = res.second, b = res.first;  
        if(p > k) r = mid - 1;
        else if(p == k) { ans = b + mid * k;break; }
        else {l = mid + 1;  ans = b + mid * k;}
    }
    cout << ans << endl; 
}

1.3 优化 dp

CF1832F. Zombies

image
image
image
image

posted @   OIer某罗  阅读(84)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示