幻想乡模拟赛S1试题+题解+标程

Problem 1 东风谷早苗(robot.cpp/c/pas)
题目描述 在幻想乡,东风谷早苗是以高达控闻名的高中生宅巫女。某一天,早苗终于入手了最新款的钢达姆模型。作为最新的钢达姆,当然有了与以往不同的功能了,那就是它能够自动行走,厉害吧(好吧,我自重)。早苗的新模型可以按照输入的命令进行移动,命令包含’E’、’S’、’W’、’N’四种,分别对应四个不同的方向,依次为东、南、西、北。执行某个命令时,它会向着对应方向移动一个单位。作为新型机器人,自然不会只单单执行一个命令,它可以执行命令串。对于输入的命令串,每一秒它会按照命令行动一次。而执行完命令串最后一个命令后,会自动从头开始循环。在0时刻时早苗将钢达姆放置在了(0,0)的位置,并且输入了命令串。她想要知道T秒后钢达姆所在的位置坐标。
输入格式 第1行:一个字符串,表示早苗输入的命令串,保证至少有1个命令
第2行:一个正整数T
输出格式 第1行:两个整数,表示T秒时,钢达姆的坐标
输入样例 NSWWNSNEEWN
12
输出样例 -1 3
数据范围 对于60%的数据:T <= 500,000且命令串长度 <= 5,000
对于100%的数据:T <= 2,000,000,000且命令串长度<= 5,000
注意 向东移动,坐标改变改变为(X+1,Y);
向南移动,坐标改变改变为(X,Y-1);
向西移动,坐标改变改变为(X-1,Y);
向北移动,坐标改变改变为(X,Y+1);

Robot 东风谷早苗
题目大意 读入一串长度为N的命令字符’N’、’E’、’W’、’S’。每一秒钟按照命令移动一次,不断重复,求问T秒之后,所在位置的坐标。起始位置为(0,0),地图无穷大。
考察算法 字符串处理 数学题
算法一 从第0秒开始模拟每一秒的位置。
时间复杂度:O(T) 期望得分:60
算法二 由于命令串是重复执行的,所以我们可以先计算出一个周期后机器人的移动量。比如在第一个周期后,机器人从(0,0)移动到(△X,△Y),那么每个周期之后机器人坐标的改变为(X+△X,Y+△Y)。设T/N = A…B,那么机器人一共会运行的周期数为A,所以在A个周期后,机器人的坐标为(A*△X,A*△Y)。之后机器人还会移动B步,此时就可以直接在(A*△X,A*△Y)的基础上枚举这B步的移动。
时间复杂度:O(N) 期望得分:100

#include <iostream>
using namespace std;
#define MAXL 5009
const int dr[4][2] = {{1, 0}, {0, -1}, {-1, 0}, {0, 1}};
char command[ MAXL ];
int TimeLimit;
int main()
{
    scanf("%s", command);
    scanf("%d", &TimeLimit);
    int cycle, rest, times, order, delta[2], last[2];
    cycle = strlen( command );
    rest = TimeLimit % cycle;
    times = TimeLimit / cycle;
    delta[0] = delta[1] = 0;
    last[0] = last[1]= 0;
    for (int i = 0; i < cycle; i++)
    {
        switch ( command[i] )
        {
            case 'E' : order = 0; break;
            case 'S' : order = 1; break;
            case 'W' : order = 2; break;
            case 'N' : order = 3; break;
            default : break;
        }
        for (int j = 0; j <= 1; j++)
        {
            delta[j] += dr[ order ][j];
            if (i < rest) last[j] += dr[ order ][j];
        }
    }
    for (int j = 0; j <= 1; j++)
        last[j] += times * delta[j];
    printf("%d %d\n", last[0], last[1]);
    return 0;
}

Problem 2 西行寺幽幽子(spring.cpp/c/pas)
题目描述 在幻想乡,西行寺幽幽子是以贪吃闻名的亡灵。不过幽幽子可不是只会吃,至少她还管理着亡灵界。话说在幽幽子居住的白玉楼有一颗常年不开花的樱树——西行妖。幽幽子决定去收集人间的春度,聚集起来让西行妖开花。很快,作为幽幽子家园艺师的魂魄妖梦收集到了M个单位的春度。并且在这段时间里,幽幽子计算出要让西行妖开出一朵花需要N个单位的春度。现在幽幽子想要知道,使用所有的春度,能够让西行妖开出多少朵花。
输入格式 第1行:一个正整数M
第2行:一个正整数N
N,M的位数不超过L,L的范围在题目后面给出
输出格式 第1行:一个整数ans,表示能开出花的朵数
输入样例 73861758
12471
输出样例 5922
数据范围 对于60%的数据:L <= 2,000且ans <= 2,000
对于100%的数据:L <= 20,000且ans <= 2,000,000,000

Spring 西行寺幽幽子
题目大意 高精度数N除以高精度数M,求商
考察算法 高精度数 二分答案/二进制拆分
算法一 从0开始枚举累加,直到当前值超过了N。此算法只需要使用加法和比较。
对于这种算法是否使用压位高精度给分相同。
时间复杂度:O(AnsL) 期望得分:60
算法二 在枚举商的时候采用二分答案的方法。如果M和Mid的乘积小于或等于N,Check的值为True;否则为False。二分的伪代码如下:
While (Left + 1 < Right)
Begin
Mid <- (Left + Right) / 2;
if Check(Mid) Then Left <- Mid;
Else Right <- Mid;
End
时间复杂度:O(LlogAns) 期望得分:100
算法三 因为Ans可以转化成二进制的形式,建立高精度数组t[32],t[i] = 2^i * M。从高位向低位进行枚举,如果对于当前位k,N >=t[k],那么Ans加上2^k,N减去t[k]。其本质相当于Ans = 2^p[1 +2^p[2]+ … +2^p[j],而N = 2^p[1] *M+2^p[2]*M+…+2^p[j]*M+rest(rest < M),p数组是将Ans表示为二进制后该位为1的位置。比如5(10) = 101(2),那么5所对应的p数组为{1,3}。
由于我们需要求的是Ans的值,所以必须从高位向低位枚举,否则会出现错误。如果有疑问,读者可以尝试从低位向高位枚举,看看输出的结果。
时间复杂度:O(LlogAns) 期望得分:100

#include <iostream>
using namespace std;
#define MAXL 20009
typedef int hp[ MAXL ];
char str[ MAXL ];
hp first, second, now, t[ 32 ];
inline int max(int x, int y)
{
    return x > y ? x : y;
}
struct HP
{
    hp box;
    void Str_to_hp(char *str, hp &a)
    {
        Set(a);
        a[0] = strlen( str );
        for (int i = 1; i <= a[0]; i++)
            a[i] = str[ a[0] - i ] - '0';
        return ;
    }
    void Set(hp &a)
    {
        memset(a, 0, sizeof(a));
        a[0] = 1;
        return ;
    }
    int Compare(hp a, hp b)
    {
        if (a[0] > b[0]) return  1;
        if (a[0] < b[0]) return -1;
        for (int i = a[0]; i >= 1; i--)
        {
            if (a[i] > b[i]) return  1;
            if (a[i] < b[i]) return -1;
        }
        return 0;
    }
    void Add(hp a, hp b, hp &c)
    {
        memset(box, 0, sizeof(box));
        box[0] = max(a[0], b[0]);
        for (int i = 1; i <= box[0]; i++)
            box[i] = a[i] + b[i];
        box[0]++;
        for (int i = 1; i <= box[0]; i++)
            if (box[i] >= 10)
                box[i + 1]++, box[i] %= 10;
        while (!box[ box[0] ] && box[0] > 1) box[0]--;
        memcpy(c, box, sizeof(c));
        return ;
    }
    void Sub(hp a, hp b, hp &c)
    {
        memset(box, 0, sizeof(box));
        box[0] = a[0];
        for (int i = 1; i <= box[0]; i++)
            box[i] = a[i] - b[i];
        for (int i = 1; i <= box[0]; i++)
            if (box[i] < 0)
                box[i + 1]--, box[i] += 10;
        while (!box[ box[0] ] && box[0] > 1) box[0]--;
        memcpy(c, box, sizeof(box));
        return ;
    }
}    Op;
int main()
{
    int ans = 0;
    scanf("%s", str);
    Op.Str_to_hp(str, first);
    scanf("%s", str);
    Op.Str_to_hp(str, second);
    memcpy(t[1], second, sizeof(t[1]));
    for (int i = 2; i <= 31; i++)
        Op.Add(t[i - 1], t[i - 1], t[i]);
    for (int i = 31; i >= 1; i--)
        if (Op.Compare(first, t[i]) == 1)
        {
            ans |= 1 << (i - 1);
            Op.Sub(first, t[i], first);
        }
    printf("%d\n", ans);
    return 0;
}

Problem 3 琪露诺(iceroad.cpp/c/pas)
题目描述 在幻想乡,琪露诺是以笨蛋闻名的冰之妖精。某一天,琪露诺又在玩速冻青蛙,就是用冰把青蛙瞬间冻起来。但是这只青蛙比以往的要聪明许多,在琪露诺来之前就已经跑到了河的对岸。于是琪露诺决定到河岸去追青蛙。小河可以看作一列格子依次编号为0到N,琪露诺只能从编号小的格子移动到编号大的格子。而且琪露诺按照一种特殊的方式进行移动,当她在格子i时,她只会移动到i+L到i+R中的一格。你问为什么她这么移动,这还不简单,因为她是笨蛋啊。每一个格子都有一个冰冻指数A[i],编号为0的格子冰冻指数为0。当琪露诺停留在那一格时就可以得到那一格的冰冻指数A[i]。琪露诺希望能够在到达对岸时,获取最大的冰冻指数,这样她才能狠狠地教训那只青蛙。但是由于她实在是太笨了,所以她决定拜托你帮它决定怎样前进。开始时,琪露诺在编号0的格子上,只要她下一步的位置编号大于N就算到达对岸。
输入格式 第1行:3个正整数N, L, R
第2行:N+1个整数,第i个数表示编号为i-1的格子的冰冻指数A[i-1]
输出格式 第1行:一个整数,表示最大冰冻指数。保证不超过2^31-1
第2行:空格分开的若干个整数,表示琪露诺前进的路线,最后输出-1表示到达对岸
输入样例 5 2 3
0 12 3 11 7 -2
输出样例 11
0 3 -1
数据范围 对于60%的数据:N <= 10,000
对于100%的数据:N <= 200,000
对于所有数据 -1,000 <= A[i] <= 1,000且1 <= L <= R <= N
注意 此题采用Special Judge

Iceroad 琪露诺
题目大意 存在一行格子,有各自的分值。从当前一格可以移动到前面L到R范围内的格子,并取得当前分值。求从左到右的最大得分即方案。
考察算法 Dp 优先队列/单调队列
算法一 从1到N行进行枚举,状态转移方程为:
f[i] = Max{f[j]} + A[i]; (i – R <= j <= i - L)
最后取从f[N – R + 1]到f[N]中的最大值。
方案的输出可采用数组prev[]记录,prev[i]记录计算f[i]时所用的j的值。
时间复杂度:O(N^2) 期望得分:60
算法二 在算法一的基础上使用优先队列或线段树取出[i – R, i – L]中的最大值,具体实现这里就不说明了。在标程中,优先队列使用的是大根堆。
时间复杂度:O(NlogN) 期望得分:100
算法三
维护一个在[i-R,i-L]内f[i]单调递减的决策队列:Q[i]记录决策位置,当队首元素位置小于i-R时队首出队,当f[i-l+1]优于队尾决策时队尾出队,然后将新的f[i-l+1]加入队尾。易知此时f[Q[head]]即为Max{f[j]|i-R <= j <= i-L},f[i]可直接通过队首位置转移求得,转移代价为O(1)。
(此解法由Wagner提供,具体实现pas)
时间复杂度:O(N) 期望得分:100

#include <iostream>
using namespace std;
#define MAXN 200009
#define Inf 999999999
bool step[ MAXN ];
int ice[ MAXN ], prev[ MAXN ], f[ MAXN ];
int N, L, R;
struct HEAP
{
    struct DATA
    {
        int from, key;
    }    heap[ MAXN ];
    int pos[ MAXN ];
    int size;
    void Swap(int x, int y)
    {
        DATA box;
        box = heap[x]; heap[x] = heap[y]; heap[y] = box;
        pos[ heap[x].from ] = x;
        pos[ heap[y].from ] = y;
        return ;
    }
    void HeapUp(int x)
    {
        int tp;
        while (x != 1)
        {
            tp = x >> 1;
            if (heap[x].key < heap[tp].key) break;
            Swap(tp, x);
            x = tp;
        }
        return ;
    }
    void HeapDown(int x)
    {
        int tp;
        while (x * 2 <= size)
        {
            tp = x << 1;
            if (tp < size) 
                tp += (heap[tp].key < heap[tp + 1].key);
            if (heap[x].key > heap[tp].key) break;
            Swap(tp, x);
            x = tp;
        }
        return ;
    }
    void Insert(int ID)
    {
        ++size;
        heap[ size ].from = ID;
        heap[ size ].key = f[ID];
        pos[ ID ] = size;
        HeapUp( size );
        return ;
    }
    void Delete(int ID)
    {
        int tp = pos[ID];
        if (tp == size)
        {
            size--;
            return ;
        }
        heap[tp] = heap[ size ];
        pos[ heap[tp].from ] = tp;
        size--;
        HeapUp(tp);
        HeapDown(tp);
        return ;
    }
    int Get()
    {
        return heap[1].from;
    }
}    Heap;
int main()
{
    scanf("%d %d %d", &N, &L, &R);
    for (int i = 0; i <= N; i++)
        scanf("%d", &ice[i]);
    for (int i = 1; i < L; i++)
        f[i] = -Inf;
    for (int i = L; i <= N; i++)
    {
        if (i - L >= 0) Heap.Insert(i - L);
        if (i - R - 1 >= 0) Heap.Delete(i - R - 1);
        prev[i] = Heap.Get();
        if (f[ prev[i] ] != -Inf)
            f[i] = f[ prev[i] ] + ice[i];
        else f[i] = -Inf;
    }
    int best = -1;
    for (int i = N - R + 1; i <= N; i++)
        if (best == -1 || f[i] > f[ best ])
            best = i;
    printf("%d\n", f[ best ]);
    do
    {
        step[ best ] = true;
        best = prev[ best ];
    }    while (best);
    step[0] = true;
    for (int i = 0; i <= N; i++)
        if (step[i]) printf("%d ", i);
    printf("-1\n");
    return 0;
}
Program iceroad;
Var
 i,j,k,n,l,r,top,tail,ans:longint;
 a,f,g,q,p:array[0..400000] of longint;
Begin
 readln(n,l,r); inc(n);
 for i:=1 to n do read(a[i]);
 top:=1; tail:=1; q[1]:=1;
 for i:=2 to n+r do
  begin
   j:=i-r; if j<0 then j:=0;
   k:=i-l; if k<0 then k:=0;
   while (top<=tail)and(i-r>q[top]) do inc(top);
   if (j<=q[top])and(q[top]<=k)
    then begin
     f[i]:=f[q[top]]+a[i];
     g[i]:=q[top];
     if f[i]>=f[ans] then ans:=i;
    end;
   while (top<=tail)and(f[k+1]>f[q[tail]]) do dec(tail);
   inc(tail); q[tail]:=k+1;
  end;
 writeln(f[ans]);
 i:=g[ans]; j:=0;
 while g[i]>0 do
  begin
   inc(j);
   p[j]:=g[i]-1;
   i:=g[i];
  end;
 for j:=j downto 1 do
  write(p[j],' ');
  writeln('-1');
End.

Problem 4 上白泽慧音(classroom.cpp/c/pas)
题目描述 在幻想乡,上白泽慧音是以知识渊博闻名的老师。春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄。因此慧音决定换一个能够聚集最多人数的村庄作为新的教学地点。人间之里由N个村庄(编号为1..N)和M条道路组成,道路分为两种一种为单向通行的,一种为双向通行的,分别用1和2来标记。如果存在由村庄A到达村庄B的通路,那么我们认为可以从村庄A到达村庄B,记为(A,B)。当(A,B)和(B,A)同时满足时,我们认为A,B是绝对连通的,记为<A,B>。绝对连通区域是指一个村庄的集合,在这个集合中任意两个村庄X,Y都满足<X,Y>。现在你的任务是,找出最大的绝对连通区域,并将这个绝对连通区域的村庄按编号依次输出。若存在两个最大的,输出字典序最小的,比如当存在1,3,4和2,5,6这两个最大连通区域时,输出的是1,3,4。
输入格式 第1行:两个正整数N,M
第2..M+1行:每行三个正整数a,b,t, t = 1表示存在从村庄a到b的单向道路,t = 2表示村庄a,b之间存在双向通行的道路。保证每条道路只出现一次。
输出格式 第1行: 1个整数,表示最大的绝对连通区域包含的村庄个数。
第2行:若干个整数,依次输出最大的绝对连通区域所包含的村庄编号。
输入样例 5 5
1 2 1
1 3 2
2 4 2
5 1 2
3 5 1
输出样例 3
1 3 5
数据范围 对于60%的数据:N <= 200且M <= 10,000
对于100%的数据:N <= 5,000且M <= 50,000

Classroom 上白泽慧音
题目大意 给定一张图,存在双向边和单向边若干,求出最大的强连通块。
考察算法 连通性
算法一 搜索。根据方法的不同能够得到不同的分值。
时间复杂度:O(?) 期望得分:0~100
算法二 通过Floyd判断连通性(F[i][j] = F[i][j] or (F[i][k] and F[k][j])),再将所有的双向连通点对统计出来,进行一次并查集。如果两个点是双向连通点对,把它们归为同一个集合。对于每个集合需要保存它的最小点编号。
时间复杂度:O(N^3) 期望得分:60
算法三 通过Tarjan算法求出图中所有的强连通分量,并记录每一个块的最小点编号。最后输出最大连通分量的顶点。
时间复杂度:O(N+M) 期望得分:100

#include <iostream>
using namespace std;
#define MAXN 5009
#define MAXM 100009
int N, M;
struct TARJAN
{
    int v[ MAXM ], next[ MAXM ], fir[ MAXN ];
    int dfn[ MAXN ], low[ MAXN ], stack[ MAXN ], tot[ MAXN ], mark[ MAXN ], rep[ MAXN ];
    bool used[ MAXM ], instack[ MAXN ];
    int cnt, top, ecnt, num;
    void DFS(int rt)
    {
        dfn[rt] = low[rt] = ++cnt;
        stack[ ++top ] = rt;
        instack[rt] = true;
        for (int p = fir[rt]; p; p = next[p])
            if (!used[p])
            {
                used[p] = true;
                if (!dfn[ v[p] ])
                {
                    DFS( v[p] );
                    low[rt] = min(low[rt], low[ v[p] ]);
                }
                else if (instack[ v[p] ])
                    low[rt] = min(low[rt], dfn[ v[p] ]);
            }
        if (dfn[rt] == low[rt])
        {
            ++num;
            do
            {
                mark[ stack[ top ] ] = num;
                instack[ stack[ top-- ] ] = false;
            }    while ( stack[ top + 1 ] != rt);
        }
        return ;
    }
    void Addedge(int rt, int s)
    {
        ++ecnt;
        v[ ecnt ] = s, next[ ecnt ] = fir[rt];
        fir[ rt ]= ecnt;
        return ;
    }
    void Prin(int x)
    {
        bool first = true;
        printf("%d\n", tot[x]);
        for (int i = 1; i <= N; i++)
            if (mark[i] == x)
            {
                if (!first) printf(" ");
                first = false;
                printf("%d", i);
            }
        printf("\n");
        return ;
    }
    void Work()
    {
        for (int i = 1; i <= N; i++)
            if (!dfn[i]) DFS(i);
        for (int i = 1; i <= N; i++)
        {
            tot[ mark[i] ]++;
            if (rep[ mark[i] ] == 0 || rep[ mark[i] ] > i)
                rep[ mark[i] ] = i;
        }
        int best = -1;
        for (int i = 1; i <= num; i++)
            if (best == -1 || (tot[i] > tot[ best ]) || (tot[i] == tot[ best ] && rep[i] < rep[ best ]))
                best = i;
        Prin( best );
        return ;
    }
}    Tarjan;
int main()
{
    int u, v, t;
    scanf("%d %d", &N, &M);
    while (M--)
    {
        scanf("%d %d %d", &u, &v, &t);
        Tarjan.Addedge(u, v);
        if (t == 2) Tarjan.Addedge(v, u);
    }
    Tarjan.Work();
    return 0;
}

总体来说S1还是很弱的.

原文地址:https://www.cnblogs.com/hatsuakiratenan/p/3132492.html