牛客练习赛103A-C的个人解题思路

比赛链接


A:abc转换

题目描述

牛牛有三个正整数 a,b,c ,它每次能够从三个数中选出任意两个数进行“相加,相减,相乘”中的任意一种操作,再将得到的结果赋值回三个数中的任意一个。

牛牛想知道如果采取最优策略,它至少需要几步才能使 a,b,c中至少一个数等于 0?
 

输入描述:

 
 

输入第一行一个正整数 T 代表案例组数。

接下来 T 行,每行三个正整数分别代表 a,b,c,。

保证:0<T,a,b,c≤1000

这个题就是直接按照题目意思来进行,可以分类成为三种情况

第一种就是里面有相等元素,那么只需要一步就能使一个数。

第二种就是a,b,c存在俩个数使得相加或者相减或者相乘等于三个数的其中一个数,那么我们可以让运算得到数替换成其他任意一个不与他相等数,在进行一次减法,就只需要俩步。

第三种就要想一下,就不是以上俩中情况时候应该怎么办了,这里楼主当时想法是第三种起码要三步起步,那我可以把任意俩个数相减使结果代替成另外那个数,然后这个数与里面相减数中的减数相加,就可以成为被减数,那我们就可以证明就最少只需要三步啦。(当然还有很多想法可以相等可以只有三步,比如俩数相加给第三个数,在自己相加给自己,再相减也是三步啦)

以下是代码

#include<bits/stdc++.h>
using namespace std;
void solve()
{
   int a[3];
    cin>>a[0]>>a[1]>>a[2];
    sort(a,a+3);                         //排个序方便后面的又长又臭的判断
    if(a[0]==a[1]||a[1]==a[2]||a[0]==a[2]){
        cout<<1<<'\n';
        return;
    }
    if(a[0]+a[1]==a[0]||a[0]+a[1]==a[1]||a[0]+a[1]==a[2]||a[1]+a[2]==a[0]||a[1]+a[2]==a[1]||a[1]+a[2]==a[2]||a[0]+a[2]==a[0]||a[0]+a[2]==a[1]||a[0]+a[2]==a[2]
       ||a[0]*a[1]==a[0]||a[0]*a[1]==a[1]||a[0]*a[1]==a[2]||a[1]*a[2]==a[0]||a[1]*a[2]==a[1]||a[1]*a[2]==a[2]||a[0]*a[2]==a[0]||a[0]*a[2]==a[1]||a[0]*a[2]==a[2]
      ||a[1]-a[0]==a[0]||a[1]-a[0]==a[1]||a[1]-a[0]==a[2]||a[2]-a[1]==a[0]||a[2]-a[1]==a[1]||a[2]-a[1]==a[2]||a[2]-a[0]==a[0]||a[2]-a[0]==a[1]||a[2]-a[0]==a[2]) //又长又臭的判断 里面有些不可能的判断语句但由于偷懒还是没有判别的
    {
        cout<<2<<'\n';
        return;
    }
    cout<<3<<'\n';

}

int main()
{
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int t;
    cin>>t;
    while(t--)
    {
        solve();
    }
	return 0;
}

B 差分构造
 

题目描述

牛牛获得了一个可重集合,集合中有 n 个整数,现在牛牛想要利用这 n 个数构造一颗最小生成树

这里的最小生成树的权重定义为每条树上边权值的和,每条树上边的权值为其所连接两个整数的差的绝对值。

显然这样的最小生成树可能不止一颗,所以你还需要输出所有最小生成树中最长树链最短的那一颗的最长树链长度。这里的树链长度指的是树链中结点的个数,与边权和点权无关。

在本题中,你需要输出最小生成树的权值,以及所有最小生成树中最长树链的最短长度

输入描述:

 
 

第一行一个正整数 n 代表可重集中元素的个数。

第二行 n 个用空格分割的整数描述了集合中的元素。

保证:

0<n≤10^5; 集合中元素的绝对值不超过 10^9

这个题乍看以为是数据结构题,然后观察后发现是用这些数据组成一个最小生成数,发现是的贪心题,只需要把数据大小排序只后俩个相邻差的绝对值之和的就是就可以得到这组数据的最小生成树,因为这样排序后,俩个数之间相邻就是在这群数里面的绝对距离小,就满足最小生成树中的的从最小边开始选起。

然后第一问解决后,那么第二问呢,最长树的最短距离,可以发现,由上面可得里面树的组成可能有俩种模式,第一种不相同的元素相邻,第二种相同元素的相邻。那我们可以分析得,如果有不同元素相邻时候,那我们是一定要让他们之间连接成一条链的,才能满足上面所说满足最小生成树的要求,那么第二步就是相同元素之间,我们可以一开始都排序之后使这个树就是排序之后这个样子,然后里面那些相同元素的相互连接在一起的,但他们距离为零,又增大的链长,那我们可以把相同的元素选出一个作为主链上的结点,其他都一个一个直接插到这个主链结点元素上。

那么这时候可以发现,长度居然只变成去重之后长度再首位如果有相同元素就各加一,还有个特判,如果只有一个结点的话要考虑他有多少的相同元素,如果少于等于俩个,那最长链有多少个相同元素,否则最长链长度就是三啦。

以下是代码

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+10;

int n,m;
int a[N];
int b[N];
vector<int>tr;

int find(int tt)
{
     return lower_bound(tr.begin(), tr.end(), tt)-tr.begin();    
}


int main()
{
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n;
    for(int i=0;i<n;i++) {
        cin>>a[i];
        tr.push_back(a[i]);      
    }
    sort(tr.begin(),tr.end());
    long long ans=0;    //答案最大可2*10^(9+5)
    tr.erase(unique(tr.begin(),tr.end()),tr.end());//去重
    for(int i=1;i<tr.size();i++)
        ans+=tr[i]-tr[i-1];        //最小生成树的计算
    m=tr.size();
    int res=tr.size();
    for(int i=0;i<n;i++)
    {
        int t=find(a[i]);  //离散化了一下,来记录每一种元素有多少个
            b[t]++;
    }
     if(m==1)  //特判
     {        
        if(b[0]<=2) cout<<ans<<" "<<b[0];
        else cout<<ans<<" "<<3;return 0;
    }
    if(b[0]>=2) res+=1;     //如果最小的元素不止一个俩个就链条要增长一位
    if(b[m-1]>=2) res+=1;    //同理
    cout<<ans<<" "<<res;
	return 0;
}

C 向前文本编辑器

题目描述

牛牛需要一个程序帮它码一段文字,初始时文本为空,光标在文本最前方。

牛牛会发出 m条指令,指令分两种:

    op s 这里 op=1 且 s是一个字符串,代表在当前光标位置插入字符串 s 并把光标移到 s的末尾(并不是整个文本的末尾)。

    op x 这里 op=2 且 x 是一个正整数,代表将光标向前移动 x 个位置(保证不会超过光标前字符的个数)。

牛妹觉得这种程序已经有人做过类似的了,所以她把这题稍微修改了一点。

在原本需要输入字符串 s 的地方,牛妹将只会给出一个正整数 len 代表 s 的长度,而 s 则被定义为字符串 T 的第 1 到第 len 个字符组成的子串。

这里的字符串 T 被定义成由循环节 "abcd…xyz012…89ABCD…XYZ" ,组成的无限长字符串。

现在请你帮牛牛实现这个文本编辑器,并输出文本最后的状态(由于最后文本可能过于庞大,所以这里只需要输出文本的第 1 和第 100,200,300,400… 个字符直到文本末尾)以及光标前方字符的个数,两者用一个空格分隔。

输入描述:

 
 

第一行一个整数 m 代表操作数。

接下来 m 行,每行描述了一个操作。

保证:0<m≤10^5,所有 len 的和不超过 10^7。

输出描述:

输出共一行一个字符串和整数分别代表文本最后的状态(如题意压缩后)和光标前方字符的个数。

这个题比赛当时看错题了,没注意是输出第1到100 200 ...这个格式,以为要输出最后光标所在位置前一个字符……。

这个题第二位还是好做,最后光标的位置,op==1就加上len,op==2就减去x,主要是第一问。一开始想的是按照题目模拟一遍把字符串建立起来,不过凭直觉想了想肯定会超时,因为如果光标跳到之前以及有的连续字串段中,就需要把他们从中间拆开,然后这个维护过程对于楼主来说其实有点难想,且答案感觉可能会是对应下标的在对于他那个字串是多少位去余上62(abc...012..ABC..的长度),然后在这个abc..012..ABC..里的位置,所以pass了,也没有去尝试,可能按题目顺序也能做吧(我是菜鸡)。

所以最后就想到,可以先把操作存储下来,且设置一个值记录光标,反过来模拟,既然op==2时候是向前进x位,那么我倒着来,就是光标向后走x位,而1操作就可以向前推具体每一个位置是什么样的状态。这样的话其实就可以直接开一个字符串总长度的空位,然后光标在里面,跟着光标,在里面填字符或者是后退光标,这样就不要担心在一个连续字串中生成一个新字串啦,而这顺序的操作,在倒过来的时候可以在向前生成子串时候,遇到已经被站位置的的字串就跳过他,如何实现跳过,这就可以把原本空串想象成一个双链表,当一段被记录过后,咱们就“截掉”这段,来达成这个操作。

说这么多不如看着代码理解一下

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+10,M=1e7+10;

int b[N][2];
int top=0;
int tt=0; //光标
int a[M][2]; // 0左 1右边
int c[M];
string s="";

int n,m;
void init() //初始化一下
{
    a[0][1]=1;
    a[10000000][0]=10000000-1;
    for(int i=1;i<=10000000-1;i++)  //这个是模拟一下双链表,i的左边是i-1,右边是i+1
    {
        a[i][0]=i-1,a[i][1]=i+1;
    }
    for(int i=0;i<26;i++)           //来记录T 方便后面取答案
        s+=i+'a';
    for(int i=0;i<10;i++)
        s+=i+'0';
    for(int i=0;i<26;i++)
        s+=i+'A';
}


int main()
{
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    init();
    cin>>m;
    int top=0;
    for(int i=m;i>=1;i--)
    {
        cin>>b[i][0]>>b[i][1];  //记录操作
        if(b[i][0]==1) {    
            tt+=b[i][1];       //记录光标位置
            top+=b[i][1];      //记录总长度是多少
        }
        else tt-=b[i][1];       //记录光标位置
    }
    int t=tt;                  //为下面计算
    for(int k=1;k<=m;k++)
    {
        if(b[k][0]==2){          //当操作为2时候就向后推,光标位置向链条上的右边移动(当然,链条可能被截断)
            for(int j=b[k][1]-1;j>=0;j--,t=a[t][1]);
        }
        else{
            int r=t;int l;
            for(int i=t,j=b[k][1]-1;j>=0;j--,i=a[i][0]) //当操作为1时候就向前推,同时还可以建立一个数组来记录一下这个位置他是这个子串的第几位,光标位置向左边移动(当然,链条可能被截断)
            {
                c[i]=j;
                l=i;
            } 
            t=a[l][0];               //此时需要截断l-r这段区间的链条,先移动光标,就到这个链条的左边了
            a[a[l][0]][1]=a[r][1];a[a[r][1]][0]=a[l][0]; //这里就是l-r链条的左边就是a[l][0],右边是a[r][1],那就将他们连在一起,来完成截断操作
        }
    }
    if(1<=top){                      
        cout<<s[c[1]%62];   //输出第一位字符
    }
    for(int i=100;i<=top;i+=100) //按题目输出
        cout<<s[c[i]%62];
    cout<<" "<<tt;  //最后的光标位置
	return 0;
}

最后这个时间是不会超时哒,因为链长总共就只有10^7,那么我最多向前加子串就只会推10^7,向后也10^7,所以复杂度就是o(2*10^7)

最后,楼主是一时兴起第一次写题解,肯定有很多写的不好的,也有很多专业知识的疏忽。可能中间也有很多错别字,或者语文没有表达清楚的地方也请多多包涵。楼主只是一个比较喜欢做做算法题的萌新,还有更多需要学习地方。如果对于我的题解有不懂的,欢迎评论区留言,来解答。也欢迎大佬来指导一下我的各个方面不足。谢谢~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值