ARC杂题

实际上如果你觉得你切了两道题但是没拍的话那就真的会保龄。所以我挂了200。警钟长鸣。

ARC125C

使字典序最小的话,他给了 \(k\) 个我们需要扔掉最后一个不看,要不然可能不优。例子我不会举。
首先去掉最后一个之后显然我们最优的就是让他给的做第一个,然后怎么小怎么来。构造的话考虑每次在他给的数后面放一个目前没有放过的最小的数,最后把所有没放过的倒序放一下。
考场上脑抽了把没放过的放开头了然后光荣保龄。

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n,k,cnt,pos,a[200010];
bool v[200010];
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1,j=1;i<k;i++){
        printf("%d ",a[i]);
        v[a[i]]=true;
        while(v[j])j++;
        if(j<a[i]){
            printf("%d ",j);v[j]=true;
            while(v[j])j++;
        }
    }
    for(int i=n;i>=1;i--)if(!v[i])printf("%d ",i);
    return 0;
}

ARC125D

如果你一个数前面出现了一个相同的数,那么以前面那个数结尾的应该全都不合法。于是考虑设 \(dp[i]\) 为第 \(i\) 个数结尾的答案,那么我们存一个 \(pos[i]\) 表示 \(i\) 的上一次出现的位置,转移就变成了:

  1. 如果没有出现过那么就是之前的所有答案+1
  2. 如果出现过则自己加上 \(pos[i]\)\(i-1\) 的所有答案,顺便把 \(pos[i]\) 位置的清空(因为如果你 \(pos[i]\) 能结尾,那 \(i\) 也是一样的)。不能加之前的,因为之前的被 \(pos[i]\) 统计过了。
    容易发现这是个单点修改区间查询,树状数组维护。
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int mod=998244353;
int n,ans,a[200010],c[200010],pos[200010];
int lowbit(int x){
    return x&-x;
}
void update(int x,int val){
    while(x<=n){
        c[x]=(c[x]+val)%mod;x+=lowbit(x);
    }
}
int query(int x){
    int sum=0;
    while(x){
        sum=(sum+c[x])%mod;x-=lowbit(x);
    }
    return sum;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        if(pos[a[i]]){
            int x=(query(pos[a[i]])-query(pos[a[i]]-1)+mod)%mod;
            update(i,(query(i-1)-query(pos[a[i]]-1)+mod)%mod);
            update(pos[a[i]],mod-x);
        }
        else update(i,query(i-1)+1);
        pos[a[i]]=i;
    }
    printf("%d",query(n));
}

ARC126C

考场上写了个整除分块,本机跑挺快结果T飞了。
我们现在显然能手推出这样一个式子:(设 \(d\) 为公约数)

\[\lfloor \frac {k+\sum_{i=1}^na_i}d\rfloor\ge \sum_{i=1}^n\lceil \frac {a_i}d \rceil \]

这个显然可以整除分块做是不是(
正解考虑开个桶整个前缀和维护 \(a\) ,于是我们找 \(d\) 的时候从大到小找,枚举倍数就行了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#define int long long
using namespace std;
int n,ans,maxx,k,cnt[300010];
signed main(){
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<=n;i++){
        int x;scanf("%lld",&x);
        k+=x;maxx=max(maxx,x);
        cnt[x]++;
    }
    for(int i=maxx-1;i>0;i--)cnt[i]+=cnt[i+1];
    if(k/n>maxx){
        printf("%lld",k/n);return 0;
    }
    for(int i=maxx;i>=1;i--){
        int ret=0;
        for(int j=1;j<=maxx;j+=i)ret+=cnt[j];
        if(k/i>=ret){
            printf("%lld",i);return 0;
        }
    }
    return 0;
}

ARC126D

这个我bdfs结果没什么详细的解释。也可能是我不会bdfs
看见这个 \(k\) 考虑状压。我们设 \(dp[i][j]\) 为扫描到 \(i\) ,找到的 \(1-k\) 的数的集合为 \(j\) 的答案,那么我们有两种转移:

  1. 扫到一个数,如果没在集合里,那么加进来。这时我们需要的步数是集合和这个数组成的逆序对数,也就是 \(j\) 中比 \(a\) 大的个数,也就是 \(j\) 中高于第 \(a\) 位的为 \(1\) 的位的个数。
  2. 把这个集合整个向右平移一位/把所有没放进来的数集体向左平移一位,取个最小值。
    代码经过卡常。


#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
int n,k,dp[1<<16],cnt[1<<16],t[1<<16];
int read(){
    int x=0;char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x;
}
int main(){
    n=read();k=read();
    for(register int j=1;j<(1<<k);++j)cnt[j]=cnt[j>>1]+(j&1),t[j]=min(cnt[j],k-cnt[j]);
    for(register int j=1;j<(1<<k);++j)dp[j]=0x3f3f3f3f;
    for(register int i=1;i<=n;++i){
        int a=read()-1;
        for(register int j=(1<<k)-1;j>=0;--j){
            if(dp[j]!=0x3f3f3f3f){
                if(!(j&(1<<a)))dp[j|(1<<a)]=min(dp[j|(1<<a)],dp[j]+cnt[j>>a]);
                dp[j]+=t[j];
            }
        }
    }
    printf("%d",dp[(1<<k)-1]);
}
posted @ 2022-09-22 15:46  gtm1514  阅读(58)  评论(3编辑  收藏  举报