zoukankan      html  css  js  c++  java
  • 洛谷 P5896 [IOI2016]aliens

    题目链接

    P5896 [IOI2016]aliens

    题目大意

    在一个 (M imes M) 的正方形网格图上分布着 (N) 个关键点,你需要选择至多 (K) 个两端点在主对角线上的正方形去覆盖它们,在这种情况下,求最少要覆盖多少个方格。

    (1leq N leq 10^5)(1leq Mleq 10^6)(1leq kleq N)

    辅助理解

    思路

    为了方便,我们把点全部翻到主对角线下方(即 (igeq j) 的位置),注意到对于点 (x)(y) ,若 (r_xleq r_y)(c_xgeq c_y),则当 (y) 被覆盖后, (x) 必然在其正方形中,所以 (x) 是没有考虑的价值的,可以按 (r_i) 排序后用栈去重。

    剩下的点组成了一条单调下降的折线,设 (nxt_i) 为第 (i) 个点下面那个点的横坐标,若后来一个正方形要覆盖点 (i) 以下的一块区域,那么只需要盖到 (nxt_i) 的地方即可,同时令 (loc_i) 为第 (i) 个点的纵坐标。设 (f_{i,k}) 为用 (k) 个正方形覆盖前 (i) 个节点最少需要覆盖的方格数,可以得到转移式:

    [f_{i,k} = min_{0leq j<i}{f_{j,k-1}+(loc_i-nxt_j+1)^2-max{0,loc_j-nxt_j+1}^2} ]

    ((i-nxt_j+1)^2) 为新添加正方形的大小,(max{0,j-nxt_j+1}^2) 为此正方形和上一个重合的区域。

    这个平方带有很强的提示性,展开:

    [f_{i,k} = min_{0leq j<i}{-2loc_i(nxt_j-1)+(nxt_j-1)^2+f_{j,k-1}-max{0,loc_j- nxt_j+1}^2}+loc_i^2 ]

    (g_{j,k} = (nxt_j-1)^2+f_{j,k}-max{0,loc_j-nxt_j+1}^2),去掉 (min)

    [f_{i,k} = min_{0leq j<i}{-2loc_i(nxt_j-1)+g_{j,k-1}}+loc_i^2\ g_{j,k-1} = 2loc_i(nxt_j-1)+f_{i,k}-loc_i^2 ]

    由于 (nxt_j-1)(2loc_i) 都是单调递增的,所以可以直接用队列维护凸包,把 (dp) 斜率优化到 (O(NK))

    但是这样还是不够快,我们需要摆脱 (K) 的影响,这里需要使用 (wqs) 二分优化,不会的可以去 这里 看看。

    通过打表(感性理解)可以发现,选 (k) 个正方形时的答案 (ans_k) 是一个关于 (k) 的一个凸性函数,其导数单调递减,直观感受就是当已经选的正方形越多,再给正方形所能做到的优化就越来越小了,这个结论我本身想尝试去证的,但是真的有点难证。。就连官方题解说的都是

    有了这个性质就可以 (wqs) 二分了,我们给添加一个正方形这个操作额外增加一个权值 (C),当 (C = 0) 时,函数的最高点的横坐标 (V(C)=N),当 (C = M^2) 时,(V(C)=1),这是二分的下界和上界,我们二分找到最大的 (C) 使得 (V(C)leq K),则答案即为 (f_{tot,K}-Kcdot C)

    时间复杂度: (O(N(logN+logM)))

    一些细节

    1. 单调栈去点的时候要注意点在同一行上的情况,如果你用 (pair) 排序,你会发现这里不是你想要的顺序,需要判断一下。

    2. 有一个 (wqs) 二分初学者容易搞错的地方,二分中的那个凸包是辅助说明其正确性所为,实际实现时不需要考虑,是隐性的。这里找最高点时正常 (dp) 就可以了,且不需要 (k) 这一维,转移时记录一下用了几个正方形。

    3. 由于不一定能找到最高点恰好是 (K),但是找不到的情况只发生在 (ans_k)(kin[V(C),V(C+1)]) 之间是等差的情况,这时有

      [f_{tot,V(C)}-Ccdot V(C)=ans_{V(C)}=f_{tot,K}-Ccdot K ]

      这是最后一步答案计算的依据,不过要注意的是我们必须找到最小的 ”最大值发生的地方“ 作为 (V(C)),具体点说就是斜率优化中比较斜率淘汰队头时,条件必须是大于,而不是大于等于。

    4. 几乎所有地方都要开long long,本人在二分上下界的地方忘开了。。

    Code

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #include<algorithm>
    #include<queue>
    #include<stack>
    #include<fstream>
    #define rep(i,a,b) for(int i = (a); i <= (b); i++)
    #define per(i,b,a) for(int i = (b); i <= (a); i++)
    #define N 100100
    #define ll long long
    #define PLL pair<ll, ll>
    #define fr first
    #define sc second
    #define Inf 0x3f3f3f3f
    using namespace std;
    inline int read(){
        int s = 0, w = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
        while(ch >= '0' && ch <= '9') s = (s<<3)+(s<<1)+(ch^48), ch = getchar();
        return s*w;
    }
    ll n, m, k, cnt;
    ll nxt[N], g[N], loc[N];
    PLL P[N], f[N];
    void init(){
        rep(i,1,n) if(P[i].fr < P[i].sc) 
            swap(P[i].fr, P[i].sc);
        sort(P+1, P+n+1);
        stack<PLL> s;
        s.push({-1, -1});
        rep(i,1,n){
            while(!s.empty() && s.top().sc >= P[i].sc) s.pop();
            if(s.top().fr != P[i].fr) s.push(P[i]);
        }
        int lst = Inf;
        while(!s.empty()){
            nxt[cnt] = lst;
            lst = s.top().sc, loc[cnt++] = s.top().fr;
            s.pop();
        }
        reverse(nxt, nxt+cnt), reverse(loc, loc+cnt);
    }
    void get_G(int i){
        g[i] = (nxt[i]-1)*(nxt[i]-1) + f[i].fr;
        if(loc[i]-nxt[i]+1 > 0) g[i] -= (loc[i]-nxt[i]+1)*(loc[i]-nxt[i]+1);
    }
    double K(int i, int j){
        return 1.0*(g[i]-g[j])/(nxt[i]-nxt[j]); 
    }
    int cal(ll Cost){
        static int q[N];
        int l = 0, r = 0;
        q[0] = 0, get_G(0);
        rep(i,1,cnt-1){
            ll x = loc[i];
            while(l < r && K(q[l], q[l+1]) < 2*x) l++;
            f[i].fr = g[q[l]]-2*x*(nxt[q[l]]-1)+x*x + Cost, f[i].sc = f[q[l]].sc+1;
            get_G(i);
            while(l < r && K(q[r-1], q[r]) >= K(q[r], i)) r--;
            q[++r] = i;
        }
        return f[cnt-1].sc;
    }  
    ll solve(){
        ll l = 0, r = m*m, mid;
        while(l < r){
            mid = (l+r)>>1;
            if(cal(mid) <= k) r = mid;
            else l = mid+1;
        }
        cal(l);
        return f[cnt-1].fr - l*k;
    }
    ll take_photos(int _n, int _m, int _k, vector<int> r, vector<int> c){
        n = _n, m = _m, k = _k;
        rep(i,0,r.size()-1) P[i+1].fr = r[i], P[i+1].sc = c[i];
        init();
        return solve();
    }
    int main(){
        cin>>n>>m>>k;
        vector<int> c(n), r(n);
        rep(i,0,n-1) c[i] = read(), r[i] = read();
        cout<<take_photos(n, m, k, r, c)<<endl;
        return 0;
    }
    

    一点小经验:犯了 “一些细节” 中第3条中的错误后,代码在随机数据上仍然可以跑的相当优秀,当时拍了 (9000) 多组极限数据都没有拍出错,不过在跑 (Nleq10)(Mleq20) 的特殊性质数据时马上挂了。

  • 相关阅读:
    calendar.getTimeInMillis() 和 System.currentTimeMillis() 的区别
    微信小程序中使用 <web-view> 内嵌 H5 时,登录问题的处理方法
    小程序 TabBar 定制
    webpack 代码优化压缩方法
    react-router v4 按需加载的配置方法
    axios发送post请求,如何提交表单数据?
    react中键盘enter事件处理
    常用证件正则表达式
    react中input自动聚焦问题
    React Router v4 页面传值的三种方法
  • 原文地址:https://www.cnblogs.com/Neal-lee/p/14715497.html
Copyright © 2011-2022 走看看