20级爪哇程序设计新生赛(二)题解

20级爪哇程序设计新生赛(二)

  • A. 小爪家的母猪(你们杭电做过的题)
  • B. 小爪的博弈(博弈)
  • C. 小爪在线教学如何排雷?(线性dp)
  • D. 小爪爱多项式(模拟)
  • E. 小爪求最值(二次函数求最值)
  • F. 小爪砍木棒(dfs剪枝)
  • G. 小爪学矩阵(模拟或dfs)
  • H. 小爪派巧克力(并查集)
  • I. 小爪的信(签到)

A. 小爪家的母猪(你们杭电做过的题)

20级爪哇程序设计新生赛(二)题解_第1张图片

递归找规律:
往后推多几个规律就出来了。
规律如下:
当i∈[1, 4]时: f [ i ] = i ; f[i] = i; f[i]=i;
当i∈[5, +∞] f [ i ] = f [ i − 1 ] + [ i − 3 ] ; f[i] = f[i - 1] + [i - 3]; f[i]=f[i1]+[i3];

#include
#include
using namespace std;
const int N = 50;
int a[N];
int main()
{
    int n;
    while (~scanf("%d",&n)) {
        if (n == 0) break;
        for (int i = 1;i <= 4; ++i) a[i] = i;
        for (int i = 5; i <= n; ++i) a[i] = a[i - 1] + a[i - 3];
        printf("%d\n", a[n]);
    }
    return 0;
}

B. 小爪的博弈(博弈)

20级爪哇程序设计新生赛(二)题解_第2张图片

题解:
首先统计下一共能放多少个棋子,每人每次只能放一个或三个,那么每一轮两人放的棋子总共为偶数个。所以,如果能放棋子位置的数量和为偶数,A一定是最后拿的,A必输;如果能放棋子位置的数量和为奇数,则B一定是最后拿的,B必输。

#include 
using namespace std; 
int main()
{
    int n;
    cin >> n;
    n = n * (n + 1) / 2;
    if (n & 1) cout << "Alice" << endl;
    else cout << "Bob" <<  endl;
    return 0;
}

C. 小爪在线教学如何排雷?(线性dp)

20级爪哇程序设计新生赛(二)题解_第3张图片

显然的dp题目,状态转移的时候,与前一位,当前位,后一位都有关系,所以开3维dp

f [ i ] [ 0 / 1 ] [ 0 / 1 ] f[i][0/1][0/1] f[i][0/1][0/1]
表示: f [ 前 i 位 ] [ 当 前 位 是 否 有 雷 ] [ 后 一 位 是 否 有 雷 ] f[前i位][当前位是否有雷][后一位是否有雷] f[i][][]

(0表示没雷,1表示有雷)

初始化: f [ 0 ] [ 0 ] [ 0 ] = 1 , f [ 0 ] [ 0 ] [ 1 ] = 1 f[0][0][0] =1,f[0][0][1] = 1 f[0][0][0]=1,f[0][0][1]=1

分类讨论:

如果 s [ i ] = 0 s[i] = 0 s[i]=0 ,那么代表,当前位,前一位,后一位均无雷

状态转移方程为: f [ i ] [ 0 ] [ 0 ] f[i][0][0] f[i][0][0] += f [ i − 1 ] [ 0 ] [ 0 ] f[i - 1][0][0] f[i1][0][0] % m o d mod mod

如果 s [ i ] = 1 s[i] = 1 s[i]=1 ,那么代表,当前位无雷,前一位有雷或者后一位有雷,

状态转移方程为:

​ 如果是前一位有雷,后一位无雷: f [ i ] [ 0 ] [ 0 ] f[i][0][0] f[i][0][0] += f [ i − 1 ] [ 1 ] [ 0 ] f[i-1][1][0] f[i1][1][0] % m o d mod mod

​ 如果是前一位无雷,后一位有雷: f [ i ] [ 0 ] [ 1 ] f[i][0][1] f[i][0][1] += f [ i − 1 ] [ 0 ] [ 0 ] f[i-1][0][0] f[i1][0][0] % m o d mod mod

如果 s [ i ] = 2 s[i] = 2 s[i]=2 ,那么代表,当前位无雷,前一位和后一位均有雷

状态转移方程为: f [ i ] [ 0 ] [ 1 ] f[i][0][1] f[i][0][1] += f [ i − 1 ] [ 1 ] [ 0 ] f[i-1][1][0] f[i1][1][0] % m o d mod mod

如果 s [ i ] = ∗ s[i] = * s[i]= ,那么代表,当前位有雷,前一位后一位不清楚

状态转移方程为:

​ 如果后一位无雷: f [ i ] [ 1 ] [ 0 ] f[i][1][0] f[i][1][0] += ( f [ i − 1 ] [ 0 ] [ 1 ] + f [ i − 1 ] [ 1 ] [ 1 ] ) (f[i-1][0][1] + f[i-1][1][1]) (f[i1][0][1]+f[i1][1][1]) % m o d mod mod

​ 如果后一位有雷: f [ i ] [ 1 ] [ 1 ] f[i][1][1] f[i][1][1] += ( f [ i − 1 ] [ 0 ] [ 1 ] + f [ i − 1 ] [ 1 ] [ 1 ] ) (f[i-1][0][1] + f[i-1][1][1]) (f[i1][0][1]+f[i1][1][1]) % m o d mod mod

如果 s [ i ] = ? s[i] = ? s[i]=? ,那么当前位置不知道,就需要讨论所有情况

​ 如果当前位有雷,下一位无雷: f [ i ] [ 1 ] [ 0 ] f[i][1][0] f[i][1][0] += ( f [ i − 1 ] [ 0 ] [ 1 ] + f [ i − 1 ] [ 1 ] [ 1 ] ) (f[i-1][0][1] + f[i-1][1][1]) (f[i1][0][1]+f[i1][1][1]) % m o d mod mod

​ 如果当前位有雷,下一位有雷: f [ i ] [ 1 ] [ 1 ] f[i][1][1] f[i][1][1] += ( f [ i − 1 ] [ 0 ] [ 1 ] + f [ i − 1 ] [ 1 ] [ 1 ] ) (f[i-1][0][1] + f[i-1][1][1]) (f[i1][0][1]+f[i1][1][1]) % m o d mod mod

​ 如果当前位无雷,下一位无雷: f [ i ] [ 0 ] [ 0 ] f[i][0][0] f[i][0][0] += ( f [ i − 1 ] [ 0 ] [ 0 ] + f [ i − 1 ] [ 1 ] [ 0 ] ) (f[i-1][0][0] + f[i-1][1][0]) (f[i1][0][0]+f[i1][1][0]) % m o d mod mod

​ 如果当前位无雷,下一位有雷: f [ i ] [ 0 ] [ 1 ] f[i][0][1] f[i][0][1] += ( f [ i − 1 ] [ 0 ] [ 0 ] + f [ i − 1 ] [ 1 ] [ 0 ] ) (f[i-1][0][0] + f[i-1][1][0]) (f[i1][0][0]+f[i1][1][0]) % m o d mod mod

输出: ( d p [ n ] [ 1 ] [ 0 ] + d p [ n ] [ 0 ] [ 0 ] ) (dp[n][1][0] + dp[n][0][0]) (dp[n][1][0]+dp[n][0][0]) % m o d mod mod 即可

#include
using namespace std;
const int N = 1000010;
const int mod = 1e9 + 7;
int f[N][2][2];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    string s;
    cin >> s;
    s = " " + s;
    int n = s.size() - 1;
    f[0][0][0] = 1, f[0][0][1] = 1;
    for (int i = 1; i <= n; i++) {
        if (s[i] == '0') f[i][0][0] += f[i - 1][0][0] % mod;
        else if (s[i] == '1') {
            f[i][0][0] += f[i - 1][1][0] % mod;
            f[i][0][1] += f[i - 1][0][0] % mod;
        } else if (s[i] == '2') f[i][0][1] += f[i - 1][1][0] % mod;
        else if (s[i] == '*') {
            f[i][1][0] += (f[i - 1][0][1] + f[i - 1][1][1]) % mod;
            f[i][1][1] += (f[i - 1][0][1] + f[i - 1][1][1]) % mod;
        } else if (s[i] == '?') {
            f[i][1][0] += (f[i - 1][0][1] + f[i - 1][1][1]) % mod;
            f[i][1][1] += (f[i - 1][0][1] + f[i - 1][1][1]) % mod;
            f[i][0][0] += (f[i - 1][0][0] + f[i - 1][1][0]) % mod;
            f[i][0][1] += (f[i - 1][0][0] + f[i - 1][1][0]) % mod;
        }
    }
    cout << (f[n][0][0] + f[n][1][0]) % mod << endl;
    return 0;
}

D. 小爪爱多项式(模拟)

20级爪哇程序设计新生赛(二)题解_第4张图片

模拟多项式计算

#include
#include 
#include
#include
#include
#include
using namespace std;
const int N = 200010;

int n, m;
int a[N], b[N], ans[N];

int main() {
    scanf("%d%d", &n, &m);
    //读入每一项系数
    for(int i = 0; i <= n; i++) scanf("%d", &a[i]);
    for(int i = 0; i <= m; i++) scanf("%d", &b[i]);
    //然后每次找到对应的系数相乘,并累加进答案的对应位的系数中
    for(int i = 0; i <= n; i++) {
        for(int j = 0; j <= m; j++) {
            ans[i + j] += a[i] * b[j];
        }
    }
    //最后注意输出
    for(int i = 0; i <= n + m; i++) printf("%d%c", ans[i], i == n + m ? '\n' : ' ' );
}

E. 小爪求最值(二次函数求最值)

20级爪哇程序设计新生赛(二)题解_第5张图片

题目说人话就是:已知 C 和 N ,让 A * (C - A * N) 最大的同时,A 最小。
做法:把括号拆开,得到 CA - NA^2,很显然,我们可以分类讨论:
① N != 0: 这是一个开口向下的抛物线,那么因为要使表达式A * (C - A * N)整体最大,那么必然A就是对称轴所在的位置。
② N == 0: 如果 C 也为 0, 那么 A 必然为 0;
但如果 C 不为 0, 那么 CA 就是一条单调递增的直线, 不存在 A * (C - A * N) 最大,故而 A 也不存在.
换个角度想, A * (C - A * N) 是一个和利润挂钩的因子,公司数量N都为0了,那么和利润相关的 A * (C - A * N) 也必然为0,故而A = 0.

综上,N不为0,求二次函数的对称轴,反之答案为0.

#include

using namespace std;

int main() {
	int t;
	cin >> t;
	for (int i = 1; i <= t; ++ i) {
		long long n, c;  //数据比较大,后面还有*法,开ll防爆
		scanf("%lld %lld", &n, &c);  
		if (n == 0)  {
			printf("0\n");
			continue;
		}
		int ans = c / (2 * n);
		int res = ans + 1;   //存在不整除,所以还需要考虑和ans相邻的下一个点
		if (c * ans - n * ans * ans >= c * res - n * res * res) 
			printf("%lld\n", ans);
		else  printf("%lld\n", res);
	}
	return 0;
}

F. 小爪砍木棒(dfs剪枝)

20级爪哇程序设计新生赛(二)题解_第6张图片

定义:
木棒:一组等长的木棒
木棍:砍断之后的(题目中输入的数据)

搜索顺序:从前往后依次拼接,一次枚举每根木棒是怎么拼出来的(每根木棒是由哪些木棍拼出来的)将木棒内部木棍排个序,以组合数的形式来枚举木棍(木棍内部顺序是无所谓的,保证总和相等即可)

对于固定长度,如果恰好把所有木棍拼成当前枚举的这个长度,则将此称为合法方案

剪枝:
枚举长度:len
满足:len|sum ,sum一定是len的倍数,只枚举sum的约数即可。

搜索顺序的优化:
先找分支较少的,先枚举较长的木棍

枚举方式: 按组合(不考虑内部顺序)方式枚举,而不是排列(考虑内部顺序)
传一个参数: 表示下一个数的下标从哪个数开始枚举,保证从小到大枚举。

排除等效冗余: (证明如下)

  1. 如果当前枚举的木棍失败,那么与当前木棍长度相同的木棍直接忽略。
  2. 如果是木棒的第一根木棒拼接失败,则一定失败,直接回溯
  3. 如果是木棒的最后一根木棒拼接失败,则一定失败,直接回溯

#include
#include
#include
#include
using namespace std;
const int N = 55;

int n;
int w[N];
int sum;//所有木棍的总和
int len;//枚举木棒的长度
bool st[N];//记录小木棍有没有被用过

//当前枚举到的木棒,当前木棒的长度,开始的位置
bool dfs(int u, int s, int start) {
    //
    if (u * len == sum) return true;
    //如果当前的木棒长度 == len 则需要开一根新的木棒,木棒数量+1
    if (s == len) return dfs(u + 1, 0, 0);
    
    //枚举每一根木棍
    for (int i = start; i < n; i++) {
        if (st[i]) continue;//如果这个木棍已经枚举过,就pass
        
        //可行性剪枝
        if (s + w[i] > len) continue;//如果当前木棒长度 + 小棍长度 > 枚举木棒的长度,则pass
        
        st[i] = true;   
        //当前这根木棒,木棒长度加上w[i],开始下标为i+1
        if (dfs(u, s + w[i], i + 1)) return true;
        
        //回溯
        st[i] = false;//恢复现场
        
        if (!s) return false;
        if (s + w[i] == len) return false;

        int j = i;
        while (j < n && w[j] == w[i]) j++;
        i = j - 1;
        
    }
    return false;
}
int main()
{
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    cin >> n;
    memset(st, false, sizeof st);
    sum = 0;
    for (int i = 0; i < n; i++) {
        cin >> w[i];
        sum += w[i];
    }
    
    //优化搜索顺序,从大到小
    sort(w, w + n);
    reverse(w, w + n);
    
    //从小到大枚举木棒的长度
    len = 1;
    while (true) {
        //dfs(枚举每一根木棒,当前木棍长度是0,当前下标是0)
        if (sum % len == 0 && dfs(0, 0, 0)) {
            cout << len << endl;
            break;
        }
        len++;
    }
    return 0;
}

G. 小爪学矩阵(模拟或dfs)

20级爪哇程序设计新生赛(二)题解_第7张图片

解法一:(模拟)
1、4个while循环,分别对应向右,向下,向左,向上走,走一趟为一个周期
2、若在当前方向的下一个位置可以踩,则踩过去

#include
using namespace std;
const int N = 110;
int n, m;
int a[N][N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    cin >> n >> m;

    int k = 0;
    int x = 0, y = -1;
    while (k != n * m) {
        //向右走
        while (y + 1 < m && a[x][y + 1] == 0) {
            y ++;
            a[x][y] = ++k;
        }
        //向下走
        while (x + 1 < n && a[x + 1][y] == 0) {
            x ++;
            a[x][y] = ++k;
        }
        //向左走
        while (y - 1 >= 0 && a[x][y - 1] == 0) {
            y --;
            a[x][y] = ++k;
        }
        //向上走
        while (x - 1 >= 0 && a[x - 1][y] == 0) {
            x --;
            a[x][y] = ++k;
        }
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++)
            cout << a[i][j] << " ";
        cout << endl;
    }
    return 0;
}

解法二(DFS)
关键点是:d = (d + 1) % 4;
其他的就是一个DFS的模板题

#include 
using namespace std;
int res[110][110];
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
int main()
{
    int n, m;
    cin >> n >> m;
    for (int x = 0, y = 0, d = 0, k = 1; k <= n * m; k ++ ) {
        res[x][y] = k;
        int a = x + dx[d], b = y + dy[d];
        if (a < 0 || a >= n || b < 0 || b >= m || res[a][b]) {
            d = (d + 1) % 4;//关键点在这里
            a = x + dx[d], b = y + dy[d];
        }
        x = a, y = b;
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j ++ ) 
            cout << res[i][j] << ' ';
        cout << endl;
    }

    return 0;
}

H. 小爪派巧克力(并查集)

20级爪哇程序设计新生赛(二)题解_第8张图片

这道题考查的是并查集
这道题我们需要使用并查集维护几个信息:

  1. 集合的大小
  2. 集合的最大值

维护集合的最大值是因为在一群互为朋友关系的集合中,我们要想让里面的小朋友都开心,
那么我们就需要让这群小朋友都分得这群小孩中 a i a_i ai 最大的数量
(即假设这有100个孩子互为朋友关系,第 i i i 个孩子的 a i a_i ai 最大,那么我们要让这群孩子都拿 a i a_i ai 块巧克力以保证他们都开心)
维护集合大小就方便我们计算需要巧克力的数量,我们每次只要找到一个集合然后把这个集合的最大值乘以数量并加入答案即可
至于找集合的话,我们为了不重不漏,我们只需要每次枚举每个小朋友,当该小朋友的对应节点为祖先节点时我们把当前节点计算一次
这样就可以做到不重不漏了

#include
#include
#include
#include
#define ll long long
using namespace std;
const ll N = 2000010;
const ll M = 1e9 + 7;

ll n, m, a[N], u, v;
ll p[N], mx[N], s[N];

//初始化并查集
void init() {
    for(ll i = 0; i <= n; i++) p[i] = i, mx[i] = a[i], s[i] = 1;
}

//找到祖先节点
ll find(ll x) {
    if(x == p[x]) return x;
    else return p[x] = find(p[x]);
}

//合并两个集合
void merge(ll x, ll y) {
    ll px = find(x), py = find(y);
    if(px != py) {
        p[py] = px;
        //维护大小
        s[px] += s[py];
        //维护最值
        mx[px] = max(mx[px], mx[py]);
    }
}

int main() {
	scanf("%lld%lld", &n, &m);
    for(ll i = 1; i <= n; i++) scanf("%d", &a[i]);
    //初始化
    init();
    while(m--) {
        scanf("%lld%lld", &u, &v);
        merge(u, v);
    }
    ll ans = 0;
    for(int i = 1; i <= n; i++) {
        //祖先节点为自己时才计算
        if(p[i] == i) {
            ans += s[i] * mx[i];
        }
    }
    printf("%lld\n", ans);
	return 0;
}

I. 小爪的信(签到)

20级爪哇程序设计新生赛(二)题解_第9张图片

题解:开个计数数组,按照小写字母的顺序映射到索引0~25,遍历一遍,对应的小写字母的索引的数组值就+1,同时存一个最大值maxx。最后从0往25遍历一遍,最先遇到数组值为maxx的就是字符数最多且ASCII码值最小的。(用map也可以)

#include 
#include

using namespace std;

#define sc scanf
#define pr printf

const int N = 100020; 

char str[N];
int book[26];  //用小写字母的顺序映射到索引0~25

int main() {
	sc("%s", str);
	int length = strlen(str);
	for (int i = 0; i < length; ++ i) 
		++ book[str[i] - 'a'];    //统计数目 
	int maxx = 0;
	char ans = 0;
	for (int i = 0; i < 26; ++ i) {
		if (book[i]) {
			if (book[i] > maxx) {
				maxx = book[i];
				ans = i;
			}
		}
	} 
	pr("%c", ans + 'a');
	return 0;
}

你可能感兴趣的:(竞赛,题解,算法)