首页 > 编程语言 >上海市中小学生人工智能算法设计复赛 - 高中组 (S) 比赛游记

上海市中小学生人工智能算法设计复赛 - 高中组 (S) 比赛游记

时间:2022-12-25 20:00:38浏览次数:63  
标签:中小学生 10 人工智能 dp rank int long Hack 复赛

比赛链接(已结束):戳我打开

比赛时间:15:30~19:00。

估了好久的补偿赛,感觉题目质量还行吧。

第一题:

看到之后居然感觉无从下手?

有了最小的数据范围 $m\leq10$ 之后,想到枚举每个人是否 $Hack$。

打算写之前随便特判了几组数据,然后发现有人已经 $A$ 了?

想写的念头就没了,接着发现 $T \leq 10^7$,算法应该是 $T \log_m$ 的样子吧。

然后还是没什么思路,写了个动态规划。

$f[i]$ 表示花 $i$ 个时间做题能拿到的最大分数,就是个 $0/1$ 背包。

感觉写出来之后是否 $Hack$ 就出来了。

结果发现真的出来了,直接枚举花多长时间做题,剩下的时间 $Hack$ 就行。

但是花这么多时间 $Hack$ 能得到多少的排名呢?

还是需要一个动态规划, $f[i][j]$ 表示前 $i$ 名中 $Hack$ $j$ 名的最少时间花费。

状态转移方程为 $f[i][j]=min(f[i - 1][j], f[i - 1][j - 1] + t[i])$。

二分就行了,时间复杂度为 $O(t\times n + T\times log_m)$,直接提交发现 $A$ 了。

$A$ 了之后才知道会爆啊,洛谷机子是真的快,$n$ 方过百万。

考试结束后听同学说应该把分数作为维度算,反正过了就行了呗(\doge。

考场代码:

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n, m, _t, ans = 0x3f3f3f3f;
int f[10000005], dp[505][505];//f[i]:花费i时间做题所能得到的最大分数
//dp[i][j]:前i个人中Hackj个人所需要花费的最少时间
struct problem {
    int t, s;
}a[110];
struct people {
    int t, s;
}b[510];
bool cmp (people p1, people p2) {
    return p1.s > p2.s || p1.s == p2.s && p1.t < p2.t;
}
int main () {
    scanf ("%d%d%d", &n, &m, &_t);
    for (int i = 1; i <= n; i ++) scanf ("%d%d", &a[i].t, &a[i].s);
    for (int i = 1; i <= m; i ++) scanf ("%d%d", &b[i].t, &b[i].s);
    sort (b + 1, b + m + 1, cmp);
    for (int i = 1; i <= n; i ++) for (int j = _t; j >= 0; j --) if (j >= a[i].t) f[j] = max(f[j], f[j - a[i].t] + a[i].s);
    memset (dp, 0x3f, sizeof dp);
    dp[1][0] = 0;
    dp[1][1] = b[1].t;
    for (int i = 2; i <= m; i ++) {
        dp[i][0] = 0;
        for (int j = 1; j <= i; j ++)
            dp[i][j] = min(dp[i - 1][j], dp[i - 1][j - 1] + b[i].t);
    }
    for (int i = 0; i <= _t; i ++) {//枚举花费多长的时间做题
        int score = f[i], rank;//所能够得到的分数
        int l = 1, r = m, mid;//二分查找该分数的排名 
        while (l <= r) {
            mid = l + r >> 1;
            if (b[mid].s > score) l = mid + 1;
            else r = mid - 1;
        }
        rank = r + 1;//当前这个人的排名
        int t_ = _t - i;//剩下的可以Hack别人的时间
        l = 1;
        r = rank - 1;//二分查找最多能Hack前rank名中的几名
        while (l <= r) {
            mid = l + r >> 1;
            if (dp[rank - 1][mid] > t_) r = mid - 1;//需要更多的时间,无法Hack 
            else l = mid + 1;//可以Hack,扩大左边界 
        }
        ans = min (ans, rank - r);//可以Hack r名,排名由rank名变为rank-r名 
    }
    printf ("%d", ans);
    return 0;
}

 

第二题:

方格路径加强版,考前没复习,打了 $n \leq 10^5$ 的 $40$ 分暴力就没管了。

状态转移方程:$f[1][i] = f[1][i - 1] + f[2][i - 1]$

$f[i][j] = f[i - 1][j - 1] + f[i][j - 1] + f[i + 1][j - 1] (2 \leq i \leq 5)$

$f[6][i] = f[5][i - 1] + f[6][i - 1]]$

如果有障碍物,$f[i][j]=0$

后来看到有些人拿了 $60$ 分,以为拿的是障碍物一格的那个。

仔细一看数据范围有个 $n \leq 10^{18}$,$m \leq 0$,就是个递推啊。

写了个矩阵乘法 $6^3\times log_n$ 考试结束前 $12$ 分钟拿了 $60$ 分。

考场 $60$ 分代码:

#include <iostream>
using namespace std;
const int mod = 1e9 + 7;
long long n, m;
long long x1, y1, x2, y2;
long long f[7][100005], ans[7][7], pre[7][7] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1};
bool a[7][100010];
void time (long long a[7][7], long long b[7][7]) {
    long long c[7][7] = {0};
    for (int i = 1; i <= 6; i ++)
        for (int k = 1; k <= 6; k ++)
            for (int j = 1; j <= 6; j ++)
                c[i][k] = (c[i][k] + a[i][j] * b[j][k] % mod) % mod;
    for (int i = 1; i <= 6; i ++)
        for (int j = 1; j <= 6; j ++) a[i][j] = c[i][j];
}
int main () {
    cin >> n >> m;
    if (m == 0) {
        if (n == 1) {
            cout << 6;
            return 0;
        }
        else if (n == 2) {
            cout << 16;
            return 0;
        }
        for (int i = 1; i <= 6; i ++)
            for (int j = 1; j <= 6; j ++) ans[i][j] = 1;
        m = n - 2;
        while (m) {
            if (m & 1) time (ans, pre);
            time (pre, pre);
            m >>= 1;
        }
        cout << (ans[1][1] + ans[1][2] + ans[2][1] + ans[2][2] + ans[2][3] + ans[3][2] + ans[3][3] + ans[3][4] + ans[4][3] + ans[4][4] + ans[4][5] + ans[5][4] + ans[5][5] + ans[5][6] + ans[6][5] + ans[6][6]) % mod;
    }
    else {
        for (int i = 1; i <= m; i ++) {
            cin >> x1 >> y1 >> x2 >> y2;
            for (long long j = x1; j <= x2; j ++)
                for (long long k = y1; k <= y2; k ++)
                    a[j][k] = true;
        }
        f[1][1] = f[2][1] = f[3][1] = f[4][1] = f[5][1] = f[6][1] = 1;
        for (int j = 2; j <= n; j ++) {
            if (! a[1][j]) f[1][j] = (f[1][j - 1] + f[2][j - 1]) % 1000000007;
            for (int i = 2; i <= 5; i ++)
                if (! a[i][j]) f[i][j] = (f[i - 1][j -  1] + f[i][j - 1] + f[i + 1][j - 1]) % 1000000007;
            if (! a[6][j]) f[6][j] = (f[5][j - 1] + f[6][j - 1]) % 1000000007;
        }
        cout << ( ( (f[1][n] + f[2][n]) % 1000000007 + (f[3][n] + f[4][n]) % 1000000007) % 1000000007 + (f[5][n] + f[6][n]) % 1000000007) % 1000000007;
    }
    return 0;
}

 

第三题:

毒瘤题,考试的时候以为是结论题,写了不对。

以为是二分题,写一大会儿不出。

然后写 $2^{20}$ 爆搜枚举每一次左拐还是右拐还 $WA$ 了。

发现 $check$ 没查完整地图,查了 $9^4$,改成 $10^4$ 之后 $T$ 了?

就加了一个”随机性剪枝“,反正合法方案肯定很多,如果 $rand() % 107 == 1$ 就 $return$  掉。

有一个点正好 $999ms$ 卡过,于是就得了 $10$ 分。

第四题:

我打 $2$ 年 $OI$ 总结出来的规律,题目背景很简单的又很难打的就是难题。

于是打了 $5$ 分暴力,后来发现可以打 $O(m\times n) $ 的算法,就求个交集。

然后打出来就 $15$ 分了。

总共:$100+60+10+15=185$ 分,$rank$ $44$,还可以吧。

$wjh(Graygoo):100+100+0+10=210$ 分,$rank$ $29$,%%%

$mxt(Mr_Imposter):20+100+0+0=120$ 分,$rank$ $107$

$yzf(Yang818):60+40+0+15=115$ 分,$rank$ $111$

$wzy(UFO2007):60+40+0+5=105$ 分,$rank$ $117$

标签:中小学生,10,人工智能,dp,rank,int,long,Hack,复赛
From: https://www.cnblogs.com/Xy-top/p/17004445.html

相关文章