SX【2020.01.09】NOIP提高组模拟赛(day1)
阅读原文时间:2023年07月08日阅读:2

【2020.01.09】NOIP提高组模拟赛(day1)

这次考得不理想,只做了前两题,后两题没时间做,说明做题速度偏慢。

 source : 100 + 20 + 0 + 0 = 120 rank7

十分不友好。

\(T1\)

传送门:[USACO17JAN]Cow Dance Show奶牛舞蹈

分析

一道清新的水题,很容易想到二分答案,易证满足单调性。

然后考虑可以接受的 \(check\)

根据时间维护一个小根堆,取堆首时判断。

然后——切了

代码略

\(T2\)

传送门:[USACO17JAN]Hoof, Paper, Scissor蹄子剪刀…

一道本应该是没多难的 \(DP\) ,然而考场时漏考虑一种转移,直接炸到 \(20\) 分。

根据题意,容易想到设状态 \(f_{i,j,k}\) 表示到第 \(i\) 回合,有改变 \(j\) 次的机会,且当前出 \(k\) (H/S/P,设为 \(1/2/3\) )

考虑转移:

设 \(p\) ,\(q\) 表示不同于 \(k\) 的另外两个手势,\(win[i][j]\) 表示手势 \(i\) 能赢手势 \(j\) ,\(a[i]\) 表示当前 \(FJ\) 的手势 则有:

\[f[i][j][k] = max(f[i-1][j-1][p] , f[i-1][j-1][q] , f[i-1][j][k]) + win[k][a[i]]
\]

代码

#include<cstdio>
#include<iostream>
using namespace std;

const int N = 1e5;
int n , k , a[N + 5] , f[N + 5][25][4] , ans = 0 , p , q;
char ch;

int data[4][4];

int main()
{
//    freopen("hps.in" , "r" , stdin);
//    freopen("hps.out" , "w" , stdout);
    scanf("%d%d" , &n , &k);
    for(register int i = 1; i <= n; i++)
    {
        ch = getchar();
        while (ch != 'H' && ch != 'P' && ch != 'S') ch = getchar();
        if (ch == 'H') a[i] = 1;
        else if (ch == 'P') a[i] = 2;
        else if (ch == 'S') a[i] = 3;
    }
    data[1][3] = data[2][1] = data[3][2] = 1;
    for(register int i = 1; i <= n; i++)
        for(register int j = 1; j <= 3; j++)
            f[i][0][j] = f[i - 1][0][j] + data[j][a[i]];
    for(register int i = 1; i <= n; i++)
        for(register int j = 1; j <= k; j++)
            for(register int l = 1; l <= 3; l++)
            {
                if (l == 1) p = 2 , q = 3;
                else if (l == 2) p = 1 , q = 3;
                else if (l == 3) p = 1 , q = 2;
                f[i][j][l] = max(f[i - 1][j][l] + data[l][a[i]], max(f[i - 1][j - 1][p] + data[l][a[i]] , f[i - 1][j - 1][q] + data[l][a[i]]));
            }
    for(register int i = 0; i <= k; i++)
        for(register int j = 1; j <= 3; j++)
            ans = max(f[n][i][j] , ans);
    printf("%d" , ans);
}

没了······

\(T3\)

传送门:[USACO17JAN]Cow Navigation奶牛导航

一道不是很难但码量大,细节多的题

\(tag\):用 \(BFS\) 模拟做 \(DP\)

详细解析如下:

先转化:因为读入的地图缘故,所以我们设起点 \((n,1)\),终点 \((1,n)\)

有两头奶牛,分别在 \((n,1)\) 处面向两个方向

且要注意:到终点的奶牛无视任何新增命令,不管另一头牛怎样,他都不动;若当前指令有一头牛会越界,也不动。(详细认真看题目)

设 \(f_{x1,y1,d1,x2,y2,d2}\) 表示牛一到达 \((x1,y1)\) 面向 \(d1\) ,牛二到达 \((x2,y2)\) 面向 \(d2\) 时的指令最短长度

因指令每个对答案的贡献都是 \(1\) ,所以可以用 \(BFS\) 来做最短路 ,边扩展边更新 \(dis\)

但实际上可以优化空间,那就是弄掉 \(d2\) 那一维

很好想,因为两头牛接受同一个指令,他们的方向是相对的,知道其中一头,就能知道另一头,取决于一开始的朝向

代码

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

const int N = 20;

int n , map[N + 5][N + 5] , head , tail , ans = 1e5;
int f[N + 5][N + 5][4][N + 5][N + 5][4] , vis[N + 5][N + 5][4][N + 5][N + 5][4];
int fx[4][2] = { -1 , 0 , 0 , 1 , 1 , 0 , 0 , -1 };
char ch;

struct node{
    int x1 , y1 , d1 , x2 , y2 , d2;
}q[N * N * N * N * 16 + 10];

inline bool check(int x , int y , int xx , int yy)
{
    return (x <= 0 || y <= 0 || x > n || y > n || map[x][y] == 1 || (xx == 1 && yy == n));
}

int main()
{
//     freopen("cow.in" , "r" , stdin);
//    freopen("cow.out" , "w" , stdout);

    scanf("%d" , &n);
    for(register int i = 1; i <= n; i++)
        for(register int j = 1; j <= n; j++)
        {
            while (ch != 'E' && ch != 'H') ch = getchar();
            if (ch == 'H') map[i][j] = 1;
            ch = getchar();
        }
    q[++tail] = (node){n , 1 , 0 , n , 1 , 1};
    memset(f , 0x3f , sizeof(f));
    f[n][1][0][n][1][1] = 0;
    vis[n][1][0][n][1][1] = 1;
    while (head < tail)
    {
        struct node now = q[++head];
        int x1 , y1 , d1 , x2 , y2 , d2;

        d1 = now.d1 , d2 = now.d2;
        x1 = now.x1 + fx[d1][0] , y1 = now.y1 + fx[d1][1];
        if (check(x1 , y1 , now.x1 , now.y1)) x1 = now.x1 , y1 = now.y1;
        x2 = now.x2 + fx[d2][0] , y2 = now.y2 + fx[d2][1];
        if (check(x2 , y2 , now.x2 , now.y2)) x2 = now.x2 , y2 = now.y2;
        if (f[now.x1][now.y1][now.d1][now.x2][now.y2][now.d2] + 1 < f[x1][y1][d1][x2][y2][d2] && vis[x1][y1][d1][x2][y2][d2] == 0)
        {
            q[++tail] = (node){x1 , y1 , d1 , x2 , y2 , d2};
            f[x1][y1][d1][x2][y2][d2] = f[now.x1][now.y1][now.d1][now.x2][now.y2][now.d2] + 1;
            vis[x1][y1][d1][x2][y2][d2] = 1;
        }

        d1 = (now.d1 + 1) % 4 , d2 = (now.d2 + 1) % 4;
        x1 = now.x1 , y1 = now.y1 , x2 = now.x2 , y2 = now.y2;
        if (f[now.x1][now.y1][now.d1][now.x2][now.y2][now.d2] + 1 < f[x1][y1][d1][x2][y2][d2] && vis[x1][y1][d1][x2][y2][d2] == 0)
        {
            q[++tail] = (node){x1 , y1 , d1 , x2 , y2 , d2};
            f[x1][y1][d1][x2][y2][d2] = f[now.x1][now.y1][now.d1][now.x2][now.y2][now.d2] + 1;
            vis[x1][y1][d1][x2][y2][d2] = 1;
        }

        d1 = (now.d1 + 3) % 4 , d2 = (now.d2 + 3) % 4;
        x1 = now.x1 , y1 = now.y1 , x2 = now.x2 , y2 = now.y2;
        if (f[now.x1][now.y1][now.d1][now.x2][now.y2][now.d2] + 1 < f[x1][y1][d1][x2][y2][d2] && vis[x1][y1][d1][x2][y2][d2] == 0)
        {
            q[++tail] = (node){x1 , y1 , d1 , x2 , y2 , d2};
            f[x1][y1][d1][x2][y2][d2] = f[now.x1][now.y1][now.d1][now.x2][now.y2][now.d2] + 1;
            vis[x1][y1][d1][x2][y2][d2] = 1;
        }
    }
    for(register int i = 0; i < 4; i++)
        for(register int j = 0; j < 4; j++)
            ans = min(ans , f[1][n][i][1][n][j]);
    printf("%d" , ans);
}

\(T4\)

传送门:[USACO17JAN]Subsequence Reversal序列反转

解析如下:

似乎无从下手的题,其实是个很套路的区间 \(DP\)

根据数值只有 \(1~50\) 的特性,我们可以用神奇的操作弄掉所谓的翻转

废话不多说,且看

设 \(f_{i,j,down,up}\) 表示区间 \([i,j]\) ,数值取值范围 \([down,up]\) 时的最长子序列长度

然后区间 \(DP\) 套路转移,但两个区间是套在一起转移的

代码

#include<cstdio>
#include<iostream>
using namespace std;

const int N = 50;
int n , f[N + 5][N + 5][N + 5][N + 5] , a[N + 5] , Mx;

int main()
{
//    freopen("reversal.in" , "r" , stdin);
//    freopen("reversal.out" , "w" , stdout);
    scanf("%d" , &n);
    for(register int i = 1; i <= n; i++)
    {
        scanf("%d" , &a[i]);
        for(register int down = 1; down <= a[i]; down++)
            for(register int up = a[i]; up <= 50; up++)
                f[i][i][down][up] = 1;
    }
    for(register int len1 = 2; len1 <= n; len1++)
        for(register int i = 1; i <= n - len1 + 1; i++)
        {
            register int j = i + len1 - 1;
            for(register int len2 = 2; len2 <= 50; len2++)
                for(register int down = 1; down <= 51 - len2; down++)
                {
                    register int up = down + len2 - 1 , Mx;
                    Mx = max(f[i][j][down + 1][up] , f[i][j][down][up - 1]);
                    Mx = max(f[i + 1][j][down][up] + (a[i] == down) , Mx);
                    Mx = max(f[i][j - 1][down][up] + (a[j] == up) , Mx);
                    Mx = max(f[i + 1][j - 1][down][up] + (a[j] == down) + (a[i] == up) , Mx);
                    f[i][j][down][up] = Mx;
                }
        }
    printf("%d" , f[1][n][1][50]);
}