Codeforces Round #466 (Div. 2)

所有的题目都可以在CodeForces上查看

中间看起来有很多场比赛我没有写了
其实是因为有题目没改完
因为我不想改,所以就没有写了(大部分题目还是改完了的)
我还是觉得如果是打了的比赛就一场一场写比较好
要不然以后就写有难度的、比较好的题目??


这场比赛时间真心良心(只是没吃饭)
状态也很好,考场上把 ABCDE 切了
F 是自己弃疗了。。。

不管啦不管啦,进入正题了

题解

A.Points on the line

翻译:

给定一个长度为n的数组
问最少删去几个数之后使得最大值减最小值的结果小于等于 d

题解

考虑n的范围很小
排序之后枚举每一段区间,计算答案即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int a[200],n,D;
int main()
{
    n=read();D=read();
    for(int i=1;i<=n;++i)a[i]=read();
    sort(&a[1],&a[n+1]);
    int ans=0;
    for(int i=n;i;--i)
    {
        ans++;
        for(int j=1;j+i-1<=n;++j)
            if(a[j+i-1]-a[j]<=D)
            {
                cout<<ans-1<<endl;
                return 0;
            }
    }

}

B.Our Tanya is Crying Out Loud

翻译

给定 n,k,A,B ,
一开始 x=n ,
每次可以花费 A 的代价
使得 x=x1 ;
每次可以花费 B 的代价
使得x=x/k(前提是 x%k=0
问使得 x 变成0的最小代价

题解

每次贪心的考虑把 x 变成[xk]的代价
看看是一个个减更优还是先减再除更优

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
ll n,K,A,B;
int main()
{
    n=read();K=read();A=read();B=read();
    ll ans=0;
    if(n<K){ans=A*(n-1);cout<<ans<<endl;return 0;}
    if(K==1){ans=A*(n-1);cout<<ans<<endl;return 0;}
    while(n!=1)
    {
        if(n<K)
        {
            ans+=A*(n-1);
            cout<<ans<<endl;
            return 0;
        }
        ll gg=n/K;
        ll t1=(n-gg)*A;
        ll t2=(n%K)*A+B;
        ans+=min(t1,t2);
        n=gg;
    }
    cout<<ans<<endl;
    return 0;
}

C.Phone Numbers

翻译

给定一个长度为 n 的串S
要求只能用 n 中包含的字符构建一个长度为k的字符串 T
使得T的字典序最小,并且 S 的字典序小于T

题解

如果 k>n 直接把 S 复制一遍
后面填字典序最小的字符就行了
否则的话,把两个字符串从首位开始对齐
T的末尾开始填
只要 S 的当前位置不是字典序最大的字符
就填上比它大的字符
然后前面直接复制

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
int n,k;
char ch[120000];
int ans[120000];
int g[120000];
int main()
{
    cin>>n>>k;
    scanf("%s",ch+1);
    for(int i=1;i<=n;++i)g[i]=ch[i]-96;
    sort(&g[1],&g[n+1]);
    int t=unique(&g[1],&g[n+1])-g-1;
    if(k>n)
    {
        for(int i=1;i<=n;++i)putchar(ch[i]);
        for(int i=n+1;i<=k;++i)putchar(g[1]+96);
        puts("");
        return 0;
    }
    bool fl=false;
    for(int i=k;i;--i)
    {
        int c=ch[i]-96;
        if(fl){ans[i]=c;continue;}
        if(c==g[t])ans[i]=g[1];
        else
        {
            ans[i]=g[lower_bound(&g[1],&g[t+1],c)-g+1];
            fl=true;
        }
    }
    for(int i=1;i<=k;++i)putchar(ans[i]+96);
    puts("");
    return 0;
}

D.Alena And The Heater

翻译

很难翻译呀,配着英文看还是挺好的

给定你一个数列A
以及让你构造的 01 B
满足以下条件:
b[1]=b[2]=b[3]=b[4]=0
b[i]=1 ,当满足:

ai,ai1,ai2,ai3,ai4>r and bi1=bi2=bi3=bi4=1

b[i]=0 ,当满足:
ai,ai1,ai2,ai3,ai4<l and bi1=bi2=bi3=bi4=0

如果都不满足上面的两种情况
那么 b[i]=b[i1]
保证题目有解
输出能够构造出 B l,r

题解

这题的关键在哪里?
保证题目有解
所以每次只需要找到 01 变化的地方
相应的对 l/r 取一个 max/min
很简单吧

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 120000
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int n,a[MAX],b[MAX],bb[MAX];
char ch[MAX];
int p[MAX],pp[MAX];
int q[MAX];
int main()
{
    n=read();
    for(int i=1;i<=n;++i)a[i]=read();
    scanf("%s",ch+1);
    for(int i=1;i<=n;++i)bb[i]=ch[i]-48;
    for(int i=5;i<=n;++i)
    {
        p[i]=min(a[i],a[i-1]);
        p[i]=min(p[i],a[i-2]);
        p[i]=min(p[i],a[i-3]);
        p[i]=min(p[i],a[i-4]);
    }
    for(int i=5;i<=n;++i)
    {
        q[i]=max(a[i],a[i-1]);
        q[i]=max(q[i],a[i-2]);
        q[i]=max(q[i],a[i-3]);
        q[i]=max(q[i],a[i-4]);
    }

    for(int i=4;i<=n;++i)
    {
        if(b[i]==b[i-1]&&b[i]==b[i-2]&&b[i]==b[i-3])
            pp[i]=b[i];
        else pp[i]=2;
    }

    int l=-1e9,r=1e9,now=0;
    for(int i=5;i<=n;++i)
    {
        if(bb[i]!=bb[i-1])
        {
            if(bb[i]==1)l=max(l,q[i]+1);
            else r=min(r,p[i]-1);
        }
    }
    cout<<l<<' '<<r<<endl;
    return 0;
}

E. Cashback

翻译

给定长度为 n 的序列a,以及一个 k
你可以把他划分成若干段
设某一段的长度为len
那么,这一段就会删去 [lenk] 个最小的数
求出这个序列能够划分出的最小和

题解

O(n2) dp 是很显然的

f[i]=min(f[j]+Calc(j+1,i))

Calc 貌似要用主席树?,
复杂度还要套个 log

但是,我们真的有必要这么做吗?
仔细观察分段
如果我们分出一个长度为 2k 的段
似乎不会比分成两段长度为 k 的更优
因为两段k中是分别删去最小值
而一段 2k 中删去的是最小的两个值

所以,我们就可以知道分段的时候长度分成 k
而不是更多的连续在一起的值
那么,分成k+p(p<k)有没有意义呢?
数字越多,最小值是单调递减的
那么,分成这样的一组不会比分出一段 k 更优
而分出一段p
等价于分出 p 1

所以,其实我们的转移没有必要 O(n) 来做
只需要考虑是分出一段 k
还是分出一个1就行了
每次分出一段 k 就要删去一个数
所以要查找区间最小值
ST表就行了
时间复杂度 O(nlog)
我因为一开始写了主席树,索性用主席树算的答案
复杂度不变, O(nlog)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 120000
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int n,a[MAX],rt[MAX],S[MAX],tt;
ll s[MAX];
ll f[MAX];
int tot,c;
struct Node
{
    int ls,rs;
    ll sum;
    int size;
}t[MAX<<5];
void Modify(int &now,int ff,int l,int r,int p,int w)
{
    now=++tot;
    t[now]=t[ff];t[now].size++;t[now].sum+=1ll*S[p]*w;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(p<=mid)Modify(t[now].ls,t[ff].ls,l,mid,p,w);
    else Modify(t[now].rs,t[ff].rs,mid+1,r,p,w);
}
ll Query(int r1,int r2,int l,int r,int K)
{
    if(l==r){return K*S[l];}
    int mid=(l+r)>>1;
    int ss=t[t[r2].ls].size-t[t[r1].ls].size;
    if(K<=ss)return Query(t[r1].ls,t[r2].ls,l,mid,K);
    else return t[t[r2].ls].sum-t[t[r1].ls].sum+Query(t[r1].rs,t[r2].rs,mid+1,r,K-ss);
}
int main()
{
    n=read();c=read();
    for(int i=1;i<=n;++i)S[++tt]=a[i]=read();
    for(int i=1;i<=n;++i)s[i]=s[i-1]+a[i];
    sort(&S[1],&S[n+1]);
    tt=unique(&S[1],&S[tt+1])-S-1;
    for(int i=1;i<=n;++i)
    {
        int p=lower_bound(&S[1],&S[tt+1],a[i])-S;
        Modify(rt[i],rt[i-1],1,tt,p,1);
    }
    for(int i=1;i<c;++i)f[i]=s[i];
    for(int i=c;i<=n;++i)
    {
        f[i]=f[i-1]+a[i];
        ll gg=s[i]-s[i-c]-Query(rt[i-c],rt[i],1,tt,1);
        f[i]=min(f[i],gg+f[i-c]);
    }
    cout<<f[n]<<endl;
    return 0;
}

F. Machine Learning

翻译

给定一个长度为 n 的序列a
两种操作
1.询问 [l,r] 区间内,所有数字出现次数的 mex
2.将 p 位置修改为x

其中 mex 表示的是最小的没有出现过的正整数

题解

很明显的莫队,带修改莫队
离线读入
离散之后维护每个数字出现的次数
其实没有必要对这个值进行类似于直接求 mex 的分块
因为出现次数在最坏情况下是 1,2,3....
这个的和是平方级别的
所以 mex 一定是根号级别的

这样的话复杂就是对的了

但是细节很多
否则时间挂烂(比如我调了1个多小时)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 200000
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar(); 
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
struct Event
{
    int id;
    int l,r,blk;
    int lst;
}Q[MAX],P[MAX];
int ans[MAX];
int S[MAX<<1],ss,n,m,a[MAX],b[MAX],blk=3000,tot,q;
int sum[MAX],Sum[MAX];
int Blk[MAX];
bool cmp(Event a,Event b){
    if(a.blk!=b.blk) return a.blk<b.blk;
    if((a.r-1)/blk!=(b.r-1)/blk)return (a.r-1)/blk<(b.r-1)/blk;
    return a.lst<b.lst;
}
void Change(int num,int w)
{
    Sum[sum[num]]--;
    if(Sum[sum[num]]==0)Blk[(Sum[sum[num]]-1)/500]--;
    sum[num]+=w;
    Sum[sum[num]]++;
    if(Sum[sum[num]]==1)Blk[(Sum[sum[num]]-1)/500]++;
}
int GetAns()
{
    for(int i=0;;++i)
    {
        if(Blk[i]==blk)continue;
        for(int j=1;j<=blk;++j)
            if(!Sum[i*blk+j])return i*blk+j;
    }
}
void Modify(int st,int now,int l,int r)
{
    if(st==now)return;
    if(now<st)
        for(int i=now+1;i<=st;++i)
        {
            a[P[i].l]=P[i].r;
            if(P[i].l>=l&&P[i].l<=r)
            {
                Change(P[i].lst,-1);
                Change(P[i].r,1);
            }
        }
    else 
        for(int i=now;i>st;--i)
        {
            a[P[i].l]=P[i].lst;
            if(P[i].l>=l&&P[i].l<=r)
            {
                Change(P[i].r,-1);
                Change(P[i].lst,1);
            }
        }
}
void Work(int pos,int w)
{
    Change(a[pos],w);
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;++i)b[i]=a[i]=read(),S[++ss]=a[i];
    for(int i=1;i<=m;++i)
    {
        int opt=read(),l=read(),r=read();
        if(opt==1){Q[++q]=(Event){q,l,r,(l-1)/blk,tot};}
        else
        {
            P[++tot]=(Event){tot,l,r,0,b[l]};
            b[l]=r;
            S[++ss]=r;
        }
    }

    sort(&S[1],&S[ss+1]);
    ss=unique(&S[1],&S[ss+1])-S-1;
    for(int i=1;i<=n;++i)a[i]=lower_bound(&S[1],&S[ss+1],a[i])-S;
    for(int i=1;i<=tot;++i)
    {
        P[i].r=lower_bound(&S[1],&S[ss+1],P[i].r)-S;
        P[i].lst=lower_bound(&S[1],&S[ss+1],P[i].lst)-S;
    }
    sort(&Q[1],&Q[q+1],cmp);

    int md=0,l=1,r=0;
    for(int i=1;i<=q;++i)
    {
        while(r<Q[i].r)Work(++r,1);
        while(l>Q[i].l)Work(--l,1);
        while(r>Q[i].r)Work(r--,-1);
        while(l<Q[i].l)Work(l++,-1);
        Modify(Q[i].lst,md,l,r);
        md=Q[i].lst;
        ans[Q[i].id]=GetAns();
    }
    for(int i=1;i<=q;++i)
        printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值