10.3 校内集训 解题报告

T1 荷花池塘 (pool)

题目背景

于大夫建造了一个美丽的池塘,用来让自己愉快的玩耍。这个长方形的池子被分割成了M 行和N 列的正方形格子。池塘中有些地方是可以跳上的荷叶,有些地方是不能放置荷叶也不能跳上的岩石,其他地方是池水(当然于大夫也是不能游泳的)。

题目描述

于大夫十分有趣,他在池塘跳跃的方式和象棋中的马一样可以向八个方向走日字形,而且于大夫只能跳上荷叶。 于大夫每天从一个给定的有荷叶的地方出发,试图到达另一个给定的有荷叶的地方。但有一天他发现自己无论如何也不能到达目的地了,除非再在水中放置几个荷叶。于大夫想让你告诉他,最少还需放置几片荷叶?在放置荷叶最少的前提下,最少需要几步能到达目的地?

输入输出格式

输入格式:
第1 行: 两个整数M , N 第2…M+1 行:第i + 1 行有N 个整数,表示该位置的状态:

0 为水; 1 为荷叶; 2 为岩石; 3 为于大夫开始的位置; 4 为于大夫要去的目标位置.

输出格式:
一行两个整数空格隔开:分别为最少放置荷叶数和最小放置为前提下最少步数。(,如果无 论如何也不能到达请输出-1 -1)。

输入输出样例

输入样例#1: 复制
input1:
4 8
0 0 0 1 0 0 0 0
0 0 0 0 0 2 0 1
0 0 0 0 0 4 0 0
3 0 0 0 0 0 1 0
输出样例#1: 复制
2 6
说明

10%的数据n,m<=4

30%的数据n,m<=10

50%的数据n,m<=30

70%的数据n,m<=50

100%的数据n,m<=100

所以这是道爆搜题…
可算是双关键字爆搜吧,我们可以用priority_queue来维护我们的BFS队列,定义所需荷叶数的优先级大于路径长度,因此我们第一次搜到终点,此时所需荷叶数最少,路径长度最短,一定是最优解

AC Code:

#include<bits/stdc++.h>
#define rg register
#define il inline
#define maxn 500005
#define ll long long
using namespace std;
il int read(){rg int x = 0 , w = 1 ; rg char ch = getchar() ; while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}while (ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + ch - '0' ; ch = getchar();}return x * w;}
char ss[1000];
int s1 , s2 , t1 , t2 , n , m;
bool vis[102][102];
int f[102][102];
int fx[8] = {2 , 2 , 1 , 1 , -1 , -1 , -2 , -2} , fy[8] = {1 , -1 , 2 , -2 , 2 , -2 , 1 , -1};
struct node{
    int x , y , len , num;
    friend bool operator<(const node a , const node b){
        return (a.num > b.num) || (a.num == b.num  && a.len > b.len);	
    }

};
node bfs(int x , int y){
    priority_queue<node> q;
    q.push((node){x , y , 0 , 0});
    while (!q.empty()){
        node now = q.top();
        q.pop();
        rg int x = now.x , y = now.y , len = now.len , num = now.num;
        if (vis[x][y]) continue;
        vis[x][y] = 1;
        if (x == t1 && y == t2) return now;
        for (rg int i = 0 ; i < 8 ; ++i){
            rg int xx = x + fx[i] , yy = y + fy[i];
            if (xx < 0 || xx > n || yy < 0 || yy > m) continue;
            if (f[xx][yy] == 2) continue;
            else if (f[xx][yy] == 1 || f[xx][yy] == 4)
                q.push((node){xx , yy , len + 1 , num});
            else if (!f[xx][yy])
                q.push((node){xx , yy , len + 1 , num + 1});
        }
    }
    return (node){0 , 0 , -1 , -1};
}
int main(){
    n = read() , m = read();
    for (rg int i = 1 ; i <= n ; ++i){
        for (rg int j = 1 ; j <= m ; ++j){
            f[i][j] = read();
            if (f[i][j] == 3) s1 = i , s2 = j;
            if (f[i][j] == 4) t1 = i , t2 = j;
        }
    }
    node ans = bfs(s1 , s2);
    cout << ans.num << ' ' << ans.len;
    return 0;	
}

T2 友好数对 (num)

题目描述

如果一个数a,能由一个数b 旋转得到,那么我们称<a,b>为友好数对,如12345 和45123为友好数对,12345 和54321 不为友好数对。

给出两个正整数L,R,求有多少友好数对<a,b>,满足L<=a<b<=R。

输入输出格式

输入格式:
第一行一个整数T,表示数据组数,每组数据两个正整数L,R。

输出格式:
对于每组数据,输出一个整数表示答案。

输入输出样例

输入样例#1: 复制
4
1 9
10 40
100 500
1111 2222
输出样例#1: 复制
0
3
156
287
说明

对于30%的数据满足L,R<=1000

对于100%的数据满足L,R<=2000000,T<=30

这道题的旋转定义很迷啊…
由题意得,如果A和B互为友好数对,那么A和B的友好数对也为友好数对,并且这些互为友好数对所组成的集合之间的交集为空,也就是说事实上每个点只会扫描一次,因此可以暴力O(n)(好吧,其实还要优化一下,我们预处理10的幂来加快计算速度)

AC Code:

#include<bits/stdc++.h>
#define rg register
#define il inline
#define maxn 5000005
#define ll long long
using namespace std;
il int read(){rg int x = 0 , w = 1 ; rg char ch = getchar() ; while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}while (ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + ch - '0' ; ch = getchar();}return x * w;}
bool vis[maxn];
int p[20];
int main(){
    rg int t = read();
    p[0] = 1;
    for (rg int i = 1 ; i <= 10 ; ++i)
        p[i] = p[i - 1] * 10;
    ll ans = 0;
    while (t--){
        ans = 0;
        int l = read() , r = read();
        for (rg int i = l ; i <= r ; ++i) vis[i] = 0;
        for (int i = l ; i <= r ; ++i){
            if (vis[i]) continue;
            vis[i] = 1;
            int x = i , tmp = 0 , len = 0 , tot = 0 , res = 0;
            while (x){
                x /= 10;
                ++len;
            }
            for (rg int j = 1 ; j < len ; ++j){
                rg int tmp = i / p[j] + i % p[j] * p[len - j];
                if (tmp >= l && tmp <= r && !vis[tmp]){
                    vis[tmp] = 1 , ans += ++res;
                }
            }
            /*while (x >= 10){
                int y = x % 10;
                x /= 10;
                tmp = tmp * 10 + y;
                ++tot;
                int tmp1 = tmp;
                for (rg int j = 1 ; j <= len - tot ; ++j)
                    tmp1 *= 10;
                if (tmp1 + x >= l && tmp1 + x <= r && !vis[tmp1 + x]){
                    vis[tmp1 + x] = 1;
                    ans += ++res;
                }
            }*/
        }
        cout << ans << endl;
    }
}

T3 排列 (pai)(好题!)

题目描述

对于一个1->n的排列 ,定义A中的一个位置i是好的,当且仅当Ai-1>Ai 或者Ai+1>Ai。对于一个排列A,假如有不少于k个位置是好的,那么称A是一个好的排列。

现在有q个询问,每个询问给定n,k,问有多少排列是好的。答案对10^9+7取模。

输入输出格式

输入格式:
首先输入q。

接下来输入q个询问n,k

输出格式:
输出q行,每行一个整数代表答案。

输入输出样例

输入样例#1: 复制
8
4 3
6 4
10 7
20 14
50 40
100 72
1000 900
3000 2000
输出样例#1: 复制
8
448
1433856
868137807
908422882
609421284
150877522
216180189
说明

对于20%的数据,n<=10,q=1

对于40%的数据,n<=20,q=1

对于60%的数据,n<=100

对于100%的数据,n,k<=3000,q<=10000

由于这道题的数据范围,n、k满足n ^ 2,而q又比较大,我们比较容易想到递推预处理来o1查询,但是式子该怎么推呢?
对于数n和1 ~ n - 1的序列来说,n肯定是比1 ~ n - 1中的any数都要大的,但是不一定它会对答案造成贡献,我们反向思考一下,因为题目的定义是

对于一个1->n的排列 ,定义A中的一个位置i是好的,当且仅当Ai-1>Ai 或者Ai+1>Ai

好处我们反而不怎么好定义,但是坏处就简单多了,也就是一个峰大于左右两边的数(易证峰不是连续的),现在我们要将n加入到1 ~ n - 1中,试想,如果它加到一个峰的旁边,它自己必然是一个峰,但是原来的峰也必然会消失,导致答案是不变的,如果它不加到峰的旁边,那么就多了一个峰。
推一下式子,我们设f[i][j]为i个数峰为j个的排列数,那么我们考虑上文的两种情况:

  1. 加到峰的旁边,不增加峰
    f[i][j] += f[i - 1][j](没有增加峰)* (i - (i - j) << 1 + 2)(若在不是峰的旁边肯定两边都得不是峰)
  2. 加到谷的旁边
    f[i][j] += f[i - 1][j - 1] * (i - j) << 1)

AC Code:

#include<bits/stdc++.h>
#define rg register
#define il inline
#define maxn 500005
#define ll long long
#define mod 1000000007
using namespace std;
il int read(){rg int x = 0 , w = 1 ; rg char ch = getchar() ; while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}while (ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + ch - '0' ; ch = getchar();}return x * w;}
ll f[3002][3002];
int main(){
    rg int q = read();
    f[2][1] = 2;
    for (int i = 3 ; i <= 3000 ; ++i)
        for (int j = 1 ; j <= i ; ++j){
            f[i][j] = (f[i - 1][j - 1] * (i - j) % mod * 2 % mod + f[i - 1][j] * (i - ((i - j) << 1) + 2) % mod) % mod;
        }
    rg int n , k;
    for (rg int i = 1 ; i <= q ; ++i){
        n = read() , k = read();
        if (n == 1){
            printf("%lld\n" , 1);
            continue;	
        }
        ll ans = 0;
        for (rg int i = k ; i <= n; ++i)
            ans = (ans + f[n][i] % mod) % mod;
        printf("%lld\n" , ans);	
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值