暑假训练赛1

Posted C_YCBX Py_YYDS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了暑假训练赛1相关的知识,希望对你有一定的参考价值。

A 金属采集

在这里插入图片描述

搜了下。发现是树形dp,这玩意确实不会,也没做过。。。所以暂时也看不懂。。

B 一元三次方程求解

在这里插入图片描述

  • 解题思路:用浮点数从负数一直枚举到正数,根据数学中,存在根的位置,左右两边的位置的函数值相乘肯定是小于0(异号)的。
#include <bits/stdc++.h>
using namespace std;
double a,b,c,d,i;
int main()
{
	cin >>a>>b>>c>>d;
  //先用枚举初步确定三个根的范围,
	//比如f[i]*f[i+1]<0则可知道[i,i+1]之间有一个根
	for(i=-100;i<=100;i+=0.01){     
		double x1=i-0.005,x2=i+0.005;
		if((x1*x1*x1+b/a*x1*x1+c/a*x1+d/a)*(x2*x2*x2+b/a*x2*x2+c/a*x2+d/a)<0)
		printf("%.2f ",i);
	}
	return 0;
}

C 求先序排列

在这里插入图片描述

  • 解题思路:这类题目在面试中很常见,给出中序+先序或者后序,要确定一个二叉树序列,同样这类题无论给的是中序+先序,还是中序+后序,我们都可以通过各种遍历的特性以及递归分治来组建该二叉树。
  • 先序排列:根节点+左子树+右子树
  • 中序排列:左子树+根节点+右子树
  • 后序排列:左子树+右子树+根节点

最后我们根据先序\\后序能瞬间确定根节点的位置,而在根据中序之中根节点的位置,我们便能确定左子树和右子树的位置了,然后递归分治便可实现一个构建二叉树的方式,而此题需要构建先序排列,则需要在左右子树均未形成的位置将根节点打印即可完成此题。

  • 小细节:寻找中序中根节点的位置可以通过哈希表提前记录下来(这种题中序一般就是个工具人)。
#include <bits/stdc++.h>
using namespace std;
string post,in;
int memo[26];
void pre(int root, int start, int end) {
    if(start > end) return ;
    int i = memo[post[root]-'A'];
    printf("%c", post[root]);
    //左子树
    pre(root - 1 - end + i, start, i - 1);
    //右子树
    pre(root - 1, i + 1, end);
}

int main() {
    cin>>in>>post;
    memset(memo,0,sizeof(memo));
    for(int i=0;i<in.size();i++){
        memo[in[i]-'A'] = i;
    }
    pre(post.size()-1,0,post.size()-1);

    return 0;
}

D 单词接龙

在这里插入图片描述

  • 解题思路:三个步骤
  1. 用邻接矩阵预处理记录任意两个编号单词的公共最小接壤尾部(当最小接壤尾部为前一个单词自身,则不可)。
  2. 创建记录被使用编号的表。
  3. dfs爆搜结果,注意每次选择需要更新使用次数以及答案,能不能选要看是否尾部存在最小接壤以及被选的次数不能超过两次!

关于邻接矩阵的更新方法:很普遍的可以用string的substr()方法进行枚举遍历出最小结果然后通过二维数组记录,我这边采用的有穷状态自动机的思想进行更新的答案。。。学过一点自动机的应该都能懂。。这是我所能想到的两种更新方法,其他方法你们也可以尝试。
我也是最近写自动机的题目写了一些,所以啥都想着自动机。。。

#include<bits/stdc++.h>
using namespace std;
int n;char start;
vector<string>s(25);
int mp[25][25];
int used[25]; // 每个单词使用的次数
int ans = 0;
//根据词语接龙的末尾有无重合字符,枚举并添加下一个选择的单词更新长度
void dfs(int pos,int len)
{
    ans = max(len, ans);
    //记录使用次数
    used[pos] ++ ;

    for (int i = 0; i < n; i ++ )
        //能有公共尾部,且使用次数小于2则可选,此时长度会变成len+s[i].size()-mp[pos][i]
        if (mp[pos][i] && used[i] < 2)
            dfs(i, len+(int)s[i].size()-mp[pos][i]);

    used[pos] -- ; // 撤销使用次数
}
//用于任意更新两个单词邻接矩阵(存储两个单词的末尾最小公共长度)的函数
void get_same(const string&s1,const string&s2,int x1,int x2){
    int state = 1;
    int len1 = s1.size(),len2 = s2.size();
    int i = 0,j=0;
    int cnt = 0;
    int res = INT_MAX;
    while(i<len1&&j<len2){
        //状态1都是两个字符不相等的情况,一旦出现相等则进入状态2
        if(state==1){
            cnt = 0;
            j = 0;
            if(s1[i]==s2[j])
                state = 2;
            else i++;

        }//处于状态2的都是两个字符现在相等的状态,如果最后到可以弹出的状态就继续进行判断
        else if(state==2){
            if(s1[i]==s2[j]){
                i++;
                j++;
                cnt++;
                if(i==len1){
                    res = min(res,cnt);
                    j = 0;
                    i = i-cnt+1;
                    state = 1;
                }
            }else{
                state = 1;
            }
        }
    }
    if(res!=len1&&res!=INT_MAX)
    mp[x1][x2] = res;
}
//构建邻接矩阵,记录任意两个单词是否可以连接以及最大重复
void pre_handle(){
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            get_same(s[i],s[j],i,j);
        }
    }
}

int main(){
    memset(mp, 0,sizeof(mp));
    memset(used,0,sizeof(used));
     cin>>n;
     for(int i=0; i<n; i++)
         cin>>s[i];
     cin>>start;
     pre_handle();
     //寻找开头的单词进行爆搜
     for(int i=0;i<n;i++){
         if(s[i][0]==start){
             dfs(i,(int)s[i].size());
         }
     }
     cout<<ans;
}

E 校门外的树

在这里插入图片描述

  • 解题思路:
    这定眼一看就是区间除交集的题目,而且数据量不大,直接用数组映射区间元素即可(具体看代码应该很容易理解)
    前几天看过的各类区间调度问题的总结文章区间调度问题
#include<bits/stdc++.h>
using namespace std;
int check[10001];
int L,M;
int main(){
memset(check,0,sizeof(check));
cin>>L>>M;
for(int i=0;i<M;i++){
    int a,b;
    cin>>a>>b;
    for(int j=a;j<=b;j++){
        check[j]++;
    }
}
//记录要砍多少颗树
int count = 0;
for(int i=0;i<=L;i++)
    if(check[i])
        count++;
cout<<L+1-count;
}

F 比赛安排

在这里插入图片描述

  • 题目分析:注意是输入一个n,然后就确定有了2的n次方个队伍,而需要你用2的n次方-1天来安排比赛。
    这种安排两个队伍进行比赛的问题,很明显就是 C n 2 C^{2}_{n} Cn2求组合的问题。
  • 重点注意两点:
  1. 以天为单位进行输出,每天每支队伍只能比赛一次(故每天的组合需要判断是否已经比过赛,然后再进行组合)
  2. 曾经已经比过赛的队伍,不能再重复进行比赛(所以每天输出的组合不能相同,也就是需要一个记录一对数据的哈希表(当然此处数据量小,可以用大容量数组代替也不会产生哈希冲突))
#include <bits/stdc++.h>
using namespace std;
int main() {
    //其中check1用于标记在该天,该队伍比过赛了,check2用于标记两只队伍已经比过赛了
    //由于数据量都不大,均可用数组代哈希表
    int n, m;
    //check1是用于某天的哈希表,所以换一天的时候需要重置
    //check2则是用于整个天数过程哈希表,故不需重置
    bool check1[1000], check2[10000] = {0};
    cin >> n;
    m = pow(2, n);
//外层循环记录当前为第几天,里层相当于从m个里面取两个进行输出,但要保证前几天已经比过赛的队伍不能输出,且在该天不能出现一只队伍比两次赛
    for (int i = 1; i <= m - 1; i++) {
        memset(check1, 0, sizeof(check1));
        printf("<%d>", i);
        for (int j = 1; j <= m; j++) {
            if (check1[j] == 0) {
                for (int k = j + 1; k <= m; k++) {
                    if (check2[j * 100 + k] == 0 && check1[k] == 0) {
                        printf("%d-%d ", j, k);
                        check2[j * 100 + k] = 1;
                        check1[j] = check1[k] = 1;
                        break;
                    }
                }
            }

        }
        cout << endl;
    }
    return 0;
}

以上是关于暑假训练赛1的主要内容,如果未能解决你的问题,请参考以下文章

比赛-暑假训练赛1 (26 Jul, 2018)

暑假训练12010年湖南省赛题解

暑假训练12010年湖南省赛题解

哈理工2015 暑假训练赛 zoj 2976 Light Bulbs

哈理工2015暑假训练赛 zoj 2078Phone Cell

SCUACM22暑假集训前劝退赛部分题解