zzuli2022新生周赛第5场题解

本场比赛因为出题人对难度判断的失误,导致最终只有50人过三道题。
有一部分原因在于题面,C题的难度其实是低于H题的,只要根据所给代码的注释往前去找点积和激活函数的描述就好了,但是因为题面藏的过深,导致过题很少。
还有一部分原因在于简单题过于靠后,很多人还没有跟榜的习惯,导致被前面的题目劝退。
不多说了,我自觉上烧烤架Orz

比赛链接:http://acm.zzuli.edu.cn/contest.php?cid=1853
题库编号:2980 ~ 2989

补题建议:B(暴力搜索)H(贪心)

A题:
打表,跑一遍所有的祝福值,用数组位置标志,方便以后查询。
然后读入形容词和名次表,排序之后枚举找到长度大于k的一组名称即为答案。

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

const int mod = 114514;

int quick_POW(int x, int y) //快速幂
{
    if(y==0)
        return 1;
    int tmp = quick_POW(x, y/2);
    if(y%2==1)
        return (long long)tmp*tmp%mod*x%mod;
    return (long long)tmp*tmp%mod;
}

int homoit(int x) //祝福函数
{
    return quick_POW(x, x+quick_POW(x, x+mod));
}

int homos[mod*2];

void gethomos() //打表,记录下每个祝福值
{
    for(int i=0;i<mod;i++)
        homos[homoit(i)] = true;
}

int main()
{
    gethomos();
    int n, m, k;
    cin>>n>>m>>k;
    string s;
    vector<string> v1, v2;
    // 将长度是祝福值的单词加入v1,v2
    for(int i=1;i<=n;i++) {
        cin>>s;
        if(homos[s.size()])
            v1.push_back(s);
    }
    for(int i=1;i<=m;i++) {
        cin>>s;
        if(homos[s.size()])
            v2.push_back(s);
    }
    sort(v1.begin(), v1.end());
    sort(v2.begin(), v2.end());
    //枚举v1,v2中的单词,因为已经排序,找到的第一对长度大于k的单词即为答案
    for(string s1:v1)
    for(string s2:v2) {
        if(s1.size()+s2.size()>k) {
            cout<<s1<<' '<<s2<<endl;
            return 0;
        }
    }
    cout<<"None"<<endl;
}

B题
暴力搜索,搜索每种重量的分配可能,然后记录下最大值

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

const int N = 20;
double ans;
double sum[N], power[N], a[N];
int n, m;

void dfs(int idx) //搜索,搜索到第几位用idx表示
{
    if(idx>n) {// 到达递归出口,所有货物都已经被分配给某一个人,开始计算时间
        double  maxtmp= 0;
        for(int i=1;i<=4;i++)
       		// 计算四人中要花费的最大时间
            maxtmp = max(maxtmp, sum[i]*1.0/power[i]);
        // 更新答案
        ans = min(ans, maxtmp);
        return ;
    }
    for(int i=1;i<=4;i++) {
        sum[i]+=a[idx]; // 给第i个人背上编号为idx的哥布林
        dfs(idx+1);// 在第i个人背上编号为idx的哥布林的条件下,搜索后续哥布林的背法
        sum[i]-=a[idx];// 第i个人卸下编号为idx的哥布林,在下轮循环中装给下一个人
    }
}

int main() {
    ans = 1e18;
    for(int i=1;i<=4;i++)
        cin>>power[i];
    cin>>n;
    int sum = 0;
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        sum += a[i];
    }
    dfs(1);
    printf("%.2f", ans);
}

C题
代码补全题,只要看明白了点积的定义,以及激活函数的描述就很好过。
程序实际上模拟的是深度神经网络的输出过程。

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

/*
vector 向量,或者说动态数组的使用方法:
定义一个元素类型为double的动态数组:
vector<double> vec;
向动态数组中加入1到n:
for(int i=1;i<=n;i++)
    vec.push_back(i);
获得动态数组的长度:
int len = vec.size();
遍历并输出动态数组:
for(int i=0;i<len;i++)
    cout<<vec[i]<<' ';
*/

void read_vector(vector<double> &vec, int len)
{
    double x;
    for(int i=1;i<=len;i++) {
        cin>>x;
        vec.push_back(x);
    }
}

void read(vector<vector<double>> &in_paras, int len_in, int len_out)
{
    for(int i=1;i<=len_out;i++) {
        vector<double> in_para;
        read_vector(in_para, len_in);
        in_paras.push_back(in_para);
    }
}

string type;
const double e = 2.718;
double activate(double x)
{
//代码补全部分
    if(type[0] == 'R')
        return max(0.0, x);
    else if(type[0] == 'S') {
        return 1.0 / (1.0 + pow(e, -x));
    }
    return x;
}

double dot(vector<double> &in, vector<double> &in_para)
{
//代码补全部分
    double ans = 0;
    int len = in.size();
    for(int i=0;i<len;i++) ans += in[i] * in_para[i];
    return ans;
}

vector<double> get(vector<double> &in, vector<vector<double>> &in_paras, vector<double> &bias)
{
    vector<double> outs;
    int len_out = in_paras.size();
    for(int i=0;i<len_out;i++) {
        double out = activate(dot(in, in_paras[i])+bias[i]);
        outs.push_back(out);
    }
    return outs;
}

double solve()
{
    int n, m, k;
    cin>>n>>m>>k;
    cin>>type;

    vector<double> in;
    read_vector(in, k);
    vector<vector<double>> in_paras[m+5];

    read(in_paras[1], k, n);
    for(int i=2;i<=m;i++)
        read(in_paras[i], n, n);
    read(in_paras[m+1], n, 1);

    vector<double> bias[m+5];
    for(int i=1;i<=m;i++) read_vector(bias[i], n);
    read_vector(bias[m+1], 1);

    for(int i=1;i<=m+1;i++) {
        in = get(in, in_paras[i], bias[i]);
    }
    return in[0];
}

int main() {
    vector<int> ans;
    double x = solve();
    double y = solve();
    double z = solve();
    if(y>x) swap(x, y);
    if(z>y) swap(z, y);
    if(y>x) swap(x, y);
    printf("%.2f %.2f %.2f\n", x, y, z);
}

D题
将每个哥布林的魔力和运来的时刻记录在一个pair里,然后排序,每次循环数组时,如果当前哥布林的魔力大于等于上次的魔力,且时间小于等于当前的时间,就可以投入魔法阵,更新时间并标记一下哪个哥布林已经被投入魔法阵。循环多次即可得到结果。
ps:出题人建议把出题人投入魔法阵烤了

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

int main() {
    int n, m, x, y;
    cin>>n>>m;
    vector<pair<int, int> > a;
    for(int i=1;i<=n;i++) {
        cin>>x;
        for(int j=1;j<=m;j++) {
            cin>>y;
            // 将魔力值作为第一键值,时间作为第二键值排序
            a.push_back(pair<int, int>(y,x));
        }
    }
    sort(a.begin(),a.end());
    int cnt = 0, last = 0, t = 0, fir = 1;
    vector<int> used(n*m+5,0);
    while(cnt<n*m) {
        int op = 0;
        for(int i=0;i<a.size();i++) {
            pair<int, int> x = a[i];
            if(used[i]) continue;
            // 如果符合条件,则投入法阵,更新used数组,更新时间t,更新当前魔力值last
            if(x.first>=last&&x.second<=t) {
                last = x.first;
                t += x.first;
                op = 1;
                used[i] = 1;
                if(fir) fir = 0;
                else cout<<' ';
                cout<<x.first;
                cnt++;
            }
        }
        if(!op) {
            if(last)
                last = 0;
            else
                t++;
        }
    }
    cout<<endl;
}

E题
枚举题,枚举每两个队列,尝试交换,然后记录下最大值。

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

struct node {
    int a[20], n;
    void input(int len) {
        n = len;
        for(int i=1;i<=n;i++)
            cin>>a[i];
    }
    int get_max() {// 得到当前队列的队伍最高者
        int ans = 0;
        for(int i=1;i<=n;i++)
            ans = max(ans, a[i]);
        return ans;
    }
    int can_be_seen(int h) {// 得到对于前面最高的人是h的情况下,该队列有多少人能被看见
        int cnt = 0;
        for(int i=1;i<=n;i++)
            if(a[i]>h) {
                h = a[i];
                cnt++;
            }
        return cnt;
    }
};

node a[55][55];

int n, m;

int get_ans()
{
    int cnt = 0;
    for(int j=1;j<=m;j++) {
        int h = 0;
        for(int i=1;i<=m;i++) {
            cnt += a[i][j].can_be_seen(h);
            h = max(h, a[i][j].get_max());// 更新下当前列的最大值
        }
    }
    return cnt;
}

int main()
{
    cin>>n>>m;
    int ans = 0;
    for(int i=1;i<=m;i++)
        for(int j=1;j<=m;j++)
            a[i][j].input(n);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=m;j++)
            for(int ii=1;ii<=m;ii++)
                for(int jj=1;jj<=m;jj++) {
                    swap(a[i][j], a[ii][jj]);// 交换任意两个队列
                    ans = max(ans, get_ans());
                    swap(a[i][j], a[ii][jj]);// 换回来
                }
    cout<<ans<<endl;
}

F题
这道题这里给出的标称是二分+搜索的写法,每次二分出一个限制的时间,然后dfs搜索能否在给定的时间内跑到房间n,由于从房间1到房间n至少存在一个小于114514的解,而且n数量很小,所以搜索是可解的。
这题同样可以用动态规划去写,用t,u分别代表当前时刻和当前位置,dp[t][u] = true表示t,u状态存在。
转移公式:if(dp[t][u]) dp[t+cost(u+v+t)][v] = true

#include<bits/stdc++.h>
using namespace std;
 
const int N = 5e4 + 5;
int n,m,firT;
const int mod = 114514;
int b[N*10];
 
long long poww(int x,int y)
{
    if(y == 0) return 1;
    long long tmp = poww(x, y>>1);
    if(y&1) return tmp*tmp*x%mod;
    return tmp*tmp%mod;
}
 
int homoit(int x)
{
    return poww(x, x+poww(x, x+114514));
}
 
bool dfs(int u, int time, int nowt=0)// 限制时间的搜索
{
    if(time<0)
        return false;
    if(u == n)
        return true;
    for(int v=n;v>=1;v--) {
        int dis = homoit(nowt+u+v) + 1;
        if(dfs(v, time-dis, nowt+dis))
            return true;
    }
    if(dfs(u, time-1, nowt+1))
        return true;
    return false;
}
 
int main()
{
    cin>>n>>firT;
    int l = 0, r = 114514;
    int ans = 0;
    // 二分答案
    while(l<=r) {
        int mid = (l+r) >> 1;
        if(dfs(1, mid, firT)) ans = mid, r = mid - 1;// 搜索在mid时间内能否搜索到可行解
        else l = mid + 1;
    }
    cout<<ans<<endl;
}

G题
基本上算是一道模拟题,将时刻表打出来后,跟着伪代码把代码复现出来就好了,并不需要真正读懂伪代码。

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

const int N = 5e4 + 5;
int n,m,x,y,z,w,k,t;
char ch;
const int mod = 114514;
int a[N];
const int MAX = 1e18 + 7;

int hidecode;

long long poww(int x, int y)
{
    if(y==0) return 1;
    long long tmp = poww(x, y>>1);
    if(y&1) return tmp*tmp%mod*x%mod;
    return tmp*tmp%mod;
}

int homoit(int x) {
    return poww(x, x+poww(x, x+114514));
}

// 实际上就是把now(),receive(),call("success")替换掉就可以了
void dec(int now, int t_end)
{
    int u = a[now], fakeu;
    for(int i=1;i<=100;i++) {
        if(homoit(i+now+hidecode)==homoit(u+now+hidecode)) {
            fakeu = i;
            break;
        }
    }
    now += fakeu;// 更新循环所费时间
    if(now>t_end) {
        cout<<"None"<<endl;
        return;
    }
    if(homoit(now+fakeu+hidecode)==homoit(now+a[now]+hidecode)) {
        cout<<now<<endl;// call("success")
        return ;
    }
    dec(now, t_end);
}

int main() {
    cin>>n>>hidecode;
    for(int i=0;i<=10000;i++) a[i] = 0;
    for(int i=1;i<=n;i++) {
        cin>>t;
        cin>>a[t];
    }
    for(int i=1;i<=10000;i++)
        if(!a[i]) a[i] = a[i-1]; // 打印时刻表,把每个时刻在哪个位置都存起来
    cin>>t;
    dec(0, t);
}

H题
贪心题,从后往前贪心的选择让公主的血量最大,同时保证不大于后面一位的操作。

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

int n,m,x,y,z,w,k,t;
const int mod = 114514;
int a[mod],b[mod];

int main() {
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n;i++)
        cin>>b[i];
    a[n+1] = 1<<31;
    long long sum = 0;
    for(int i=n;i;i--) {// 从后往前,选择使公主血量最大的操作
        int tmp = b[i];// 操作三一定合法
        if(a[i]+1<=a[i+1])// 判断操作一是否合法
            tmp = max(a[i]+1, tmp);
        if(a[i]<=a[i+1])// 判断操作二是否合法
            tmp = max(a[i], tmp);
        a[i] = tmp;
        sum += tmp + b[i];
    }
    cout<<sum<<endl;
}

I题
循环解密,考察了循环读入

#include<bits/stdc++.h>
using namespace std;
 
int main()
{
    char s[200];
    scanf("%s", s);
    int len = strlen(s);
    int cnt = 0, x;
    while(~scanf("%d", &x)) {
        char ch = s[cnt%len] + x;
        putchar(ch);
        cnt++;
    }
}
  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值