CF1853

CF1853A

目标:让序列 \(a\) 有序 --> 无序

发现每个操作只会对序列前缀加减 这只会对 \(a_i\ a_{i-1}\) 之间关系产生影响 相当于让 \(a_i+2\)

扫一遍找两数差值最小除2即可(要求破坏有序 所以+1)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define gc getchar
#define pc putchar
const int N=1e7+5;
const int M=1e8+5;
const int inf=0x3f3f3f3f;
const int mod=998244353;
const double eps=1e-8;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int t,n,a[N],ans;
signed main(){
    t=read();
    while(t--){
        n=read();ans=inf;
        for(int i=1;i<=n;i++)a[i]=read();
        for(int i=1;i<=n-1;i++)ans=min(ans,a[i+1]<a[i]?0:(a[i+1]-a[i])/2+1);
        writel(ans);
    }
    return 0;
}

CF1853B

序列类似斐波那契 \(f_{i+2}=f_{i+1}+f_i\) 可推出 \(f_i=f_{i+2}-f_{i+1}\)

那么在知道第 \(k\) 项是 \(n\) 的情况下 枚举第 \(k-1\) 项即可推出整个序列

要求序列非负 只需看第 \(1\) 项是否非负即可

发现 \(k\) 很大 但 \(k>30\) 时 第 \(k\) 项一定超出 \(n\) 上界

那么判掉 \(k>30\) 情况即可 时间复杂度 \(O(30n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define gc getchar
#define pc putchar
const int N=1e7+5;
const int M=1e8+5;
const int inf=0x3f3f3f3f;
const int mod=998244353;
const double eps=1e-8;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int t,n,k,ans,a[N];
signed main(){
    t=read();
    while(t--){
        n=read();k=read();ans=0;
        if(k>30){puts("0");continue;}
        for(int i=0;i<=n;i++){
            a[k]=n,a[k-1]=i;
            for(int j=k-2;j;j--){
                a[j]=a[j+2]-a[j+1];
                if(a[j]<0||a[j]>a[j+1]){a[1]=-1;break;}
            }
            ans+=(a[1]>=0);
        }
        writel(ans);
    }
    return 0;
}

CF1853C

首先 如果 \(a[1]\neq 1\) 那么第 \(1\) 项一定不会被删掉 直接输出1

考虑其他情况:

首先答案显然具有单调性 二分答案

那么考虑怎么check

我们发现每一次操作 \(mid\) 排名都会减去 \(a\) 中 值比它排名小 的数的数量

(如果这个数被删了 那么排名就一定不是正的)

\(a\) 中 值比它排名小的最大的数 可以二分找

时间复杂度 \(O(k\log nk\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define int ll
#define gc getchar
#define pc putchar
const int N=1e7+5;
const int M=1e8+5;
const int inf=0x3f3f3f3f;
const int mod=998244353;
const double eps=1e-8;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int t,n,k,ans,a[N];
inl bool check(int mid){
    for(int i=1;i<=k;i++){
        int x=upper_bound(a+1,a+n+1,mid)-a-1;
        mid-=x;
    }
    return mid>=1;
}
signed main(){
    t=read();
    while(t--){
        n=read();k=read();ans=0;
        for(int i=1;i<=n;i++)a[i]=read();
        if(a[1]^1){puts("1");continue;}
        int l=2,r=4e10+1;
        while(l<=r){
            int mid=l+r>>1;
            if(check(mid)){
                ans=mid,r=mid-1;
            }else l=mid+1;
        }
        writel(ans);
    }
    return 0;
}

CF1853E

(DF是构造 题解都看不懂/fn/fn/fn 不写了)

前置芝士:P1969 [NOIP2013 提高组] 积木大赛

从这道题我们知道:对于 \(i\)

  • \(a_i\le a_{i-1}\) 可以在搭前一块的时候捎带着把这一块也搭了 可以白嫖

  • \(a_i>a_{i-1}\) 白嫖到上一块的高度 剩下自己搭 \(res+=a_i-a_{i-1}\)

这道题其实也一样:由当前序列减到0 等价于 从0加到当前序列 所以几乎一样(2400的橙题

这题多了一个东西 加到 \(k\) 会变0 相当于对 \(k\) 取模

那对做法有啥影响捏

可以进行如下骚操作

先从前面某块( \(j\) )到 \(i-1\) 块整体全部加 \(k\) (在对 \(k\) 取模意义下前面相当于没变) 而这样就可以把当前第 \(i\) 块带起来了

求花费每次需要把前面全扫一遍 复杂度 \(O(n^2)\) 无法接受

我们会发现如下情况:

  • \(a_j\le a_{j-1}\) 还是一样 可以靠前一块带一部分 花费 \(k-(a_{j-1}-a_j)\)

  • \(a_j>a_{j-1}\) 只能自己加 花费 \(k\) (但自己直接加一定小于 \(k\) 所以一定不优 不用管)

所以我们可以开个堆 维护前面的最小花费 复杂度降到 \(O(n\log n)\)

全部步骤:

  • \(a_i\le a_{i-1}\) 可以在搭前一块的时候捎带着把这一块也搭了 可以白嫖 最后把这一块的花费 \(k-(a_{i-1}-a_i)\) push到堆里

  • \(a_i>a_{i-1}\)
    \((1)\) 白嫖到上一块的高度 剩下自己搭 花费 \(a_i-a_{i-1}\)
    \((2)\) 在前面找一个位置整体加 \(k\) 花费 \(q.top()\)

注意: \(a_i-a_{i-1}\) 即使当前不优 也可能出现下一次单点加比这一次大 这样总体不优

可以把 \(a_i-a_{i-1}\) 也塞到堆里 如果下一次选到它 那么相当于 \(i\) 单点加 下一块再从堆里取 这是反悔贪心的思想

这玩意只能选一次 必须pop

而对于 \((2)\) 操作 每个位置最多只会用一次 用第二次答案一定劣(第二次没法白嫖前一块 答案变 \(k\) ) 一起pop出去即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define int ll
#define gc getchar
#define pc putchar
const int N=1e7+5;
const int M=1e8+5;
const int inf=0x3f3f3f3f;
const int mod=998244353;
const double eps=1e-8;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int t,n,k,res,a[N];
signed main(){
    t=read();
    while(t--){
        priority_queue<int,vector<int>,greater<int>>q;
        n=read();k=read();q.push(k);res=0;
        for(int i=1;i<=n;i++)a[i]=read()%k;
        for(int i=1;i<=n;i++){
            if(a[i]<=a[i-1]){q.push(k-(a[i-1]-a[i]));continue;}
            q.push(a[i]-a[i-1]);
            res+=q.top();q.pop();
        }
        writel(res);
    }
    return 0;
}
posted @ 2023-10-24 20:31  xiang_xiang  阅读(521)  评论(0编辑  收藏  举报