构造题做

  1. 找出题中特殊限制(前提)

  2. 找出答案的限制(求出的方法?取值范围?性质?若需判断能否构造,条件是什么?...)

  3. 贪心(关键):逐一解决限制

CF1712D Empty Graph

  1. \(n\) 个点的完全图,\(l,r\) 之间的边权为 \(\min_{i=l}^r a_i\)

  2. 由于是完全图,则任意两点都存在边,两点之间的最短路只有两种情况:直接走;通过 \(a_{min}\) 的点走,即 \(d(u,v)=\min(e(u,v),e(u,k_{min})+e(k_{min},v))=\min(\min_{i=l}^ra_i,2\times a_{min})\)

  3. 注意到 \(e(l,r)\ge e(l+1,r)\),这告诉我们只用考虑编号相邻两点即可。于是整个直径就是 \(\max(\min(a_i,a_{i+1},2\times a_{min}))\)。看到 \(max,min\) 相互嵌套果断二分答案。首先解决 \(a_{min}\) 的限制,若 \(2\times a_i<mid\) 显然要把 \(a_i\) 变大。然后解决 \(\min(a_i,a_{i+1})\),如果存在 \(\ge mid\) 的那没事了,若没有,则考虑是否有 \(a_i\ge mid\),有则随便修改一条它相邻的边(点),无则要修改两个点。判断总修改次数是否 \(\le k\) 即可。

点击查看代码
const int N=1e5+10,inf=1e9;

int n,k,a[N],tmp[N];

bool check(int mid){
    int tot=0,qwq=-1;bool flag=0;
    for(int i=1;i<=n;++i){
        if(2*a[i]<mid)tmp[i]=inf,++tot;
        else tmp[i]=a[i];
    }
    for(int i=1;i<n;++i){
        if(min(tmp[i],tmp[i+1])>=mid){flag=1;break;}
        qwq=max(qwq,tmp[i]);
    }qwq=max(qwq,tmp[n]);
    if(flag)return tot<=k;
    return (qwq>=mid?tot+1:tot+2)<=k;
}

void solve(){
    read(n),read(k);
    for(int i=1;i<=n;++i)read(a[i]);
    int l=1,r=1e9,mid,ans;
    while(l<=r){
        mid=(l+r)>>1;
        if(check(mid))ans=mid,l=mid+1;
        else r=mid-1;
    }
    printf("%d\n",ans);
}

int main(){
    int t;read(t);
    while(t--)solve();
    return 0;
}

CF1717D Madoka and The Corruption Scheme

  1. 比赛是二叉树的形式,可以钦定比赛对手,可以钦定赢家,但是某场比赛的赢家会被改变(只有 \(k\) 次)。

  2. 在会被改变的情况下,求能保证的编号最小。

  3. 既然我们能钦定一开始的排列顺序以及谁赢,不如我们把他直接确定下来,

image

红边表示钦定它赢,用 0 表示该边赢,1 表示该边输,则叶子节点都对应一个唯一的 01 串。全为 0 的那位就是最后的赢家。

对于赞助商单次改变结果的操作相当于将某个节点的 1 变成 0。对于一个人,赞助商想要他赢,它的编号不能有多于 \(k\) 个 1。

于是我们可以算出赞助商能钦定的赢家个数 \(cnt\),我们从小到大安排编号,那么最终的答案就是 \(cnt\) 啦。

\(cnt\) 怎么算?编号的二进制位有 \(n\) 位,其中 \(i\) 位是 \(1\) 的人就有 \(\dbinom{n}{i}\) 个,答案就是 \(\sum_{i=0}^{\min(n,k)}\dbinom{n}{i}\)

点击查看代码
const int N=1e5+10,mod=1e9+7;

int n,k;
long long fac[N],ifac[N],ans=0;

long long qpow(long long a,int b){
    long long res=1;
    while(b){
        if(b&1)res=res*a%mod;
        b>>=1;a=a*a%mod;
    }
    return res;
}
long long C(int a,int b){
    return fac[a]*ifac[b]%mod*ifac[a-b]%mod;
}
int main(){
    read(n),read(k);fac[0]=1;
    for(int i=1;i<=n;++i)fac[i]=1ll*i*fac[i-1]%mod;
    ifac[n]=qpow(fac[n],mod-2);
    for(int i=n-1;~i;--i)ifac[i]=1ll*(i+1)*ifac[i+1]%mod;
    for(int i=0;i<=min(n,k);++i)ans=(ans+C(n,i))%mod;
    printf("%lld\n",ans);
    return 0;
}

CF1381A2 Prefix Flip (Hard Version)

  1. 对于一次操作,是将前缀取反后反转。

  2. 最多操作 \(2n\) 次,使 \(a\) 变成 \(b\)

  3. 注意到操作并不会影响到后缀,考虑从后往前逐位确定。对于一位 \(a_i\not= b_i\),则考虑能否通过反转前缀 \([1,i]\) 得到,若 \(a_1\not=b_i\),则先反转第一个数再反转 \([1,i]\)。故最多操作 \(2n\) 次,符合要求。记录一下前缀区间的 \(l,r\) 和反转情况就能很好维护了。

点击查看代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
#define pb push_back

const int N=1e5+10;
int n,a[N],b[N];
vector<int>ans;

void solve(){
    scanf("%d",&n);ans.clear();
    for(int i=1;i<=n;++i)scanf("%1d",&a[i]);
    for(int i=1;i<=n;++i)scanf("%1d",&b[i]);
    int l=1,r=n,rev=0;
    for(int i=n;i;--i){
        if((a[r]^rev)==b[i]){
            l<r?--r:++r;
            continue;
        }
        if((a[l]^rev)==b[i])ans.pb(1);
        ans.pb(i);
        swap(l,r),rev^=1;
        l<r?--r:++r;
    }
    printf("%d ",ans.size());
    for(int i:ans)printf("%d ",i);printf("\n");
}

int main(){
    #ifdef LOCAL
        freopen("std.in","r",stdin);
        freopen("my.out","w",stdout);
    #endif
    int t;scanf("%d",&t);
    while(t--)solve();
    return 0;
}

CF1706D2 Chopping Carrots (Hard Version)

  1. 结果的式子中含有下取整,有 \(\max\)\(\min\)\(1\le p_i\le k\)

  2. \(\max-\min\)最小值

  3. 可以考虑枚举最小值 \(v\in[0,a_1]\)(注意 \(a\) 是递增给出的,\(p_1=1\) 时取得上界 \(a_1\),下界显然是 \(0\)),对于 \(1\le i\le n\) 计算 $\max_{i=1}^n (\left\lfloor\frac{a_i}{p_i}\right\rfloor)-v $ 的最小值,其中 \(p_i\) 尽量大,这可以通过 \(p_i=\left\lfloor\frac{a_i}{v}\right\rfloor\) 得到。时间复杂度是 \(O(n\times a_1)\) 的,只能通过弱化版本。

这个做法的瓶颈就是对于枚举的每一个 \(v\) 都要 \(O(n)\) 算一遍能取到的最大值,考虑如何通过预处理优化这个过程。

\(maxn(v)\) 表示当取 \(v\) 为最小值时,取到的最大值,也就是上面 \(O(n)\) 算的东西。考虑每个 \(a_i\) 对其有什么影响。

注意到 \(\left\lfloor\frac{a_i}{p_i}\right\rfloor\) 的取值只有 \(O(\min(k,\sqrt{a_i}))\) 种(整除分块的结论),记为 \(s_1,s_2,...,s_x\),显然对于 \(s_j<v\le s_{j+1}\)\(maxn(v)\) 至少为 \(s_{j+1}\)。于是我们可以用 \(s_{j+1}\) 去更新 \(maxn(s_j+1)\),最后计算答案的时候扫描一遍就可以了。总共时间复杂度 \(O((\sum_{i=1}^n\min(k,\sqrt{a_i}))+a_1)\)

点击查看代码
int n,k,a[N],maxn[N];

void solve(){
    read(n),read(k);
    memset(maxn,0,sizeof maxn);
    for(int i=1;i<=n;++i){
        read(a[i]);int pre=-1,cur;
        //注意这里枚举取值是递减的,所以是用 pre 更新 cur+1
        for(int j=1;j<=min(a[i],k);j=(a[i]/(a[i]/j))+1){
            cur=a[i]/j;
            maxn[cur+1]=max(maxn[cur+1],pre);
            pre=cur;
        }maxn[0]=max(maxn[0],pre);
    }
    int qwq=0,ans=1e9;
    for(int i=0;i<=a[1];++i){
        qwq=max(qwq,maxn[i]);
        ans=min(ans,qwq-i);
    }printf("%d\n",ans);
}

CF1738D Permutation Addicts

  1. \(b\) 的构造与 \(a\)\(k\) 存在大小关系,且 \(b\) 所代表的值在 \(a\) 前面,或为特殊值。

  2. 给出 \(b\),构造合法的 \(a\)\(k\)

  3. 写的太好了遂搬过来

image

点击查看代码
int n;
vector<int>e[N],ans;

void dfs(int u){
    ans.pb(u);
    for(int v:e[u])if(e[v].empty())ans.pb(v);
    for(int v:e[u])if(!e[v].empty())dfs(v);
}

void solve(){
    read(n);ans.clear();
    for(int i=0;i<=n+1;++i)e[i].clear();
    int k=N;
    for(int i=1,b;i<=n;++i){
        read(b);e[b].pb(i);
        i<b?k=min(k,b-1):k=min(k,i-1);
    }printf("%d\n",k);
    if(!e[0].empty())dfs(0);
    else dfs(n+1);
    for(int i=1;i<=n;++i)printf("%d ",ans[i]);printf("\n");
}

CF1658C Shinju and the Lost Permutation

  1. 排列 \(p\)\(i\) 循环置换表示将后 \(i\) 个数移到最前

  2. 定义 \(i\) 循环置换的权值为 \(\max_{j=1}^{k}p_j\) 的不同元素个数,给出 \(c_i\) 表示 \(i-1\) 循环置换的权值,是否存在排列 \(p\) 满足 \(c\)

  3. 注意到若最大数 \(n\) 在最前面,则 \(c_i=1\),这样的 \(c_i\) 有且仅有一个。事实上 \(i-1\) 循环置换到 \(i\) 只是将最后一个数移到前面,于是我们从这个 \(1\) 的位置开始往后循环判断,可以分讨得出相邻两次置换一定得满足 \(c_i-c_{i-1}\le 1\)

点击查看代码
const int N=1e5+10;

int n,c[N];

void solve(){
    read(n);int flag=0,pos;
    for(int i=1;i<=n;++i){
        read(c[i]);
        if(c[i]==1)pos=i,++flag;
    }
    if(flag>1||!flag)return printf("NO\n"),void();
    for(int i=1;i<=n;++i){
        int nxt=pos+1>n?1:pos+1;
        if(c[nxt]-c[pos]>1){flag=0;break;}
        pos=nxt;
    }
    if(!flag)printf("NO\n");
    else printf("YES\n");
}

int main(){
    #ifdef LOCAL
        freopen("std.in","r",stdin);
        freopen("my.out","w",stdout);
    #endif
    int t;read(t);
    while(t--)solve();
    return 0;
}

CF1691D Max GEQ Sum

1、2. 给出 \(a_i\) 询问对于所有的 \((1\le i<j\le n)\) 都满足 \(\max_{l=i}^j a_l\ge \sum_{l=i}^j a_l\)

  1. 一种很自然的思路就是钦定某个数为最大值,向左右拓展判断最大的和是否小于等于它。向左右拓展可以用双向链表维护,从小到大钦定数之后删掉就可以了。
点击查看代码
const int N=2e5+10;
#define int long long

int n,a[N],sum[N],p[N];
int pre[N],nxt[N];
int lg[N],mn[20][N],mx[20][N];

void build(){
    for(int i=1;i<=lg[n];++i){
        for(int j=1;j+(1<<i)-1<=n;++j){
            mn[i][j]=min(mn[i-1][j],mn[i-1][j+(1<<(i-1))]);
            mx[i][j]=max(mx[i-1][j],mx[i-1][j+(1<<(i-1))]);
        }
    }
}
int query(int l,int mid,int r){
    int p=lg[mid-l+1],q=lg[r-mid+1];
    return max(mx[q][mid],mx[q][r-(1<<q)+1])-min(mn[p][l],mn[p][mid-(1<<p)+1]);
}

void solve(){
    read(n);
    for(int i=1;i<=n;++i){
        read(a[i]);pre[i]=i-1,nxt[i]=i+1,p[i]=i;
        sum[i]=sum[i-1]+a[i],mn[0][i]=sum[i-1],mx[0][i]=sum[i];
    }build();
    sort(p+1,p+n+1,[](const int &x,const int &y){return a[x]<a[y];});
    
    for(int i=1;i<=n;++i){
        int id=p[i];
        if(a[id]<query(pre[id]+1,id,nxt[id]-1))return printf("NO\n"),void();
        pre[nxt[id]]=pre[id];nxt[pre[id]]=nxt[id];
    }printf("YES\n");
}

signed main(){
    for(int i=2;i<N;++i)lg[i]=lg[i>>1]+1;
    int t;read(t);
    while(t--)solve();
    return 0;
}

CF1659D Reverse Sort Sum

  1. 操作 \(i\) 得到的序列就是将 \(a\)\(i\) 个数排序后的序列。\(c_i\) 表示 \(n\) 次操作每列的和。

  2. 给出 \(c_i\)\(a_i\),保证有解。

  3. 一个显然的结论就是 \(a_i\) 里面每个 \(1\)\(\sum c_i\) 都贡献了 \(n\) 次,于是 \(1\) 的个数就是 \(\frac{\sum c_i}{n}\)。由于每次操作都是和前缀有关,所以考虑从后往前推。
    注意到,若序列存在 \(1\),则 \(c_n\) 要么是 \(n\) 要么是 \(1\),这分别对应 \(a_n=1\)\(a_n=0\)。那我们可不可以用类似的结论推出 \(a_i\) 呢?当然是可以的。
    \(a_i=1\),则它一定会在前 \(i\) 次操作内给 \(c_i\) 贡献 \(i\) 次。设当前在位置 \(i\),剩余 \(cnt\)\(1\) 没填。

posted @ 2022-10-20 15:57  RuntimeErr  阅读(28)  评论(0编辑  收藏  举报