Codeforces - Gym101911 - A/B/C/D/E/F/G/H/I/J/K (没有L) 题解

写在前面:
这套题本来可以做出更多的,但是由于自己太菜,被cygg队落了六道题
深深的认识到自己的菜。还是要好好学习。
这套题思路都可以,也不算难,就整理一下。

A

题意:工作时间喝咖啡。总共要喝n杯咖啡,且一天中有n个时间点(第几分钟)可以喝咖啡,问你最快几天可以喝完。当然如果只有这些条件,第一天就可以喝完了。但是有限制条件:两次喝咖啡时间必须不小于d,也就是说如果同一天喝两杯咖啡,t2-t1>d才可以,不然第二杯没法喝。
思路:贪心的思想。先把所有可以喝的时间从小到大排序。第一天肯定喝时间最早的那个,然后从第二个可以喝的时间开始遍历,然后和队首相比:
相差时间大于d,那么可以喝,当前的天数就是队首喝咖啡那一天,然后把队首pop掉,当前这个入队(是为了更新这一天最新喝咖啡的时间)
如果相差时间不大于d,那么就不可以喝,所以新开一天,当前这一杯是在cnt+1天喝,这个总的cnt记录开到了第几天。
思路关键是用队列维护更新每一天最晚喝咖啡的时间,如果某一天最晚一次喝的咖啡时间被更新了,那先前一次喝咖啡的时间就pop掉了,所以每次只和队首的时间比即可(已经是那一天的最晚时间,且是后面所有天数里最早的,总之就是最合适的!(你品,你细品)
下面是AC代码

const int maxn=2e5+10;
int n,m,d;
struct node{
    int id,t,cnt;//cnt为天数
    bool operator<(const node &oth)const{
        return t<oth.t;
    }
}a[maxn];
int ans[maxn];
queue<node> q;
int main() {
	scanf("%d%d%d",&n,&m,&d);
    rep(i,1,n){
        sd(a[i].t);
        a[i].id=i;
    }
    sort(a+1,a+n+1);
    int cnt=1;
    a[1].cnt=cnt;
    ans[a[1].id]=a[1].cnt;//最早的第一天用
    node tp=a[1];
    q.push(tp);
    rep(i,2,n){//从第二个开始
        if(a[i].t-q.front().t>d){//若可以塞在今天
            a[i].cnt=q.front().cnt;
            ans[a[i].id]=a[i].cnt;
            q.push(a[i]);
            q.pop();
        }else{
            a[i].cnt=++cnt;
            ans[a[i].id]=a[i].cnt;
            q.push(a[i]);
        }
    }
    printf("%d\n",cnt);
    rep(i,1,n){
        printf("%d",ans[i]);
        putchar(i==n?'\n':' ');
    }
	return 0;
}

B

题意:从飞机上往下跳伞,跳之后水平方向和竖直方向相同速度前进。如果遇到上升气流,就不下降。上升气流分段,问你从哪里跳,平移距离最大。
思路:自己没有思路。。。还是太菜了。题解说是前缀和+二分找答案,前缀和是方便求区间和,二分是upperbound 找之后第一个满足条件的区间。
即,我们出发肯定是从某个上升气流的起点开始的。所以我们这样划分区间:
求出与n个上升气流区间相对应的间隔区间。每个间隔区间是对应的上升气流前面那个(编号相同)。还有第n+1个间隔区间是最后一个气流之后,是无限远。
所以我们从1~n枚举所有间隔区间,其实就是枚举起点,然后二分查找后面所有间隔区间第一个大于等于b[i]+h的坐标(第一个间隔区间长度为0,作为前缀和的时候其实就是找h,其实每个都是找h 找b[i]+h其实是因为前缀和的特质。
那么每次找到的坐标p和当前枚举的坐标i 自己所平移总长度是原高度+气流区间经过长度
因为编号的问题(同编号 间隔区间在气流前面) 可知是a[p-1]-a[i-1]+h
然后看到mmk有个On的方法 看不懂(懒得看。。。) 就用这个nlogn的吧。。。
下面是AC代码

const int maxn=2e5+10;
//问从哪里开始飘的最远
//枚举每一个开始的下降区间即起点
int n,h;
struct node{
	int l,r;
}q[maxn];
int a[maxn],b[maxn];//a是不下降的 前缀和 b是下降的
int main() {
	sdd(n,h);
	rep(i,1,n){
		sdd(q[i].l,q[i].r);
	}
	a[1]=q[1].r-q[1].l;
	b[1]=0;//第一个间隔在第一个不下降前面 因为要枚举前缀和为0的状态
	rep(i,2,n){//处理前缀和
		a[i]=a[i-1]+q[i].r-q[i].l;
		b[i]=b[i-1]+q[i].l-q[i-1].r;
	}
	b[n+1]=inf;//最后一个下降长度为无穷
	int ans=0;
	rep(i,1,n){//遍历起点
		int p=lower_bound(b+1,b+n+2,b[i]+h)-b;
		ans=max(ans,a[p-1]-a[i-1]+h);
	}
	printf("%d\n",ans);
	return 0;
}

C

题意:一堆数,两个相同的可以变成一个两倍。如果只有一个相同的,可以买,问至少需要买几个,如果怎样都不行,就输出-1
思路:优先队列判就行了 如果当前这个和后面的不想等,且当前的两倍大于后面,那么永远不行。
有个坑点!就是cnt是ll类型的,如果赋值-1会出问题。要好好赋值,评测机的内存空间可能很不靠谱。wa了n发。。。

using namespace std;
const int maxn=2e5+10;
int n;
priority_queue<ll,vector<ll>,greater<ll> > q;
int main() {
	sd(n);
    rep(i,1,n) {
        ll tp;
        scanf("%lld",&tp);
        q.push(tp);
    }
    ll cnt=0;
    while(!q.empty()){
        ll tp=q.top();
        q.pop();
        if(q.empty()) break;
        if(tp==q.top()){
            q.push(tp*2);
            q.pop();
        }else{//若不等于堆顶
            if(tp*2>q.top()){
                cnt=(ll)-1;
                break;
            }
            q.push(tp*2);
            cnt++;
        }
    }
    printf("%lld\n",cnt);
	return 0;
}

D

题意:给你一堆数<=1e7, 问你 对于每个数,能不能输出这个数的两数乘积形式,且用过的乘积形式不能用 比如6可以分成 1 6 6 1 2 3 3 2 如果第四次出现6,就不行!
思路:我原来思路是对每个数枚举1~这个数,不过有个rec记录当前枚举到哪了,但是这样枚举还是太多了。
题解思路是:从1枚举到sqrt这个数,而且若不是平方*平方,那设置一个标志记录交换过来也可以用,这样大大降低了复杂度,一个小优化。挺好的。

const int maxn=2e5+10;
int c[maxn];
int a[maxn],b[maxn];
int rec[10000010];//rec[i]记录当前这个值到几了
bool con[10000010];//con[i]表示当前这个能不能用
int main() {
	int n;
    sd(n);
    rep(i,1,n) sd(c[i]);
    bool flag=true;
    rep(i,1,n){
        if(!con[c[i]]){//若当前这个值所对应的记录不能用
            //寻找第一个可以整除c[i]的
            if(rec[c[i]]==0) rec[c[i]]++;//从1开始
            if(rec[c[i]]*rec[c[i]]>c[i]){
                flag=false;
                break;
            }
            while(1){
                if(c[i]%rec[c[i]]) {
                    rec[c[i]]++;
                    if(rec[c[i]]*rec[c[i]]>c[i]){//若超了
                        flag=false;
                        break;
                    }
                }else{
                    a[i]=rec[c[i]];
                    b[i]=c[i]/rec[c[i]];
                    if(a[i]!=b[i]) con[c[i]]=true;
                    else rec[c[i]]++;//如果平方数的话,只能用这一次
                    break;
                }
            }
            if(!flag) break;
        }else{
            b[i]=rec[c[i]];
            a[i]=c[i]/rec[c[i]];
            con[c[i]]=false;
            rec[c[i]]++;
        }
    }
    if(!flag){
        printf("NO\n");
    }else{
        printf("YES\n");
        rep(i,1,n){
            printf("%d %d\n",a[i],b[i]);
        }
    }
	return 0;
}

E

题意:给你一个区间一串数,比如1 2 3 1 2 1 然后给你一系列操作,比如 2 1
然后你要把2之间的都变成2 然后1之间的都变成1
思路:cygg说浩神是模拟的 自己不会,所以用了之前的线段树lazy标记就变成了半个板子题。
注意用双端队列存储某个颜色(3e5个颜色)的左右区间 每次修改看看是否符合条件 然后去队首队尾修改
最后输出用query查询输出 因为很多不会询问到叶子,所以不到nlogn
下面是ac代码

const int maxn=3e5+10;
int n;
int a[maxn];
deque<int> q[maxn];//记录每种颜色还存在于哪些位置
struct node{
    int l,r,c,lazy;//lazy也是颜色
}t[maxn<<2];
void pushup(int k){
    if(t[k<<1].c==t[k<<1|1].c&&t[k<<1].c!=-1)
        t[k].c=t[k<<1].c;
    else t[k].c=-1;//-1代表不止一种颜色
}
void build(int k,int l,int r){
    t[k].l=l,t[k].r=r,t[k].lazy=0;
    if(l==r){
        t[k].c=a[l];
    }else{
        int mid=(l+r)>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
        pushup(k);
    }
    return;
}
void pushdown(int k){
    if(t[k].lazy){
        t[k<<1].c=t[k<<1|1].c=t[k].lazy;
        t[k<<1].lazy=t[k<<1|1].lazy=t[k].lazy;
        t[k].lazy=0;
    }
    return;
}
void update(int k,int l,int r,int c){//将l~r改成c
    if(l<=t[k].l&&t[k].r<=r){
        t[k].c=c;
        if(t[k].l!=t[k].r) t[k].lazy=c;
    }else{
        pushdown(k);
        int mid=(t[k].l+t[k].r)>>1;
        if(l<=mid) update(k<<1,l,r,c);//更新这里的目标区间不变!!!
        if(mid<r) update(k<<1|1,l,r,c);
        pushup(k);
    }
}
int query(int k,int p){//查询位置p的是什么
    if(t[k].l==t[k].r){
        return t[k].c;
    }else{
        if(t[k].c!=-1) return t[k].c;
        if(t[k].lazy) return t[k].lazy;
        pushdown(k);
        int mid=(t[k].l+t[k].r)>>1;
        int ans;
        if(p<=mid) ans=query(k<<1,p);
        else ans=query(k<<1|1,p);
        return ans;
    }
}
int main() {
	sd(n);
    rep(i,1,n){
        sd(a[i]);
        q[a[i]].push_back(i);//放入位置
    }
    build(1,1,n);
    int m,tp;
    sd(m);
    while(m--){
        sd(tp);
        if(q[tp].size()>1){
            while(!q[tp].empty()&&query(1,q[tp].front())!=tp) q[tp].pop_front();
            while(!q[tp].empty()&&query(1,q[tp].back())!=tp) q[tp].pop_back();
            if(q[tp].size()>1){
                int s=q[tp].front(),t=q[tp].back();
                update(1,s,t,tp);
            }
        }
    }
    rep(i,1,n){
        printf("%d%c",query(1,i),i==n?'\n':' ');
    }
	return 0;
}

F

前缀和就行 没什么说的

const int maxn=1e6+10;
int a[maxn];
int b[28][maxn];
inline int get_un(int num){
    return abs(num/100000+num/10000%10+num/1000%10-num/100%10-num/10%10-num%10);
}
int main() {
	rep(i,0,999999){
        a[i]=get_un(i);
        b[a[i]][i]=1;
    }
    rep(i,0,27){
        rep(j,1,999999){
            b[i][j]+=b[i][j-1];
        }
    }
    int t;sd(t);
    while(t--){
        int num;
        char s[10];
        scanf("%s",s);
        num=(s[0]-'0')*100000+(s[1]-'0')*10000+(s[2]-'0')*1000+(s[3]-'0')*100+(s[4]-'0')*10+(s[5]-'0');
        int q=get_un(num);
        int ans=0;
        rep(i,0,q-1){
            ans+=b[i][num-1];
        }
        printf("%d\n",ans);
    }
	return 0;
}

G

题意:构造树,要求满足给出的边 断开之后两边最大的结点是所给的两个
思路:首先其中一个必须是n 不然肯定不行 然后我们造单链。
先将所有结点编号从小到大排列,然后从小的往上加。
如果当前这个编号比前一个大,直接插入n和与n最近的p之间。
如果这个编号和前一个相等,就从比当前这个中找一个更小且没用过的挂在n与最近的p之间
如果找不到就NO

//给你n-1对点 代表两个集合中编号最大的点
const int maxn=1010;
int n;
int a[maxn];//记录与n分成两部分的点们
bool con[maxn];//记录某点有没有用过
int pre[maxn];
int main() {
	sd(n);
    bool flag=true;
    rep(i,1,n-1){
        con[i]=false;
        int x,y;
        sdd(x,y);
        if(y!=n) flag=false;
        else a[i]=x;
    }
    if(!flag){
        printf("NO\n");
    }else{
        sort(a+1,a+n);//排好序了
        //a[1]是最小的
        //还得设置一个变量代表离n最近的点
        int p=a[1];//记住第一次得赋值!
        pre[a[1]]=n;
        con[a[1]]=true;
        rep(i,2,n-1){
            if(a[i]>a[i-1]){
                pre[a[i]]=n;
                pre[p]=a[i];//这里不一定对!!!
                p=a[i];
                con[a[i]]=true;
            }else{
                bool flag=false;
                rep(j,1,a[i]-1){
                    if(!con[j]){
                        flag=true;//找到了
                        pre[p]=j;
                        pre[j]=n;
                        con[j]=true;
                        p=j;
                        break;//注意要break!!
                    }
                }
                if(!flag){
                    printf("NO\n");
                    return 0;
                }
            }
        }
        printf("YES\n");
        pre[n]=-1;
        int t=a[1];
        while(pre[t]!=-1){
            printf("%d %d\n",t,pre[t]);
            t=pre[t];
        }
    }
	return 0;
}

H

题意:铺砖。1x2的砖 只能横着放 如果放不下需要劈开 统计阴影四周 所有成单的列再乘以行数/2即可
三行代码题

int main() {
	int n,m,a,b,c,d;
    sdd(n,m);sdd(a,b);sdd(c,d);
    printf("%d\n",((a-1)*(m&1)+(n-c)*(m&1)+(c-a+1)*((b-1)&1)+(c-a+1)*((m-d)&1)+1)/2);
	return 0;
}

I

水题:刚开始没想明白各种做 就是看这个序列之间少了几个就行了

int n;
int a[1010];
int main() {
	sd(n);
    rep(i,1,n) sd(a[i]);
    sort(a+1,a+n+1);
    printf("%d\n",a[n]-a[1]+1-n);
	return 0;
}

J

题意:电视机和发射源,组合题,画画图能看出来关系

using namespace std;
ll gcd(ll a,ll b){
    if(b==0) return a;
    return gcd(b,a%b);
}
int main() {
	ll a,b,x,y;
    cin>>a>>b>>x>>y;
    ll g=gcd(x,y);
    x/=g;
    y/=g;
    printf("%lld\n",a/x<b/y?a/x:b/y);
	return 0;
}

K

题意:问你把一个序列怎样分才能使每一个序列中位数都不小于k 问最多分成几个这样的序列
思路:首先小于中位数的数量一定不大于n/2,然后要先用这些 剩下的单个成序列即可

const int maxn=5050;
int a[maxn];
int main() {
    int n,m;
    sdd(n,m);
    int cnt=0;
    rep(i,1,n){
        sd(a[i]);
        if(a[i]<m) cnt++;
    }
    int num=0;
    if(n&1){
        if(cnt>=(n+1)/2){
            cnt=0;
        }else{
            int nn=n;
            nn-=(2*cnt+1);
            num++;
            num+=nn;
        }
    }else{
        if(cnt>=n/2){
            cnt=0;
        }else{
            int nn=n;
            nn-=(2*cnt+1);
            num++;
            num+=nn;
        }
    }
    printf("%d\n",num);
	return 0;
}

啊 !好累啊!

发布了120 篇原创文章 · 获赞 12 · 访问量 5271

猜你喜欢

转载自blog.csdn.net/weixin_43735161/article/details/104812040