2021“MINIEYE杯”中国大学生算法设计超级联赛(3)题解

2021“MINIEYE杯”中国大学生算法设计超级联赛(3)题解

6976 Game on Plane

题意:

有 n条直线,Alice每次选1,2,…,n条直线,Bob每次画一条直线,答案是Bob画的直线和Alice选的直线的相交数,现在Alice想要最大化答案,Bob想要最小化答案,问每次选1,2,…,n条直线的答案是多少.

思路:
首先能想到的是Alice肯定要选彼此不平行的直线,Bob肯定要选平行最多的直线画一条和它们斜率相同的直线,那么也就不难想到Alice的选法最优策略就是最小化斜率出现次数的最大值,所以不断从每种斜率的直线中各选一种,一定是在各个斜率循环跑,具体实现过程就是将各个斜率按直线数排序,从最大的开始跑即可。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int>p;
p a[100010];
int i,j,k;
int f[100010];
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int main(){
    int t;
    scanf("%d",&t);
    int dx,dy;
    int x1,x2,y1,y2;
    while(t--){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++){
            scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
            dx=x1-x2;dy=y1-y2;
            if(dx==0) dy=1;   //如果垂直于坐标轴x y=1;
            else if(dy==0) dx=1;  //如果垂直于坐标轴y x=1;
            else{
                if(dx<0) {dx*=-1;dy*=-1;}  //因为要用x排序,则xy正负同时颠倒,保证了斜率不变 但又可以排序
                int d=gcd(abs(dx),abs(dy));
                dx/=d;dy/=d; }
            a[i]=p(dx,dy); }   //用pair存"斜率"(直接存坐标防止误差)
        sort(a+1,a+1+n);     //根据从小到大排序
        for(i=1;i<=n;i++) f[i]=0;  //初始化记录斜率相同数量的组数
        for(i=1;i<=n;i=j)
        {  //j-i描述斜率相同直线数量
            for(j=i;j<=n&&a[i]==a[j];j++);   //记录了当前斜率相同的有几个
            for(k=1;k<=j-i;k++) f[k]++;    //记录了斜率分布的个数,保存至数组中
        }
        for(i=j=1;i<=n;i++){
            while(f[j]==0) j++;   //j为最多可以平行的边数
            f[j]--;  //--意味该线被选取
            cout<<i-j<<endl;  //j来描述当前相同斜率最多的条数
        }
    } return 0;
}

6979 Photoshop Layers

题意:
给你n个用16进制表示的RGB三元组,q个询问,询问区间[l,r]的三元组和,如果某个三个元组的状态为1,那么直接用该三元组的值覆盖前面的值,如果是2则正常求和.

思路:
可以看出,只要所查询的区间中出现了1模式的图层,那么该区间就可以转换为这一点和右端点之间的查询,最后就可以转换成某一段由1个“1”模式和n个“2”模式相加的情况。
后面输出的时候需要判断一下选择图层的起点和与终点上一个覆盖图层的点的位置关系,如果选择的图层起始点在覆盖之前图层之前,直接输出最终的图层即可,如果在后面,那么就输出最终图层减去起点图层的前一个状态

AC代码

#include<bits/stdc++.h>
#include<cstdio>
using namespace std;
const int maxn=1e5+10;
int R[maxn],G[maxn],B[maxn];
int flag[maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {  int n,q;
        cin>>n>>q;
        flag[0]=0;
        for(int i=1;i<=n;i++)
        {
            int p,x;
            scanf("%d%X",&p,&x);  //用16进制的形式读取x	
            B[i]=x&255; //取后两位给B
            x>>=8;
            G[i]=x&255; //取中间两位给G
            x>>=8; 
            R[i]=x;  //取前两位给R
            if(p==1)
            {
                flag[i]=i;
            }
            else
            {
                flag[i]=flag[i-1];
                B[i]+=B[i-1];
                G[i]+=G[i-1];
                R[i]+=R[i-1];
            }
        }
            for(int i=0;i<q;i++)
            {
                int l,r;
                scanf("%d%d",&l,&r);
                if(l<=flag[r])
                printf("%02X%02X%02X\n",min(R[r],255),min(G[r],255),min(B[r],255));
                else
                printf("%02X%02X%02X\n",min(R[r]-R[l-1],255),min(G[r]-G[l-1],255),min(B[r]-B[l-1],255));
            }
    }return 0;
}

6983 Segment Tree with Pruning

题意:
对区间[1,n]建线段树,返回条件是r−l+1<=k,问建成的线段树有多少节点?

思路:
可以直接模拟建树过程,对区间长度记忆化搜索,因为区间长度相同,其子节点个数也都是相同的.

AC代码1

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
ll n,k;
map<ll,ll>T;
ll build(ll n)
{
    if(T.find(n)!=T.end()) return T[n];
    if(n<=k) return T[n]=1;
    else return T[n]=build(n/2)+build(n-n/2)+1;  //建左右子树
}
int main()
{
    cin>>t;
    while(t--)
    {
        cin>>n>>k;
        T.clear();
        cout<<build(n)<<endl;
    }return 0;
}

AC代码2

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
map<ll,ll>mp;
ll n,k;
ll build(ll l,ll r){
    if(mp.count(r-l))return mp[r-l];
    if(r-l+1<=k) return 1;
    ll mid=(l+r)/2,sum=1;
    sum+=build(l,mid);
    sum+=build(mid+1,r);
    return mp[r-l]=sum;
}
int main(){
    int t;
    cin>>t;
    while(t--){
        mp.clear();
        cin>>n>>k;
        cout<<build(1,n)<<endl;
    }return 0;
}

如果觉得写的还不错,点个赞吧^ - ^

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

稚皓君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值