Chgtaxihe's blog Chgtaxihe's blog
首页
练习
算法学习
读书笔记
小玩意

Chgtaxihe

首页
练习
算法学习
读书笔记
小玩意
  • 数论

  • 动态规划

  • 暴力

  • 图论

  • 数据结构

  • 字符串

  • 几何

  • 博弈论

  • 其他

    • 01分数规划学习笔记
    • CDQ分治学习笔记
    • wqs二分学习笔记
      • 注意事项
      • 例题 洛谷P2619
      • New Year and Handle Change (Codeforces 1279F)
    • 一两句话小技巧
    • 单调性应用学习笔记
    • 哈希函数(Hash)函数的设计
    • 带权并查集处理线段
    • 环检测算法学习笔记
    • 约瑟夫环学习笔记
    • 组合情况
    • 高效位运算

wqs二分学习笔记

# 注意事项

要注意的是,要使用wqs二分,则代价函数cost(x)cost(x)cost(x)(选xxx个/操作xxx次...)必须是一个凸包,即cost′(x)cost'(x)cost′(x)单调。(大多数情况下没法严格证明,只能感性猜测或打表)

另外,二分出来的结果不一定正好选择/操作了kkk(题目给定值)次,有几种可能:

  1. 二分不对,比如斜率可能为小数,二分时却只考虑了小数
  2. 记g(x)=bias⋅x+cost(x)g(x)=bias\cdot x+cost(x)g(x)=bias⋅x+cost(x),此时g′(x)=g′(k)g'(x)=g'(k)g′(x)=g′(k)。不影响,直接按kkk次算即可。

另外,可以将biasbiasbias视作"惩罚",有助于理解biasbiasbias的作用。

# 例题 洛谷P2619

/*
    2019-08-21:
         参考:https://www.cnblogs.com/CreeperLKF/p/9045491.htmla
*/
#include <bits/stdc++.h>
#define ll long long
#define Android ios::sync_with_stdio(false), cin.tie(NULL)
using namespace std;

const int maxe = 1e5 + 1000;
const int maxv = 5e4 + 1000;

int bias = 0;
int V, E, need, edge_cnt;
struct edge{
    int u, v, cost, color;
    bool operator <(const edge & e)const{
        // 不能再这里搞事情,应当直接比较
        // 因为当 mid = k -> cnt < need && mid = k+1 -> cnt > need 时,结果是错误的
        // if((color?cost:(cost+bias)) == (e.color?e.cost:(e.cost+bias)))
        //     return color < e.color;
        // return (color?cost:(cost+bias)) < (e.color?e.cost:(e.cost+bias));
        if(cost == e.cost) return color < e.color;
        return cost < e.cost;
    }
}edges[maxe];

struct union_set{
    int u[maxv];

    void clear(int n){for(int i=0; i<=n; i++) u[i] = i;}
    int fa(int nd){return u[nd]==nd?nd:u[nd]=fa(u[nd]);}
    void comb(int a, int b){u[a] = b;}
} uns;

int ans;

int check(int b){
    bias = b;
    for(int i=0;i<E;i++) if(!edges[i].color) edges[i].cost += b;
    sort(edges, edges + E);

    ans = 0;
    uns.clear(V);
    int cnt = 0, disjoint_cnt = V - 1;
    for(int i=0;i<E && disjoint_cnt;i++){
        int a = uns.fa(edges[i].u), b = uns.fa(edges[i].v);
        if(a == b) continue;
        ans += edges[i].cost;
        uns.comb(a, b);
        disjoint_cnt--;
        if(!edges[i].color) cnt++;
    }

    for(int i=0;i<E;i++) if(!edges[i].color) edges[i].cost -= b;
    return cnt;
}

void solve(){
    cin >> V >> E >> need;
    int u, v, cos, col;
    for(int i=0;i<E;i++){
        cin >> u >> v >> cos >> col;
        edges[i] = {u, v, cos, col};
    }

    int l = -120, r = 120, ret = -1;
    while(l <= r){
        int mid = (l + r) / 2; // 相当于斜率
        if(check(mid) >= need){
            ret = ans - need * mid;
            l = mid +1;
        }else{
            r = mid -1;
        }
    }
    cout << ret << endl;
}

signed main(){
    Android;
    solve();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# New Year and Handle Change (Codeforces 1279F)

一道好题,比赛的时候抄上面的模板过了。赛后仔细思考一下,想明白了几个之前不懂的地方。

dpdpdp转移很简单,计算小写字母个数时,dp[i]=min(dp[i−1]+islower(i),dp[i−l]+bias)dp[i]=min(dp[i-1]+islower(i), dp[i-l]+bias)dp[i]=min(dp[i−1]+islower(i),dp[i−l]+bias)。对大写字母同理。

重点在于wqs二分的部分,由于题目的特殊性,wqs二分后,操作数不一定为kkk。

令二分biasbiasbias得到的结果为retretret,但此时操作次数cnt[n]cnt[n]cnt[n]不为kkk(必有cnt[n]≤kcnt[n]\le kcnt[n]≤k),下面将简要说明ans=dp[n]−k∗biasans=dp[n]-k*biasans=dp[n]−k∗bias。

记二分得到最终结果为biasbiasbias,对应的操作数cnt[n]cnt[n]cnt[n]为k′k'k′,令g(k′)=bias⋅k′+op(k′)g(k')=bias\cdot k'+op(k')g(k′)=bias⋅k′+op(k′),其中op(i)op(i)op(i)为操作iii次时的最优答案(本题中,我们要求的便是op(k)op(k)op(k))。显然g(k′)=bias⋅k+op(k)g(k')=bias\cdot k+op(k)g(k′)=bias⋅k+op(k)(否则,二分的最终结果不可能为k′k'k′)。因此op(k)=g(k′)−bias⋅kop(k)=g(k')-bias\cdot kop(k)=g(k′)−bias⋅k(而非g(k′)−bias⋅k′g(k')-bias\cdot k'g(k′)−bias⋅k′)。

此处的g(k′)g(k')g(k′)即为代码中的dp[n]dp[n]dp[n]

#include <bits/stdc++.h>
#define ll long long
#define Android ios::sync_with_stdio(false), cin.tie(NULL)
using namespace std;

const int maxn = 1e6 + 1000;

int n, k, l;
char buffer[maxn];
ll dp[maxn], cnt[maxn];

int ck(int p, int pick){
    if(pick == 0) return buffer[p] <= 'Z' && buffer[p] >= 'A';
    return !(buffer[p] <= 'Z' && buffer[p] >= 'A');
}

ll check(ll bias, int pick = 0){
    for(int i=1; i<=n; i++){
        cnt[i] = cnt[i-1];
        dp[i] = dp[i-1] + ck(i, pick);
        if(i < l){
            if(dp[i] > bias){
                dp[i] = bias;
                cnt[i] = 1;
            }
        }else{
            if(dp[i] > dp[i - l] + bias){
                dp[i] = dp[i - l] + bias;
                cnt[i] = cnt[i-l] + 1;
            }
        }
    }
    return cnt[n];
}

void solve(){
    cin >> n >> k >> l;
    cin >> (buffer + 1);
    ll ans = n;

    for(int pic=0; pic<2; pic++){
        ll l = 0, r = n + 10, mid, ret = r;
        while(l <= r){
            mid = (l + r) >> 1;
            if(check(mid, pic) <= k){
                ret = mid;
                r = mid - 1;
            }else{
                l = mid + 1;
            }
        }
        check(ret, pic);
        ans = min(ans, dp[n] - k * ret);
    }
    cout << ans << '\n';
}

signed main(){
    Android;
    solve();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
上次更新: 2021/02/24, 03:37:30
CDQ分治学习笔记
一两句话小技巧

← CDQ分治学习笔记 一两句话小技巧→

最近更新
01
深入理解Java虚拟机
03-04
02
DNS工作原理
02-27
03
改用VuePress啦
02-23
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式