ZAFU_2021_2_17_2021寒假个人赛第四场题解

A题
原题链接:https://codeforces.com/problemset/problem/1296/A
相关tag:简单思维

我们每次操作可以把数组中的一个数变为另一个数,那么如果这两个数同为奇数或者同为偶数的话,操作后是不会对整个数组总和的奇偶性造成影响的。只有奇数变为偶数或者偶数变为奇数的时候,才会对整个数组总和的奇偶性造成影响。

那么我们可以统计出原数组中奇数的个数记为num1,偶数的个数记为num2。
整个数组总和的奇偶性是由奇数的个数决定的,当num1为奇数的时候,整个数组的总和一开始就是奇数,无需更改。
如果num1为偶数,此时整个数组的总和为偶数,就需要让某一个奇数变为偶数,或者一个偶数变为奇数,那么就要求原数组中奇数和偶数都存在。也就是num1和num2均不为0,才能通过操作变换使得整个数组的总和从偶数变为奇数。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;

int main()
{
    IOS
    int t;cin>>t;
    while(t--)
    {
        int n;cin>>n;
        int num1=0,num2=0;
        while(n--)
        {
            int x;cin>>x;
            if(x&1) num1++;//判断x是奇数还是偶数
            else num2++;
        }
        if(num1&1) cout<<"YES"<<endl;//如果num1是奇数,原数组中奇数出现了奇数次,数组总和为奇数,无需操作已经满足
        else if(num1&&num2) cout<<"YES"<<endl;//num1是偶数,原数组总和为偶数,需要奇数和偶数均存在才能改变总和的奇偶性
        else cout<<"NO"<<endl;
    }
}

B题
原题链接:https://codeforces.com/problemset/problem/1296/B
相关tag:暴力,模拟,贪心
当然你也可以选择推公式,都可以,这里给出暴力做法。

我们贪心策略,当前手上个位数上的钱都先放着不用,其余的钱都拿去买,拿了返利后,重复之前的操作,直到钱的数量小于10无法得到返利为止。

容易证得,最多两次上述操作,就可以使得我们手上剩余钱的位数-1,因此暴力计算时间完全足够。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;

int main()
{
    IOS
    int t;cin>>t;
    while(t--)
    {
        int n;cin>>n;
        int ans=0;
        while(n>9)//当还可以得到返利时就不断重复把除了个位的钱都花掉,贪心去拿返利
        {
            ans+=n/10*10;
            n=n%10+n/10;
        }
        cout<<ans+n<<endl;
    }
}

C题
原题链接:https://codeforces.com/problemset/problem/670/C
相关tag:离散化,《算法竞赛进阶指南》第33页例题原题。

当然也可以用map来做,这里还是更希望能去学习一下离散化。
注意到这道题数据虽然只有2e5个,但是数值非常大,最大有1e9,我们是开不了1e9大小的数组来直接记录每个数出现了几次的。
但是数组也就2e5个,加上下面安排中的数字,总共也就最多6e5种不同的数字。可以通过离散化把这些数字映射到1-6e5这6e5个整数里。

离散化具体操作去看书或者博客去学吧。这里给出stl的实现和数组实现两种方案。

stl实现的离散化代码:

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;

vector<int>origin;//用于离散化

int find(int x)//在离散化数组中查找当前x所在的位置,这里也可以自己手写个二分
{
    return (int)(lower_bound(origin.begin(),origin.end(),x)-origin.begin());
}

int num[maxn];//记录m个数字
int cas[maxn*3];//cas[i]记录origin[i]代表的数字出现了几次,注意这里要开三倍大小,因为后面m种安排的节目数字不一定在num中出现过
int x[maxn],y[maxn];//x[i]代表第i种安排的第一个节目,y[i]是第i种安排的第二个节目
int feichang[maxn],yiban[maxn];//feichang[i]代表第i种安排非常开心的有几个人,yiban[i]代表第i种安排一般开心的有多少个人
int n;

int main()
{
    IOS
    cin>>n;
    for(int i=1;i<=n;i++){cin>>num[i];origin.push_back(num[i]);}
    int m;cin>>m;
    for(int i=1;i<=m;i++) {cin>>x[i];origin.push_back(x[i]);}
    for(int i=1;i<=m;i++) {cin>>y[i];origin.push_back(y[i]);}//注意要把安排里的数字也压入origin,因为这些数字可能在前面的num数组中未出现
    sort(origin.begin(),origin.end());
    origin.erase(unique(origin.begin(),origin.end()),origin.end());//离散化过程
    for(int i=1;i<=n;i++) cas[find(num[i])]++;//统计每个数出现了几次
    int ans=1;
    for(int i=1;i<=m;i++)
    {
        feichang[i]=cas[find(x[i])];
        yiban[i]=cas[find(y[i])];
        if(feichang[i]>feichang[ans]||feichang[i]==feichang[ans]&&yiban[i]>yiban[ans]) ans=i;
    }
    cout<<ans<<endl;
}

数组实现的离散化代码:

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;

int origin[maxn*3];//用于离散化
int tot=0,now=1;//tot代表离散化前origin数组中保存了几个数,now代表离散化后origin数组中保存了几个数

int find(int x)//在离散化数组中查找当前x所在的位置,这里也可以自己手写个二分
{
    return (int)(lower_bound(origin,origin+now,x)-origin);
}

int num[maxn];//记录m个数字
int cas[maxn*3];//cas[i]记录origin[i]代表的数字出现了几次,注意这里要开三倍大小,因为后面m种安排的节目数字不一定在num中出现过
int x[maxn],y[maxn];//x[i]代表第i种安排的第一个节目,y[i]是第i种安排的第二个节目
int feichang[maxn],yiban[maxn];//feichang[i]代表第i种安排非常开心的有几个人,yiban[i]代表第i种安排一般开心的有多少个人
int n;

int main()
{
    IOS
    cin>>n;
    for(int i=1;i<=n;i++){cin>>num[i];origin[tot++]=num[i];}
    int m;cin>>m;
    for(int i=1;i<=m;i++) {cin>>x[i];origin[tot++]=x[i];}
    for(int i=1;i<=m;i++) {cin>>y[i];origin[tot++]=y[i];}//注意要把安排里的数字也压入origin,因为这些数字可能在前面的num数组中未出现
    sort(origin,origin+tot);
    for(int i=1;i<tot;i++)
        if(origin[i]!=origin[now-1]) origin[now++]=origin[i];//离散化过程
    for(int i=1;i<=n;i++) cas[find(num[i])]++;//统计每个数出现了几次
    int ans=1;
    for(int i=1;i<=m;i++)
    {
        feichang[i]=cas[find(x[i])];
        yiban[i]=cas[find(y[i])];
        if(feichang[i]>feichang[ans]||feichang[i]==feichang[ans]&&yiban[i]>yiban[ans]) ans=i;
    }
    cout<<ans<<endl;
}

D题
原题链接:https://codeforces.com/problemset/problem/892/B
相关tag:双指针,或者也可以叫尺取法,滑动窗口,名字并不重要,掌握思想是关键。

这里我们可以从后往前看,用now来记录当前仍然为黑色的数字中,最右侧的是哪一个,num[i]记录第i个数字会把自己前面几个数染成白色。
随着循环借助当前的位置i减去num[i]-1,得到i位置对前面的进行染色后,仍然为黑色的最右侧下标是多少,和now比较并更新now。

循环内的操作都是O(1)的,总复杂度O(n)

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;
const int xx=1;

int n;
int num[maxn];

int main()
{
    IOS
    cin>>n;
    for(int i=1;i<=n;i++) cin>>num[i];
    int ans=0;
    int now=n;//now记录当前仍然为黑色的数字中,最右侧的是哪一个
    for(int i=n;i>0;i--)//从后往前看
    {
        if(i<=now) ans++;//如果当前下标在i前,代表当前位置仍然为黑色
        if(num[i]) now=min(now,i-num[i]-1);//更新now
    }
    cout<<ans<<endl;
}

E题
原题链接:https://codeforces.com/problemset/problem/520/B
相关tag:数学,dfs/bfs跑最短路径

这道题结论做法当然好,但是我还是更希望大家能看一下,这种问题是怎么样转换成一个图论可以解决的模型的,并且学习一下dfs/bfs算法。

这道题我们可以把每个值看成一个点,那么点之间的边是什么呢,那就是x到x-1,x到2x,有这两类边,对应题目给我们的两种操作。

我们建图之后直接找x到y的最短路径就行了,可以借助bfs或者dfs剪枝来是实现。

dfs实现:

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;

int x,y;

int dis[maxn];//dis[i]记录从x到i最少要几次操作

void dfs(int now,int d)//now代表当前数字是多少,d代表从x到now经过了多少次操作
{
    if(dis[now]<=d) return;//如果now数字已经有了更优的到达方式,不再进行下面的操作
    dis[now]=d;//更新x到now需要的最少次数
    if(now>=y) {dis[y]=min(dis[y],d+now-y);return;}//如果当前数值已经大于等于目标y了,就没必要再乘2了,直接-1到值y为止
    dfs(now*2,d+1);//两种操作,一种是当前数值乘以2
    if(now>1) dfs(now-1,d+1);//另一种操作,当前数值-1
}

int main()
{
    IOS
    cin>>x>>y;
    for(int i=0;i<maxn;i++) dis[i]=INF;
    dfs(x,0);
    cout<<dis[y]<<endl;
}

bfs实现:

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+7;
const double eps=1e-6;
const int mod=1e9+7;

int x,y;

int dis[maxn];//dis[i]记录从x到i最少要几次操作
bool flag[maxn];//flag[i]记录数字i是否已经进过bfs过程的队列

void bfs()
{
    queue<int>Q;
    Q.push(x);
    dis[x]=0;
    while(Q.size())
    {
        int now=Q.front();
        Q.pop();
        if(now==y) break;//如果已经走到数字y了,结束bfs过程
        if(!flag[now*2])
        {
            if(now*2>=y) dis[y]=min(dis[y],dis[now]+1+now*2-y);
            else
            {
                Q.push(now*2);
                dis[now*2]=dis[now]+1;
                flag[now*2]=1;
            }
        }
        if(now>1&&!flag[now-1])
        {
            Q.push(now-1);
            dis[now-1]=dis[now]+1;
            flag[now-1]=1;
        }
    }
}

int main()
{
    IOS
    cin>>x>>y;
    dis[y]=INF;
    if(x>=y) dis[y]=x-y;
    else bfs();
    cout<<dis[y]<<endl;
}

F题
原题链接:
相关tag:离线,并查集

离线之后反向操作,就把删点变成加点了,然后就可以用并查集来解决问题了。
懂得自然懂,直接丢代码。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e5+7;
const double eps=1e-6;
const int mod=1e9+7;

bool flag[maxn];
ll num[maxn];
ll ans[maxn];
int ope[maxn];
int n;

int fa[maxn];
ll sum[maxn];

int ask(int x) {return fa[x]==x?x:fa[x]=ask(fa[x]);}

void merge(int x,int y)
{
    fa[x]=y;
    sum[y]+=sum[x];
}

int main()
{
    IOS
    cin>>n;
    for(int i=1;i<=n;i++) cin>>num[i];
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=n;i++) cin>>ope[i];
    ans[n]=0;
    for(int i=n;i>1;i--)
    {
        ans[i-1]=ans[i];
        flag[ope[i]]=1;
        sum[ope[i]]+=num[ope[i]];
        ans[i-1]=max(ans[i-1],sum[ope[i]]);
        if(ope[i]>1&&flag[ope[i]-1])
        {
            merge(ask(ope[i]),ask(ope[i]-1));
            ans[i-1]=max(ans[i-1],sum[ask(ope[i])]);
        }
        if(ope[i]<n&&flag[ope[i]+1])
        {
            merge(ask(ope[i]),ask(ope[i]+1));
            ans[i-1]=max(ans[i-1],sum[ask(ope[i])]);
        }
    }
    for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
}

G题
原题链接:https://codeforces.com/problemset/problem/1339/D
相关tag:树上构造

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e5+7;
const double eps=1e-6;
const int mod=1e9+7;

bool jishu=0,oushu=0;
int dp[maxn],n;
vector<int>dir[maxn];

void dfs(int pre,int now,int deep)
{
    bool leaf=0;
    if(dir[now].size()==1)
    {
        if(deep&1) jishu=1;
        else oushu=1;
    }
    for(int i=0;i<dir[now].size();i++)
    {
        if(dir[now][i]==pre) continue;
        dfs(now,dir[now][i],deep+1);
        if(dir[dir[now][i]].size()==1) leaf=1;
        else dp[now]+=dp[dir[now][i]]+1;
    }
    dp[now]+=leaf;
}

int main()
{
    IOS
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int a,b;cin>>a>>b;
        dir[a].push_back(b);
        dir[b].push_back(a);
    }
    int tar=-1;
    for(int i=1;i<=n;i++)
        if(dir[i].size()>1) {tar=i;break;}
    dfs(-1,tar,0);
    if(jishu&&oushu) cout<<3<<' ';
    else cout<<1<<' ';
    cout<<dp[tar]<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值