Grakn Forces 2020A-F题解

Grakn Forces 2020A-F题解
比赛链接:https://codeforces.com/contest/1408

A题
水,简单构造施行

题意为给定三个长度为n的数列a[],b[],c[],其中对于每个i值,a[i],b[i],c[i]的值相互之间不相同。现在你需要构造一个新的长度为n的数列d[],d[i]需要在a[i],b[i],c[i]中选择一个值,并且d[]这个数组相邻之间的数值不相等(包括首尾)。

直接记录一下上一个位置构造了什么什么数字就可以了,除了末尾位置构造的时候还要同时考虑一下和头部的数字不同即可。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int a[107],b[107],c[107];

int32_t main()
{
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n;
        for(int i=0;i<n;i++) cin>>a[i];
        for(int i=0;i<n;i++) cin>>b[i];
        for(int i=0;i<n;i++) cin>>c[i];
        int now=-1;
        for(int i=0;i+1<n;i++)
        {
            if(a[i]!=now) now=a[i];
            else if(b[i]!=now) now=b[i];
            else now=c[i];
            cout<<now<<' ';
        }
        if(a[n-1]!=now&&a[n-1]!=a[0]) now=a[n-1];
        else if(b[n-1]!=now&&b[n-1]!=a[0]) now=b[n-1];
        else now=c[n-1];
        cout<<now<<endl;
    }
}

B题
贪心,构造

题意为给定一个长度为n的单调不下降的只包含非负整数的数列a[],你需要构造m个数列b[],每个b[]数列中不同的数字不能超过k个,a[i]的值要等于这m个b[i]值的和。现在询问你m的最小值。

贪心去构造就是了,我们把问题转化为从a[]中减去b[]的值,我们的目标是把整个a[]都变为0
直接按照a[]数列中原本的数字尽可能去构造,我们第一次最多能构造出k个不同的数字,能把a[]中的k个不同的数字(包括0)变为0,之后的a[]中0会一直存在,我们每次最多能将k-1个不同的数字(不包括0)变为0。
由此可以推得,用cas表示a[]数列中有几个不同值的数字

当k=1的时候,我们只能对原数列清除1个数字(包括原有的0)
如果cas=1,m=1
如果cas>1的话,就是无法把原数列全部清零的情况,输出-1

当k>1的时候,我们的第一次可以清除k个数字,之后每次只能清除k-1个数字
当cas<=k的时候,m=1
当cas>k的时候,m=1+(cas-k)/(k-1)(向上取整)

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int32_t main()
{
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
        int n,k;
        cin>>n>>k;
        int now=-1,cas=0;//cas记录整个数列中有几个不同的数
        for(int i=0;i<n;i++)
        {
            int x;
            cin>>x;
            if(x!=now) cas++;
            now=x;
        }
        if(k==1)
        {
            if(cas>1) cout<<-1<<endl;
            else cout<<1<<endl;
        }
        else
        {
            int ans=1;//第一次可以消除k个不同的数字为0
            if(cas>k) ans+=(cas/*-k+k*/-2)/(k-1);//之后每次消除k-1个不同的数字为0了,此时计算(ans-k)/(k-1)向上取整的结果
            cout<<ans<<endl;
        }
    }
}

C题
施行,简单讨论

题意为在长度为l的一段路上,两辆车分别从左右两个端点出发向对方驶去,初始速度均为1/s,这段路上有n个站点,车子每经过一个站点,速度+1/s,询问你经过多少时间两车相遇。

直接for两遍可以处理出不管相交的情况下,两辆车分别经过每个节点的时间情况。
根据站点和两个端点我们可以把整段路分成n+1段,对于两辆车相交所在位置的那一段的左端点来说,右侧车辆到达那个端点用的时间必定不小于左侧车辆到达那个端点的时间,可以由此寻找出他们相交的那一段的位置,再分类讨论计算一下时间即可,详见代码。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=1e5+7;

double timea[maxn],timeb[maxn];//timea记录左边的汽车开到每个点要用的时间,timeb记录右边汽车的
int dis[maxn];

int32_t main()
{
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n>>dis[n+1];
        for(int i=1;i<=n;i++) cin>>dis[i];
        timea[0]=timeb[n+1]=0;
        for(int i=1;i<=n+1;i++)
        {
            timea[i]=timea[i-1]+(dis[i]-dis[i-1])/(double)i;
        }
        int tar;
        for(tar=n;1;tar--)//tar记录在dis[tar]到dis[tar+1]之间辆车相遇,时间点满足timea[tar]<=timeb[tar]
        {
            timeb[tar]=timeb[tar+1]+(dis[tar+1]-dis[tar])/(double)(n+1-tar);
            if(timeb[tar]>=timea[tar]) break;
        }
        if(timea[tar]>timeb[tar+1])//分两种情况分类讨论计算,步骤分开写了方便理解
        {
            double ans=timea[tar];
            double d=dis[tar+1]-dis[tar]-(timea[tar]-timeb[tar+1])*(n+1-tar);
            ans+=d/(tar+1+n+1-tar);
            cout<<fixed<<setprecision(10)<<ans<<endl;
        }
        else
        {
            double ans=timeb[tar+1];
            double d=dis[tar+1]-dis[tar]-(timeb[tar+1]-timea[tar])*(tar+1);
            ans+=d/(tar+1+n+1-tar);
            cout<<fixed<<setprecision(10)<<ans<<endl;
        }
    }
}

D题
暴力,思维,贪心

题意为n个人m盏灯在一个二维平面上。当且仅当一个人的横坐标和纵坐标均不大于某一盏灯的横纵坐标,这个人会被这盏灯发现。现在你需要使用最少次数的操作,使得所有人都不被任何一盏灯发现。
每次操作你可以使得所有人的位置横坐标+1,或者使得所有人的位置纵坐标+1。
需要输出最少的操作次数。

此题中n和m的范围都较小,只有2000,并且横纵坐标的值也相对较小,只有1e6。常规的暴力和直接二分横(纵)坐标的增加值的思路都很难得到结果。但是我们可以通过在暴力法上增加一定的“范围”来解决这次的问题。

由于n和m的值都较小,因此直接暴力n × \times ×m是可行的,我们讨论每一个人对应每盏灯。
第i个人对于第j盏灯来说,当x的增加值不高于max(0,c[j]-a[i])的时候,那么y的增加值就必须不小于d[j]-b[i]的值。
我们可以用cas[i]在暴力过程中记录x的增加值不超过i的时候y需要增加的最大值,由于此时我们记录的是单点数据,并没有真的对0到c[j]-a[i]这个区间都进行了修改,因此暴力结束之后还需要反向for一遍x增加值,用y记录这一过程中的y需要增加的最大值即可。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=2e3+7;

int n,m;
int a[maxn],b[maxn],c[maxn],d[maxn];
int cas[1000007];//cas[i]记录的是在对x选择增加的值最大为i的情况下,需要对y增加的最小值。注意此处仅对单个灯的情况进行了考虑,并不是完全的

int32_t main()
{
    IOS;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
    for(int i=1;i<=m;i++) cin>>c[i]>>d[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(c[j]>=a[i]) cas[c[j]-a[i]]=max(cas[c[j]-a[i]],d[j]-b[i]+1);//对于每个灯,如果x的移动没有使得第i个人离开了第j盏灯的范围,那就需要在y上进行移动
        }
    }
    int ans=INF,y=0;
    for(int i=1000001;i>=0;i--)//cas[i]记录的值并不完备,对与i=x的情况,i>x的所有情况对应的y上限也应该被满足,因此反向for一遍取一下最小值即可
    {
        y=max(y,cas[i]);
        ans=min(ans,i+y);
    }
    cout<<ans<<endl;
}

E题
模型转换,最大生成树

题意为有n个节点编号为1到n,有m个点集合编号为1到m。每个点集中都包含一些编号在1到n之间的节点,第i个点集中的点两两之间有一条颜色为i的边。在这样构成的图中,现在你可以进行任意次在某个点集中删除掉某一个结点的操作,给定两个数组a[m]和b[n],在第i个点集中删除节点j的操作消耗值为a[i]+b[j],现在询问最少消耗多少值,可以使得最后的图中不存在某个环路中,每条边的颜色都不同。

不得不说300iq出的题都非常精妙…这道题将n个结点和m个颜色都看成另一个新图中的n+m个结点,原图中存在环路每条边的颜色都不相同就被转化为了新图当中存在闭环。原问题就被转化成了记录新图中删除所有边需要的消耗值减去新图中最大生成树的消耗值。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=1e5+7;

ll fa[2*maxn],a[maxn],b[maxn];
ll n,m;

void init()
{
    for(ll i=0;i<n+m;i++) fa[i]=i;
}

ll get(ll x)
{
    return x==fa[x]?x:fa[x]=get(fa[x]);
}

void merge(ll x,ll y)
{
    fa[get(x)]=get(y);
}

struct Node
{
    ll u,v,len;
};

vector<Node>node;

bool cmp(Node a,Node b)
{
    return a.len>b.len;
}

int32_t main()
{
    IOS;
    cin>>m>>n;
    init();
    for(ll i=0;i<m;i++) cin>>a[i];
    for(ll i=0;i<n;i++) cin>>b[i];
    ll ans=0;
    for(ll i=0;i<m;i++)
    {
        ll s;
        cin>>s;
        for(ll j=0;j<s;j++)
        {
            ll x;
            cin>>x;
            node.push_back({x-1,n+i,a[i]+b[x-1]});
            ans+=a[i]+b[x-1];
        }
    }
    sort(node.begin(),node.end(),cmp);
    for(ll i=0;i<node.size();i++)
    {
        if(get(node[i].u)==get(node[i].v)) continue;
        ans-=node[i].len;
        merge(node[i].u,node[i].v);
    }
    cout<<ans<<endl;
}

F题
构造,二分,思维。

题意为给定一个长度为n的初始数列a[],a[i]=i。(n最大值为15000)
现在你每次操作选定两个下标i和j,将a[i]和a[j]同时改变为f(a[i],a[j])的值。f(x,y)函数为一个两个参数的函数,对相同的一对x和y,会返回一个同样的数值。
最多对这个数列进行500000次操作,使得这个数列最多存在两个不同的数字。输出构造方法。

首先显而易见的,两个不同的位置进行操作后值变为相同,得到一个长度为2的值相同的区间。
再之后也容易推得,两个长度均为l的区间,我们把这两个区间的数字一一配对操作l次后可以使得总共的2l区间的数值变成相同的。
利用上面两个结论,我们可以用二分的方式使用nlogn的操作次数将任意的2的整数次幂长度的区间变为相同的数值。

然后问题在于,n的值不一定是2的整数次幂长度,可能会有一段剩余的部分。
这个部分如果要和前面已经构造值相同的部分继续值相同,构造方式非常难想。
但是注意到题目要求是最后数列是可以存在两个不同的数值的…因此我们寻找最小的长度temp值满足temp是2的整数次幂,且2 × \times ×temp>=n。然后我们在整段n的区间的最左侧和最右侧各自对temp长度进行上述的二分构造操作,就必定能使得整个数列变为由最多两个值构成了。
比如长度为7,第一次对1-4区间操作,第二次对4-7区间操作。
这样操作之后就会得到x,x,x,y,y,y,y的最终数列。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

struct Node
{
    int a,b;
};

int n;
vector<Node>node;

void qsort_bushi(int l,int r)//类似快排的递归写法,对长度为2的整数次幂的部分进行区间合并,左边的一半和右边的一半一一对应进行操作
{
    if(l==r) return;
    int mid=(l+r)>>1;
    qsort_bushi(l,mid);
    qsort_bushi(mid+1,r);
    while(l<r) node.push_back({l++,r--});
}

int32_t main()
{
    IOS;
    cin>>n;
    int temp=1;
    while(temp*2<n) temp<<=1;//寻找一个最小的2的整数次幂,其两段区间可以覆盖整个n的长度
    qsort_bushi(1,temp);//进行一次操作后1到temp的区间都变成了相同的数字
    qsort_bushi(n-temp+1,n);//进行一次操作后n-temp+1到n的区间都变成了相同的数字
    cout<<node.size()<<endl;
    for(int i=0;i<node.size();i++) cout<<node[i].a<<' '<<node[i].b<<endl;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值