梦的开始:Codeforces Round #748(Div.3)A~E

前言:

	非ACM强校,非科班,大二,纯兴趣无OI经验,8月前仅上过C语言课,8月开始学习部分算法知识,10月第一场codeforces比赛,div3,即本场,一题没有做出来,有些失望,慢慢成长。
	**我一定能够成为我想要去成为的人。**

A. Elections

  • 题目大意:
    输入三个数,分别求出使这三个数变为三个数中最大数的最小加数。例如输入1 3 4,输出就是4 2 0。
  • 题型分类与依据:
    分类:简单思维题,但要注意不同分数情况选手的处理分类。
    依据:凭感觉就知道是水题的水题。
  • 解题思路:
    要加最少的分,变成分数最高的,分两种情况讨论:①如果我们目前不是分数最高的,那么我们的目标分数,就是最高分选手的分数+1,那么就是我们目前的分数,加上我们与最高分选手之间相差的分数,再加1;②如果我们目前分数是最高的,我们不用加分,我们就是最高的,那么我们的最少加分就是零。
  • 代码实现:
#include<bits/stdc++.h>
using namespace std;
int main(){
    ios::sync_with_stdio(false);
    int t,a,b,c,maxa,maxb,maxc;
    cin>>t;
    while(t--){
        cin>>a>>b>>c;
        if(a>max(b,c)){
            maxa=0;
        }
        else
            maxa=max(a,max(b,c))-a+1;
        if(b>max(a,c)){
            maxb=0;
        }
        else
            maxb=max(a,max(b,c))-b+1;            
        if(c>max(a,b)){
            maxc=0;
        }
        else
            maxc=max(a,max(b,c))-c+1;    
        cout<<maxa<<" "<<maxb<<" "<<maxc<<endl;
    }
    return 0;
}

B.Make it Divisible by 25

  • 题目大意
    输入一串数字(大于等于25),已知该数字串在经过删掉某几位上的数字后,能被25整除,求最少删掉几个数字,能实现被25整除。如100最少删0个数字,3295最少删1个数字9,删3和9也是可以的,但不是最少,我们要求最少。
  • 题型判断及依据
    题型:数论、深搜DFS、贪心
    判断依据:数论——数字,被25整除,整除问题一般牵涉数论;深搜DFS、动态规划DP——通过一步一步(每次删一个数字)达到极值状态(最少),一般牵涉贪心、BFS、DFS、DP。
  • 解题思路
    整除问题一般考虑尾数特征,一般都会有不变的尾数规律特征。题目是说能被25整除,能被25整除的数字,后两位数字的组成可能组合,有且仅有4种,分别为:25、50、75、00。那如果题目改成20呢?很明显,也是一样的,有且仅有20、40、60、80、00这5种组合情况。这就是整除问题的习惯性套路。
    知道了这个特征,这个问题就从如何变成能被25整除的数,转化为如何变为尾数为25、50、75、00四种组合中的任意一种情况。
    我们再仔细看看这四种组合,发现了尾数要么是5,要么是0。也就是说,答案的尾数要么是5,要么是0。那么我们如何最快地删除以实现呢?思路很好想,采用贪心策略,寻找离末尾最近的0或5,把它后面的数全删了,就可以最快地删除以实现末尾是0或5了,而后面的数字数量,就是需要删除的数字数量。
    但是,我们还要考虑,尾数是5的情况下,5前面的2或7如何最快删除地得来;尾数是0的情况下,0前面的0或5如何最快删除地得来?思路和刚才一致,继续采用贪心策略,如果尾数为5,从后往前找离这个尾数5前面最近的2或7;如果尾数为0,从后往前找离这个尾数0前面最近的0或5。中间所间隔的数字数量,就是需要删除的数字数量。
    最后,将构造2、5尾数时后面的数字数量,加上2、5前面离他最近的匹配数字之间的数字数量,就是构造出一个能被25整除的数字所需要删除的数字数量,我们只需要遍历所有情况,找出所有可构造的删除数字量中的最小值即可。(DFS方法也可以做,但是可能本人TLE了,暂时未破,所以先用贪心+数论的常规解题思路讲解)。
  • 代码实现
#include<bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    int t;
    string s;
    cin>>t;
    while(t--){
        int ans=999999;
            cin>>s;
            int cnt=0,d=0;
            for(int i=s.size()-1;i>=0;i--){
                if(s[i]=='5'){
                    for(int j=i-1;j>=0;j--){
                        if(s[j]=='2'||s[j]=='7'){
                            ans=min(cnt+d,ans);    
                            break;
                        }
                            d++;
                    }
                }
                cnt++;
            }
            cnt=0,d=0;
            for(int i=s.size()-1;i>=0;i--){
                if(s[i]=='0'){
                    for(int j=i-1;j>=0;j--){
                        if(s[j]=='5'||s[j]=='0'){
                            ans=min(cnt+d,ans);
                            break;
                        }
                            d++;
                    }
                }
                cnt++;
            }
            cout<<ans<<endl;
    }
    return 0;
}    

C.Save more mice

  • 题目大意
    在一维坐标轴中,猫位于原点,坐标为0,老鼠洞位于终点,坐标为n,在猫与老鼠洞之间,有k只老鼠。老鼠到达老鼠洞 ,就安全,猫到达老鼠的位置,老鼠就算死亡。每一次,可以选择其中某一只老鼠向右移动一个单位,然后猫会自动向右移动一个单位,然后再选任意一只老鼠向右移动一个单位,然后猫再向右移动一个单位。移动的原则是,老鼠第一个移动。请问,k只老鼠中,最多可以有几支老鼠安全存活?
  • 题型分类与依据
    题型:贪心
    依据:题目中有“最多”,这一明显的提示词存在。
  • 解题思路
    判断出来是贪心,那么贪心题型中最重要的思路就是“排序”优先处理。k支老鼠,我们肯定是先救最好救的,再救次好救的,依次类推,最后再救最难救的。每救完一只老鼠,猫就会移动所救老鼠距离老鼠洞的相同距离。途径中的老鼠都会死亡,当猫走到老鼠洞的时候,除了被救的老鼠,剩余的老鼠全死了,也就结束了。
    因此我们的思路,就是sort函数对各个老鼠的位置进行降序排序,优先处理离老鼠洞的老鼠,同时记录猫的总路程。当猫的总路程大于等于n时,最多能救的老鼠,就救完了。
  • 代码实现
#include<bits/stdc++.h>
using namespace std;
int main(){
    ios::sync_with_stdio(false);
    int t,n,k,a[400005];
    cin>>t;
    while(t--){
        cin>>n>>k;
        for(int i=0;i<k;i++){
            cin>>a[i];
        }
        sort(a,a+k,greater<int>());
        int s=n,cnt=0;
        for(int i=0;i<k;i++){
            s-=(n-a[i]);
            if(s<=0)break;
            cnt++;
        }
        cout<<cnt<<endl;
    }
     return 0;
}

D1.All the same

  • 题目大意
    给出n个数字,选定一个数字k,n个数字分别减去特定几次k,n个数字最终值相等,求能满足题目要求的k的最大值。如1 3 5,k=2,1减去0次k,3减去1次k,5减去2次k,最终变为1 1 1,3个数字全相等。
  • 题型分类与依据
    题型:数论中的比较隐蔽最大公因数问题。
    依据:(猜测)与数论有关的最大值公共数,一般都是考察最大公因数,可能只是比较隐蔽。
  • 解题思路
    首先,一个问题,求1 2 4 6的k值,是否与求 2 3 5 7的k值是同一个问题?
    很明显,是一样的。唯一不同的是,后者的最终的相等值比前者多1。
    也就是说,影响这道题结果的,只有各个数之间的差值关系。
    那我们不妨将这个问题简化,全部都变成0是最小数的序列。
    以样例1:1 5 3 1 1 5为例,我们把它简化为0 4 2 0 0 4。
    减去特定几次k,n个数字相等,继刚才那条,这个相等的数是可以任意选的,对把,那么我们就把这个相等的数选择为简化后的序列最小数0。
    问题就变成了,0 4 2 0 0 4,我们选一个k,各个非零元素减去任意整数个k后,让每个非零元素都变成0,求k的最大值。
    太多非零元素有点棘手,我们先从两个非零元素开始分析。
    假设A和B是简化序列中唯一的两个非零元素,如果存在这个最大k,那么必然满足:
    A-ak=0,B-bk=0,其中a,b为特定整数,代表A减去了特定a次k,B减去了特定b次k。
    那么我们就有A=a*k,移项得A/k=a;同理可得B/k=b。a和b是两个正整数。其实a和b是多少不重要,但重要的是a和b都是整数,也就是说A和B能被k整除,即k是A的因数,也是B的因数,那么k就是A和B的公因数!
    那么题目要求最大k,就是求简化序列中的非零元素序列中的最大公因数。
    求多个数字的最大公因数的思路,也是比较常见而且显而易见的了,我们可以先求前两个数字的最大公因数,两个数字的最大公因数可以通过名为双下划线小写gcd的系统内置数学函数实现,也可以用辗转相除法自定义一个gcd函数,先求前里个数字的最大公因数。求出前两个数字的最大公因数后,用该最大公因数与序列中其它的非零元素再求最大公因数,再求出一个新的最大公因数,再与其它的非零元素求最大公因数,直到最后,即是所有非零数字的最大公因数。
  • 代码实现
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    ios::sync_with_stdio(false);
    int t,n;
    ll m,a[45];
    cin>>t;
    while(t--){
        cin>>n;
        ll mina=1e9;
        for(int i=0;i<n;i++){
            cin>>a[i];
            mina=min(a[i],mina);
        }
        for(int i=0;i<n;i++){
            a[i]-=mina;
        }
        sort(a,a+n,greater<int>());
        ll m=__gcd(a[0],a[1]);
        for(int i=1;i<n;i++){
            if(a[i]!=0){
                m=__gcd(m,a[i]);
            }
        }
        if(m==0)m=-1;
        cout<<m<<endl;
    }
    return 0;
}

D2.Half the same

  • 题目大意
    D1的基础上,求能够使得一半数字被减到相同的最大K。
  • 题型分类
    数论中的最大公因数问题,枚举,STL。
  • 解题思路
    求最大公因数,如果求所有数的最大公因数,显然前一题不断求两两之间的公因数的方法是最快的;但是如果是求部分数的最大公因数的最大值,遍历每个非零元素的最大满足因数来实现则更为明智。
    我们来解释一下,什么叫通过遍历所有序列中某两元素差值的最大满足因数来实现。
    先解释一下,为什么要求两元素差值。由D1知:A-ak=B-bk,因此我们有,A%k=B%k,A%k-B%k=0,(A-B)%k=(A%k-B%k)%k=0%k=0,(A-B)能够被k整除,故k一定是|A-B|中的整数。
    接下来,我们枚举所有的差数对,实现所有差数的遍历。
    例如我有一个序列, 3 6 7 9 12 15。那么,我们可以先找3和6 7 9 12 15的所有差数,并验证该差数,是否是至少n/2个元素的因数,如果是,我们就称之为“满足因数”,即满足D2题意的因数。如果3的满足因数有多个,我们只记录3的最大满足因数。类似地,接下来找6和剩下所有元素(7 9 12 15)的差数,依次类推,知道所有差数都被遍历过位置。在此过程中,不断更新比较最大满足因数,最后记录的即为最大的最大满足因数。
    同时,我们需要先预处理输入数据已经有至少一半的相同数据的情况,此时k取任意值,都可以实现至少一半的数据相同,输出-1。
  • 代码实现
#include<bits/stdc++.h>
using namespace std;
int a[50],n;
bool check(int num)//满足因数的检验
{
    map<int,int> mp;
    for (int i=1;i<=n;++i)mp[(a[i]%num+num)%num]++;//统计该因数是多少元素的因数
    map<int,int>::iterator i;
    for(i=mp.begin();i!=mp.end();i++) if(i->second >= n/2)return true;//是大于等于一半数字的因数,返回true,为满足因数
    return false;// 反之,则不是满足因数,不能选择该因数  
}
int solve(int num)//搜索该差值的最大满足因数
{
    int ans = 0;
    for (int i=1;i<=num/i;++i)if (num%i==0)//如果k是因数,num/k也肯定是因数,进一步判断是否其两者否为满足因数(PS:降低时间复杂度)
    {
        if (check(i))ans = max(ans,i);//如果是满足因数,与之前的假设的最大满足因数比较,记录更大的满足因数
        if (check(num/i))ans=max(ans,num/i);//同上
    }return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    int t;cin>>t;
    while (t--)
    {
        cin>>n;
        for (int i=1;i<=n;++i)cin>>a[i];
        map<int,int> mp;
        map<int, int>::iterator i;
        for (int i=1;i<=n;++i)mp[a[i]]++;//统计同一个数字出现的次数
        int ans = 0;//记录最大满足因数,由于因数一定大于0,因此可以初始化为0.
            for(i = mp.begin(); i != mp.end(); ++i)if (i->second>=n/2)//如果同一个数字出现超过一半,直接满足题意,可以取无穷大,输出-1
        {
            ans= - 1;
            break;
        }if (ans==-1)
        {
            cout<<ans<<endl;
            continue;
        }
        for (int i=1;i<=n;++i)
            for (int j=i+1;j<=n;++j)
                ans = max(ans,solve(abs(a[j]-a[i])));//遍历每一个数字的因子最大值,求最大值的最大值,就是题意要求的答案
        cout<<ans<<endl;
    }
}

E.Gardener and Tree

  • 题目大意
    N个点,N-1条边,每一次减去度数为1的点,问k次后,还剩多少个点?0个点,剪了还是0个点,只有一个点,剪了就0个点,只有两个点相连,剪了就0个点。
  • 题型分类与依据
    题型:BFS,数据结构,STL,图
    依据:这题的解题思路,明显是树的层次遍历,遍历顺序是是从最底层(度数全为1),向上搜k-1层。同时,涉及存储图的数据的问题。图的存储,可以用邻接矩阵、邻接表,也可以用vector。
  • 解题思路
    首先存图,用vector创建一个动态数组,再创建一个度数数组,数组下标名为点名,数组内部值为这个点所关联的所有点的名字。每关联两个点,就将这两个点的度数数组值加一。
    然后BFS,BFS第一步,队列类型是什么,取决于元素要包含哪些内容。根据题意,元素既要包含它是哪个点,即点的名字,也要包含,它的操作轮次,因为一旦到了第k轮,就不用再剪了。因此,我们可以用pair,也可以用结构体,创建一个新类型,包含这两个内容。
    在队列内加入首元素,根据题意,首元素应该是度数小于等于1的所有点,那么就用for循环,遍历度数小于等于1的点加入队列,此时的轮次值,全部都是1。
    考虑删除度数为1的点,如果首元素de数组为1,那么说明首元素这个点已经被剪掉了,就continue,访问队列中下一个元素点,如果de数组为0,那么就把首元素的点的de数组赋值为1,表示现在把这个点去掉的标志。
    考虑题意,如果这个点所在的轮次,是第k轮,也不用继续操作了,也continue。
    考虑次轮可能入队点选择,次轮可能入队列的点,应该是该点相连的点,用迭代器遍历该点所有的相连的点,由于当前点已经被剪去,所以遍历到的所有相连点的度数数组都减一,同时判断一下遍历的相连点能否入队列,判断依据还是,度数为小于等于1的点,就类似于刚才for循环挑选队列第一轮元素的值,都为度数小于等于1的点,除了度数小于等于1,我们还应该要注意,这个相连点是么有被剪过的,也就是它的de数组值为0,两个条件都满足就入队列,但是该相连点的操作轮次值要赋为当前点的操作轮次值+1,代表是当前点的下一轮操作.
    遍历完所有当前点的相连元素,就进行第二轮的bfs搜索,直到第k轮,所有元素一直continue,所有元素都出列,队列为空,搜索结束。
    最后,遍历de数组值,有多少个1,就说明有多少个点被剪了,那么总数n减去被减去的点数,就是所剩的点数,也就是最终答案。
  • 代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5+100;
vector<int> G[maxn];
bool de[maxn];
int du[maxn];
int n,k;
struct pos{
    int dian,cnt;
};
int main()
{
    ios::sync_with_stdio(0);
    int T;cin>>T;
    while (T--)
    {
        cin>>n>>k;//n个点,剪枝k次
        for (int i=1;i<=n;++i)G[i].clear(),du[i]=de[i]=0;//初始化,vector从第一个点到第n个点的位置都要空出来
        for (int i=1,u,v;i<n;++i)
        {
            cin>>u>>v;
            G[u].push_back(v);//把v放到G容器组中,第u个vector容器中的最后
            G[v].push_back(u);//同上
            du[u]++;du[v]++;//每个容器的度数加1
        }
        queue<pos> que;//新建一个队列,队列的元素类型是pair,左元素为点名,右元素为此次操作的轮次
        for (int i=1;i<=n;++i)if (du[i]<=1)//度数小于等于一的,加入队列
            que.push({i,1});
        while (!que.empty())
        {
            pos p = que.front();//取队列首元素
            que.pop();//首元素出列
            if (de[p.dian])continue;//剪过的点,不用继续操作
            de[p.dian]=1;//剪去该点
            if (p.cnt==k)continue;//如果轮次已经到了,就不用再修剪该点,到下一个点
            for (int v:G[p.dian]) //利用整形的v,遍历动态数组G[p.dian]内部所有元素的值,所有点的度数减一
            {
                du[v]--;
                if (du[v]<=1&&!de[v])que.push({v,p.cnt+1});//如果这个点度数小于等于1了,并且还没有剪过,那么加入队列,轮次加一
            }
        }
        int ans = n;
        for (int i=1;i<=n;++i)if (de[i])--ans;//有多少次被剪,就少多少个节点数
        cout<<ans<<"\n";
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值