2022GDUT寒训专题一

C题

题面

马在中国象棋以日字形规则移动。

请编写一段程序,给定n×m大小的棋盘,以及马的初始位置 (x, y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。

输入格式

第一行为整数 T(T < 10),表示测试数据组数。 每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标 n,m,x,y(0≤xn−1,0≤ym−1,m<10,n<10)。

Sample Input

1
5 4 0 0

输出格式

每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,0 为无法遍历一次。

Sample Output

32

思路

因为要求马遍历棋盘的途径总数,我们可以很容易的想到使用dfs来解决这个问题。如何判断是否为填满了整个棋盘就要想到如果能遍历,那一定是走了n*m个点,于是在设计dfs的时候设计为void dfs(int x, int y, int step),其中step为当前走了多少步。

代码

 1  1 #include <iostream>
 2  2 #include <algorithm>
 3  3 using namespace std;
 4  4 int n, m, x, y, ans;
 5  5 int vis[20][20];
 6  6 int a[8][2] = {{2,1},{2,-1},{-2,1},{-2,-1},{1,2},{1,-2},{-1,2},{-1,-2}};
 7  7 //顺便记录步数
 8  8 void dfs(int x, int y, int step)
 9  9 {
10 10     //其实就是当且仅当step为n*m-1步的时候即为填满了整个棋盘
11 11     if(step == n*m-1)
12 12     {
13 13         ans++;
14 14         return;
15 15     }
16 16     for(int i = 0;i < 8;++i)
17 17     {
18 18         //然后正常遍历就行了
19 19         int xx = x+a[i][0], yy = y+a[i][1];
20 20         //如果一直进不去自然会return的
21 21         if(xx >= 0 && xx <= n-1 && yy >= 0 && yy <= m-1 && vis[xx][yy] == 0)
22 22         {
23 23             vis[xx][yy] = 1;
24 24             dfs(xx, yy, step+1);
25 25             vis[xx][yy] = 0;
26 26         }
27 27     }
28 28 }
29 29 
30 30 int main()
31 31 {
32 32     int t;cin >> t;
33 33     while(t--)
34 34     {
35 35         //多组数据要记得初始化
36 36         for(int i = 0;i < 20;++i)
37 37             for(int j = 0;j < 20;++j)
38 38                 vis[i][j] = 0;
39 39         cin >> n >> m >> x >> y;
40 40         ans = 0;
41 41         vis[x][y] = 1;
42 42         dfs(x, y, 0);
43 43         cout << ans << endl;
44 44     }
45 45     return 0;
46 46 }

 

I题

题面

给一个长度为 N的数组,一个长为 K的滑动窗体从最左端移至最右端,你只能看到窗口中的 K 个数,每次窗体向右移动一位,如下图:

窗口位置最小值最大值
[1 3 -1] -3 5 3 6 7 -1 3
1 [3 -1 -3] 5 3 6 7 -3 3
1 3 [-1 -3 5] 3 6 7 -3 5
1 3 -1 [-3 5 3] 6 7 -3 5
1 3 -1 -3 [5 3 6] 7 3 6
1 3 -1 -3 5 [3 6 7] 3 7

你的任务是找出窗体在各个位置时的最大值和最小值。

输入格式

第 1 行:两个整数 N 和 K; 第 2 行:N个整数,表示数组的 N个元素(≤2e9);

输出格式

第一行为滑动窗口从左向右移动到每个位置时的最小值,每个数之间用一个空格分开; 第二行为滑动窗口从左向右移动到每个位置时的最大值,每个数之间用一个空格分开。

样例

Input&Output

8 3
1 3 -1 -3 5 3 6 7
-1 -3 -3 -3 3 3
3 3 5 5 6 7

思路

这题是滑动窗口找最值问题,我们需要用到的工具是双头的单调队列。

若找的是最大值,则要构造一个递减的队列,反之递增。

解释如下:

1、首先明确我们遍历数组时,当前下标的意思为滑动窗口的右端点为该元素。记滑动窗口大小为k。

2、假设存在滑动窗口右端点a,记滑动窗口下一个元素为b,对于ab二者我们有两种情况:b>=a或者b<a(分别对应第3和4点)。

3、b>=a:我们知道b在a的右边,那么b影响的范围是[b,b+k-1],a影响的范围是[a,a+k-1]。因为b-a=1,所以b影响的范围始终比a影响的范围大。这意味着,一旦我们能走到b点,a就不再是最大值了,此时a已经没有了任何作用,a从队首出队。在a之前还有a-1,a-2......他们能影响的范围比a还要小,更不可能成为最大值,所以同理他们也从队首出队。一直到队列为空,现在b为新的最大值,为队首。

4、b<a:b更小,所以b没办法取代a,但是b有可能在a影响不到的地方也就是b+k-1这个点成为新的最大值,所以我们让b从队尾入队,可以理解为“待使用”。这里要注意的是b从队尾入队的时候也要遵循3中的原则,所以如果b比队尾元素大,那队尾元素也没有用了,我们也是将队尾元素出队,一直到b比队尾元素小停止。

5、还需要注意的一点是,滑动窗口是有大小的,所以我们队列中的元素是有“生命周期”的,也就是当前下标-队列元素下标+1要小于等于k

所以我们实际上队列存的是下标,这样引用起来比较方便。

代码

#include <iostream>
#include <algorithm>
using namespace std;
//这题是滑动窗口找最大或最小值的问题
//要运用到双头单调队列,一直维护最大或最小值,并且由于是滑动窗口所以元素存在生命周期,这里用数组q模拟
//
int num[1000010];
int q[4000020];
int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);cout.tie(NULL);
    //
    int n, k;cin >> n >> k;
    for(int i = 0;i < n;++i) cin >> num[i];
    //
    int head, tail;
    //最小值
    head = 0, tail = 0;
    //先让第一个元素入队
    q[head] = 0;
    for(int i = 0;i < n;++i)
    {
        //如果队首元素达到了生命周期,那么队首元素出队直到头尾指针相遇
        while(head <= tail && i-q[head]+1 > k) head++;
        if(num[q[head]] >= num[i])
        {
            //如果说你比这个队首元素还要小,那么一直出队
            while(head <= tail && num[q[head]] >= num[i]) head++;
            //走多了一格,要回去一格
            q[--head] = i;
        }else
        {
            //否则从队尾入队,但要维护单调性
            while(head < tail && num[q[tail]] >= num[i]) tail--;
            q[++tail] = i;
        }
        //当且仅当你达到所求滑动窗口大小的时候才能输出
        if(i >= k-1) printf("%d ", num[q[head]]);
    }
    printf("\n");
    //最大值
    head = 0, tail = 0;
    q[head] = 0;
    for(int i = 0;i < n;++i)
    {
        while(head <= tail && i-q[head]+1 > k) head++;
        if(num[q[head]] <= num[i])
        {
            while(head <= tail && num[q[head]] <= num[i]) head++;
            q[--head] = i;
        }else
        {
            while(head < tail && num[q[tail]] <= num[i]) tail--;
            q[++tail] = i;
        }
        if(i >= k-1) printf("%d ", num[q[head]]);
    }
    printf("\n");
    return 0;
}
//

 

 

J题

题面

给你一个长度为 n的整数序列{A1,A2,⋯,A**n},要求从中找出一段连续的长度不超过 m的非空子序列,使得这个序列的和最大。

输入格式

第一行为两个整数 n,m;

第二行为 n个用空格分开的整数序列,每个数的绝对值都小于 1000。

对于50% 的数据1≤n,m≤1e4;

对于 100% 的数据,1≤n,m≤2×1e5。

输出格式

仅一个整数,表示连续长度不超过 m的最大非空子序列和。

样例

Input&Output

6 4
1 -3 5 1 -2 3
7

思路

这题看到求区间和,首先会想到前缀和预处理。然后看到是连续的长度不超过m的非空子序列,想到长度为m的滑动窗口的子序列。

现在要让这个和最大,也就是当前所走到的元素的前缀和减去一个所能取到的最小的前缀和。

所以我们先用滑动窗口动态找到区间最小前缀和,然后再拿当前前缀和减去区间最小前缀和即可。

以下两点需要注意:

1、此时的滑动窗口实际上可以取到m+1

2、若当前所找到的滑动窗口最小前缀和就是当前的值,那么说明一直没有更大的前缀和出现,那么答案为pre[i]-pre[i-1],即还原为元素本身。

代码

 

 1 #include <iostream>
 2 #include <algorithm>
 3 using namespace std;
 4 typedef long long ll;
 5 const int maxn = 2e5+10;
 6 
 7 long long pre[maxn];
 8 int q[maxn*2];
 9 int main()
10 {
11     ios_base::sync_with_stdio(false);
12     cin.tie(NULL);cout.tie(NULL);
13     //
14     int n, k;cin >> n >> k;
15     for(int i = 1;i <= n;++i) {int temp;cin >> temp;pre[i] = pre[i-1]+temp;}
16         //for(int i = 1;i <= n;++i) printf("pre = %lld\n", pre[i]);
17     //
18     ll ret = -maxn;
19     int head, tail, cnt;
20     head = 1, tail = 1;
21     q[head] = 0;
22     cnt = 0;
23     for(int i = 1;i <= n;++i)
24     {
25         while(head <= tail && i - q[head] + 1 > k+1) head++;
26         if(pre[q[head]] >= pre[i])
27         {
28             while(head <= tail && pre[q[head]] >= pre[i]) head++;
29             q[--head] = i;
30         }else
31         {
32             while(head < tail && pre[q[tail]] >= pre[i]) tail--;
33             q[++tail] = i;
34         }
35         if(i == q[head]) ret = max(ret, pre[i]-pre[i-1]);
36         else ret = max(ret, pre[i] - pre[q[head]]);
37     }
38     //
39     cout << ret << endl;
40     return 0;
41 }

 

 

 

posted @ 2022-01-20 18:13  _77  阅读(87)  评论(0编辑  收藏  举报