首页 > 其他分享 >洛谷 P8978 「DTOI-4」中位数

洛谷 P8978 「DTOI-4」中位数

时间:2023-05-13 23:35:54浏览次数:53  
标签:P8978 cnt 洛谷 dl tot DTOI 操作 include 转移

首先考虑二分答案,把原序列变成01序列。

那么问题就相当于转换成判断能否在 \(k\) 次操作内,将序列变成全 \(1\)。

由于每次操作一定可以做到把 \(1\) 的个数\(n\)变成 \(n'=2n-1\)。因此可以得知操作一定是 \(\log n\) 级别的。

但是这个问题仍然不太好做,很多贪心都是假的。考虑最暴力的区间DP。

由于多选 \(1\) 一定不劣,所以操作区间一定互相不交。
因为如果要相交,一定比包含关系劣。

设 \(f[i][l][r]\) 表示使用 \(i\) 步能否将 \([l,r]\) 变成全 \(1\)。

考虑转移,最简单的思路就是枚举最右的区间:\(f[i][l][r]|=f[x][l][k]\&f[y][k+1][r],x+y=i\)。

特殊做一下 \(f[i][l'][r']|=f[i-1][l][r]\)。

但是分析 \(f[x][l][k]\) 和 \(f[y][k+1][r]\) 这两个状态,假设 \(k-l+1<=r-k\)。

那么显然 \(f[y][k+1][r]\) 是可以转移到 \(f[y+1][l'][r],(l'\le l)\),这样操作更优秀。

所以以此类推,不如将 \(x\) 次操作全部给 \(f[y][k+1][r]\)。

那么如此,就可以贪心地分析得到每次转移只会从一个区间转移而来。

转移式就直接变成了 \(f[i][l'][r']|=f[i-1][l][r]\)。

也就是说,如果将操作区间建树,那么就是一条链。形成包含关系。

这样就可以改进状态为,设 \(f[i][r]\) 表示用 \(i\) 次操作,只考虑 \([1,r]\),最小的 \(l\),使得 \([l,r]\) 可以全变为 \(1\)。

这个就属于线性DP 了。

转移式:\(f[i][r]=\min(P[r][u][f[i-1][u]]),lim[r]\le u\le i\)。

这里转移式的思路是找到上一层操作区间的右端点,左端点。

可以计算出以\(r\)为操作区间右端点最远的左端点 \(l=P[r][u][f[i-1][u]]\)。

具体可以自行推得,并不困难。

这里 \(P[r][u][f[i-1][u]]\) 的用意就是表示这个 \(l\) 与 \(r\),\(u\),\(f[i-1][u]\) 相关。同理 \(lim[r]\) 表示仅与 \(r\) 相关。(这里都忽视了 \(i\) 这个状态)

考虑使用单调队列优化转移,因为发现将越多的 \(0\) 变成 \(1\) 的决策点越优。这个可以由 \(l\) 的表达式看出。

而 \(u\) 是否合法则与 \(f[i-1][u]\) 相关,并非随 \(u\) 递增。
因此在入队时需要注意判断。

至于 \(lim[r]\),是与前缀 \(1\) 的个数和 \(0\) 的个数差相关。

因为这个差的变化为 \(1\) 或 \(-1\),\(lim[r]\) 的变化量最多为 \(1\)。所以可以暴力扫单调队列的队头。

具体这部分的详细细节可以看代码或看出题人题解。

那么这题就做完了,时间复杂度为 \(O(n\log^2 n)\)。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<map>
#include<vector>
#include<queue>
#include<set>
#include<stack>
#include<bitset>
#include<random>
#include<unordered_map>
#include<deque>
#include<cassert>
#include<chrono>
using namespace std;
#define re int
inline int read(){
    int x=0,ff=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')ff=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^'0');c=getchar();}
    return x*ff;
}
int n,k,a[400005],c[400005],s[400005],ss[400005],f[400005],g[400005],p[400005];
int dl[400005],tot,cnt,b[800005];
bool ok(int xx){
    for(re i=1;i<=n;i++){
        if(a[i]>=xx)c[i]=1;
        else c[i]=0;
        s[i]=s[i-1]+c[i];
        ss[i]=ss[i-1]+(c[i]?1:-1);
        //cout<<c[i]<<" ";
    }//puts("");
    if(s[n]<2)return 0;
    memset(b,0x3f,sizeof(b));
    for(re i=n;i;i--)b[ss[i]+n]=i;
    b[n]=0;
    for(re i=1-n;i<=n;i++)b[i+n]=min(b[i+n],b[i+n-1]);
    f[0]=1;
    for(re i=1;i<=n;i++){
        if(c[i])f[i]=f[i-1];
        else f[i]=i+1;
    }
    if(f[n]==1)return 1;
    for(re i=1;i<=21&&i<=k;i++){
        for(re j=1;j<=n;j++)g[j]=f[j],p[j]=j-g[j]+1-(s[j]-s[g[j]-1]);
        tot=1;cnt=0;
        for(re j=1;j<=n;j++){
            if(f[j]<=j){
                while(cnt&&p[j]>=p[dl[cnt]])cnt--;
                if(!cnt||(-s[g[j]-1]+p[j])*2+g[j]>=(-s[g[dl[cnt]]-1]+p[dl[cnt]])*2+g[dl[cnt]])dl[++cnt]=j;
                tot=min(tot,cnt);
            }
            while(tot<=cnt&&(s[j]-s[g[dl[tot]]-1]+p[dl[tot]])*2<=(j-g[dl[tot]]+1))tot++;
            if(tot<=cnt){
                while(tot&&(s[j]-s[g[dl[tot]]-1]+p[dl[tot]])*2>(j-g[dl[tot]]+1))tot--;
                tot++;
            }
            if(f[j]>j)f[j]=b[ss[j]-1+n]+1;
            if(tot<=cnt){
                f[j]=min(b[ss[j]+p[dl[tot]]*2-1+n]+1,f[j]);
                //if(f[j]>g[dl[tot]])f[j]=1;
            }
            f[j]=min(f[j],g[j]);
        }
        if(f[n]==1)return 1;
    }
    return 0;
}
signed main(){
    n=read();k=read();
    for(re i=1;i<=n;i++)a[i]=read();
    if(n==1){cout<<a[1]<<endl;return 0;}
    int l=0,r=1e9,mid;//cout<<ok(939);return 0;
    while(l<=r){
        mid=(l+r)>>1;
        if(ok(mid))l=mid+1;
        else r=mid-1;
    }
    cout<<l-1<<endl;
    return 0;
}

标签:P8978,cnt,洛谷,dl,tot,DTOI,操作,include,转移
From: https://www.cnblogs.com/sjcx-cl-2023/p/17398505.html

相关文章

  • 洛谷 P7999 [WFOI - 01] 翻转序列(requese)
    洛谷传送门注意到如果\(n\)足够小,可以过\(n^2\)。选\(x=3\)(这样做的好处是能交换两个相邻元素),每次把值为\(i\)的元素挪到\(i\),注意到我们不关心其他元素,所以翻转\([l,r]\)的效果可以看成是交换\(p_l,p_r\)。于是先跳大步,再跳小步。可以过\(n\le100\),拿到50分......
  • 洛谷 P4544 [USACO10NOV]Buying Feed G - 题解
    https://www.luogu.com.cn/problem/P4544感觉是很没意思的DP+DS优化,以前做过几道更难的题:https://codeforces.com/contest/1788/problem/Ehttps://atcoder.jp/contests/abc288/tasks/abc288_f这种题只要是让我写总是能把代码整的伤痕累累(逃第一眼:我艹不就是一个sbDP吗......
  • 机器分配 P2066 洛谷
    #dp#背包求方案#分组背包#字典序#T3机器分配P2066机器分配-洛谷|计算机科学教育新生态(luogu.com.cn)题目描述总公司拥有高效设备M台,准备分给下属的N个分公司。各分公司若获得这些设备,可以为国家提供一定的盈利。问:如何分配这M台设备才能使国家得到的盈利最大?求出......
  • 洛谷 P5488 差分与前缀和
    洛谷传送门看起来很毒瘤,但是推出贡献系数后就是一个朴素的卷积了。首先考虑前缀和。考虑\(j\(j\lei)\)的\(a_j\)贡献到\(i\)的过程,是找到\(j=p_0\lep_1\le\cdots\lep_k=i\)的方案数。令\(x_i=p_i-p_{i-1}\),即求\(x_i\ge0,\sum\limits_{i=1}^kx_i......
  • 洛谷 P3321 [SDOI2015] 序列统计
    洛谷传送门感觉挺综合的一道题。考虑朴素dp,\(\forallx\inS,f_{i+1,jx\bmodm}\getsf_{i,j}\)。复杂度\(O(nm^2)\)。显然可以矩乘优化至\(O(m^3\logn)\),但是不能通过。如果转移式中是加法而不是乘法,那很容易卷积优化。接下来是一个很重要的套路:化乘为加。实数......
  • 洛谷 P8492 - [IOI2022] 无线电信号塔
    想到将最优化问题转化为数点问题的一步了,但是因为转化的姿势不太好导致我的数点不太能用特别简洁的数据结构维护,最后只好看题解(考虑先解决单组询问的问题,对于每个点\(i\),我们找出它左边最近的\(h_l\leh_i-D\)的点\(l\),和它右边最近的\(h_r\leh_i-D\)的点\(r\),然后新建一......
  • 洛谷 P8367 - [LNOI2022] 盒(组合数学)
    设\(a\)数组的前缀和为\(s_i\),\(b\)数组的前缀和为\(t_i\),那么根据模拟费用流或者贪心的思想,每一条边经过的次数即为\(|s_i-t_i|\),因此非常trivial的做法是转换贡献体,枚举每种方案下每条边被经过的次数,然后乘以\(w_i\)求和,具体来说:\[ans=\sum\limits_{i=1}^{n-1}\sum\l......
  • 洛谷 P3369 【模板】普通平衡树
    有旋Treap模板#include<bits/stdc++.h>usingnamespacestd;structNode{ Node*ch[2]; intval,rank; intrep_cnt; intsiz; Node(intval):val(val),rep_cnt(1),siz(1){ ch[0]=ch[1]=nullptr; rank=rand(); } voidupd_siz(){ siz=......
  • 洛谷P4824题解
    题面题意:给出字符串s和t,每次操作将s中出现的第一个t删去,直到不能删为止。求最后的串。|s|<=1e6题解:hash做法。(此题也有kmp和权值线段树做法)因为涉及到删除操作,所以我们要动态的实现这个过程。所以考虑开一个栈来存储当前留下的字符。然后每有一个字符入栈,就拿当前......
  • 洛谷P7469题解
    题面题意:有两个字符串a和b,问b中有多少个本质不同子串可以由a删除若干个字符得到。|a|,|b|<=3000题解:字典树(这个题做法很多,后补)。把字符串b的每个子串打到字典树上。然后因为3000^2*26这个东西比较大,所以不能用nxt[id][26]来存储,于是使用链式前向星存图,这样tri......