训练赛Round #8 被B题卡太久了

46 篇文章 2 订阅
8 篇文章 0 订阅

A

给一串序列
可以任意的添加数字
让每个相邻两个数 满足 小的数的两倍 大于等于大的数
求满足条件且添加次数最小的情况

题解思路

一开始还感觉很难 , 认真看了看,直觉 ,直接贪心,不断把 不满足条件中的两个数中的 小的数的两倍加到之间 加到 加不了 (符合两倍小于等于大的数的)就行了。

AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <map>
#include <algorithm>

using namespace std;

const  int  INF =  0x3f3f3f3f;

int main ()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)
    {
        int n,ans = 0 ;
        cin>>n;
        int p ;
        cin>>p;
        for (int i = 2 ; i <= n ; i++ )
        {
            int k;
            cin>>k;
           // cout<<p<<" "<<k<<"  "<<ans<<"\n";
            if ( k > p )
            {
                if ( k > 2*p )
                {
                    int p1 = p;
                    while( p1 < k )
                    {
                        p1*=2;
                        ans++;
                    }
                    ans--;
                }
            }else
            {
                if ( p > 2*k)
                {
                    int p1 = k;
                    while( p1 < p )
                    {
                        p1*=2;
                        ans++;
                    }
                    ans--;
                }
            }
            p = k;
        }
        cout<<ans<<"\n";
    }


    return 0 ;
}

B

三个数之间的转换
a b c 每隔一个单位就消耗1

题解思路

最开始想简单写,写着写着把自己写爆炸了,这题花了起码1个多小时,最后之间暴力枚举写的,害。

其实可以考虑线性传递的,隔两个单位看成先到一个再到一个。
这样就有了学长的写法 TQL
在这里插入图片描述

AC代码

暴力写法

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <map>
#include <algorithm>

using namespace std;

const  int  INF =  0x3f3f3f3f;

int main ()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)
    {
        long long  n , c0 = 0 , c1 = 0 , c2 = 0 ,ans = 0 ;
        cin>>n;
        for (int i = 0 ; i < n ; i++ )
        {
            int k;
            cin>>k;
            if ( k % 3 == 0 )
                c0++;
            else if ( k % 3 == 1 )
                c1++;
            else
                c2++;
        }
       // cout<<c0<<" "<<c1<<" "<<c2<<"\n";
        long long  p = ( c0 + c1 + c2 )/3 ;
        /*
        if ( c0 == c1 && c1 == c2 )
        {
            cout<<"0\n";
            continue;
        }  */
        if ( c0 > p && c1 <= p && c2 <= p )
        {
            ans = 2*(p - c2) + ( p - c1) ;

        }else if ( c0 > p && c1 > p )
        {
            ans = 2*( c0 - p ) + (c1 - p );

        }else if  ( c0 > p && c2 > p )
        {
            ans = ( c0 - p ) + 2*(c2 - p );

        }else if ( c1 > p && c0 <= p && c2 <= p)
        {
            ans = 2*(p - c0) + ( p - c2) ;

        }else if ( c1 > p && c2 > p )
        {
            ans = 2*(c1 - p ) + ( c2 - p );
        }else if ( c2 > p && c0 <= p && c1 <= p )
        {
            ans = 2*( p - c1 ) + (p - c0 );
        }
        cout<<ans<<"\n";
    }


    return 0 ;
}

C

某数能不能被1e4以下的两个数(能相同)的立方和合成。

题解思路

我看才1e4,想直接暴力,结果T了,看了下苏佬的代码,双指针居然过了,双指针复杂度大概在 Nlogn左右,比 N平方好多了。双指针也有点二分的感觉。

学长的是二分枚举答案,妙啊,用的太少了,没往那边想。

AC代码

双指针写法

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include <set>

using namespace std;

const  int  INF =  0x3f3f3f3f;
set <long long > q;

long long a[20000];
void pr()
{
    for (long long  i = 1 ; i <= 10000 ; i++ )
    {
        a[i] = i*i*i;
    }
}
int main ()
{
    ios::sync_with_stdio(false);
    pr();
    int t;
    cin>>t;
    while( t-- )
    {
        int book = 0,l = 1 ,r = 10000;
        long long  n;
        cin>>n;
        while(l <= r )
        {
            if (a[l] + a[r] < n )
                l++;
            else if (a[l] + a[r] > n )
                r--;
            else
            {
                book = 1;
                break;
            }
        }
        if (book)
            cout<<"YES\n";
        else
            cout<<"NO\n";
    }

    return 0 ;
}

二分枚举答案写法

学长此处用了两个剪枝

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include <set>
#include <cmath>

using namespace std;

const  int  INF =  0x3f3f3f3f;

long long  n;
bool che(long long x)
{
    long long l = 1 , r = sqrt(x);   //对右边界剪枝
    while( l <= r )
    {
        long long mid = (l+r)/2;
        long long t = mid*mid*mid;
        if ( t > x )
        {
            r = mid -1;
        }else if ( t == x)
            return 1;
        else
            l = mid + 1;
    }
    return 0;
}
void slo()
{
    for (long long  i = 1 ; i <= 10000 ; i++ )
    {
        if ( i*i*i >= n )   //肯定搜不到的时候直接剪枝
            break;
        long long p = n - i*i*i;
        if (che(p))
        {
            cout<<"YES\n";
            return;
        }
    }
    cout<<"NO\n";
}
int main ()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while( t-- )
    {
        cin>>n;
        slo();
    }

    return 0 ;
}


D

维护一个二叉树。
根节点为最大值,根节点左节点和右节点为左边最大和右边最大。将左节点看成他下面节点的根节点。以此类推。
判断数组中的元素位于二叉树的第几层。

题解思路

数据量小

dfs暴力大法,分两边区间直接找最大值然后继续分区间,直到分不了为止。

AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <map>
#include <algorithm>

using namespace std;

const  int  INF =  0x3f3f3f3f;

int a[200];
int k[200];
int n ;

void dfs(int step , int pit ,int p1,int p2)
{
    if ( pit == p1 && p1 == p2 )
    {
        return ;
    }
    int t1,t2 = -1 ,t3,t4 = -1 ;
    for (int i = p1 ; i < pit ; i++ )
    {
        if ( t2 < a[i] )
        {
            t2 = a[i];
            t1 = i;
        }
    }
    if ( t2 != -1)
    {
        k[t1] = step;
        dfs(step + 1 , t1 , p1 , pit - 1);
    }
    for (int i = pit + 1 ; i <= p2 ; i++ )
    {
        if ( t4 < a[i] )
        {
            t4 = a[i];
            t3 = i;
        }
    }
    if ( t4 != -1 )
    {
        k[t3] = step ;
        dfs(step+1 , t3 , pit + 1, p2 );
    }
}
int main ()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)
    {
        int  p1 = 0 ,tmp = -1 ,m = 0 ;
        cin>>n;
        for (int i = 1 ; i <= n ; i++ )
        {
            cin>>a[i];
        }
        for (int i = 1 ; i <= n ; i++ )
        {
            if (a[i] > tmp )
            {
                p1 = i;
                tmp = a[i];
            }
        }
        k[p1] = m ;
        m++;
        dfs(m,p1,1,n);
        for (int i = 1 ; i <= n ; i++ )
        {
            cout<<k[i]<<" ";
        }
        cout<<"\n";
    }

    return 0 ;
}

E

N个人有代币Ai

可以进行任意次比赛,选择两个人的编号,代币多的人获胜,胜者获得败者的所有代币。当两人代币一样多的时候,随机获胜。

多次比赛后,最后只有一个人获胜

求有可能获胜的人的编号按升序输出。

题解思路

前缀和加贪心。
一开始就想排序,看到要编号,有点怕,就卡死了。看了下学长就解析需要排序,其实就是储存编号麻烦,别怕的。
首先,最后的人一定能获胜,我们可以从后面往前枚举,看看倒数第二个人的前缀和是否比最后一个人的数大,(前缀和即这个人打败所有比他小的人获得的最大值),如果比他大,那么就可以获胜。
然后不断往前比较倒数第三的前缀和和倒数第二的值。
因为如果能打败倒数第二就肯定能得到倒数第二的前缀和,这样就能打败倒数第一了。
小的时候就可以直接break了

以此类推。
学长的思路和我的有点不同,学长是二分枚举第一个获胜的人。原理和上面差不多,也就是直接寻找break之前的那个人。

AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <map>
#include <algorithm>

using namespace std;

const  int  INF =  0x3f3f3f3f;

struct node
{
    int w,p;
}a[200100];

long long sum[200100];

bool cmp (node A, node B)
{
    return A.w < B.w;
}
int main ()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)
    {
        vector <int> ans ;
        int n;
        cin>>n;
        for (int i = 1 ; i <= n ; i++ )
        {
            int t1;
            cin>>t1;
            a[i].w = t1;
            a[i].p = i;
        }
        sort(a+1,a+n+1,cmp);
        sum[0] = 0 ;
        for (int i = 1 ; i <= n ; i++ )
        {
            sum[i] = ( sum[i-1]+ a[i].w );
        }
        ans.push_back(a[n].p);
        for (int i = n-1 ; i>= 1 ; i--)
        {
            if ( sum[i] >= a[i+1].w )
            {
                ans.push_back(a[i].p);
            }else
                break;
        }
        sort(ans.begin(),ans.end());
        cout<<ans.size()<<"\n";
        for (int i = 0 ; i < ans.size() ; i++ )
            cout<<ans[i]<<" ";
        cout<<"\n";
    }
    return 0 ;
}

F

给出一段序列,你可以任意删除某个数,使每个数出现的次数相同(0或者N)。

求最小的删除次数。

题解思路

不用map的精彩离散化 前缀和 枚举

没想出来,看学长的代码。离散化的目的是为了求前缀和,而前缀和是为了枚举保留的数。
我们只关心这个数是否和别的数不同和出现的次数。
这样就有了学长sort后求出每个不同的数出现的次数的数组。
为了方便求出枚举保留某个数的次数时,需要删除别的数的次数。
进行了前缀和操作。
后面的枚举基本上就是数学问题了,看好边界就能处理。

因为是让每个数的次数相同,所以必然会趋于某个数的次数,这个数就不用删除。

AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <map>
#include <algorithm>

using namespace std;

const  int  INF =  0x3f3f3f3f;
const  int  N = 2e5 + 10 ;

int a[N];
int cnt[N];
int sum[N];
int main ()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n;
        for (int i = 0 ; i < n ; i++ )
            cin>>a[i];
        sort(a,a+n);
        int pre = -1;
        int pit = 0 ;
        for (int i = 0 ; i < n ; i++ )
        {
            if (pre == -1 )
            {
                cnt[pit] = 1;
                pre = a[i];
            }else if ( pre == a[i])
            {
                cnt[pit]++;
            }else
            {
                pre = a[i];
                pit++;
                cnt[pit] = 1;
            }
        }

        sort(cnt,cnt+pit+1);
        /*
        for (int i = 0 ; i <= pit ; i++ )
            cout<<cnt[i]<<" ";
        cout<<"\n";   */
        sum[0] = cnt[0];
        for (int i = 1 ; i <= pit ; i++ )
        {
            sum[i] = sum[i-1] + cnt[i];
        }
        int minn = sum[pit] - (pit)*cnt[0] -sum[0];
        for (int i = 1 ; i <= pit ; i++ )
        {
            minn = min(minn , (sum[pit] - sum[i]) - (pit-i)*cnt[i] + sum[i-1] );
        }
         cout<<minn<<"\n";
    }

    return 0 ;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值