第一题: 甜甜花酿鸡
在线测评链接:https://www.sspnote.com/oj/3/82
题目描述
薯条哥是一名厨艺达人,最近特别喜欢做甜甜花酿鸡,因为这种菜式的口感鲜美且具有一定的艺术性。
薯条哥的厨房里,堆满了新鲜的食材,其中包括大量的鸡肉和甜甜花。
然而,当他想要烹制一些甜甜花酿鸡时,他发现他的材料可能不够用。他有 个甜甜花, 个鸡肉,以及 个神秘的魔法食材,可以当作甜甜花或者鸡肉使用。
薯条哥知道做一只甜甜花酿鸡需要 个甜甜花和 个鸡肉。他希望尽可能多地制作甜甜花酿鸡,而不浪费任何食材。
现在他需要你的帮助,来计算他最多能制作多少只甜甜花酿鸡。
输入描述
输入三个整数 , 用空格隔开。
输出描述
一个整数,代表可以制作的甜甜花酿鸡的最大数量。
样例
输入
3 3 3
输出
2
样例解释
可以将两个万能食材当作一个甜甜花和一个鸡肉,所以是 4 4 1
可以制作两个甜甜花酿鸡。
题解:分类讨论
首先,由于 是魔法药材,我们先不考虑,我们分类讨论 和 的大小关系
-
情况1: ,那么显然,不利用 的情况下,可以制作 个甜甜花酿鸡,如果利用 的话,总共可以制作 个甜甜花酿鸡 -
情况2: ,可以利用 尽可能去平衡 和 的值
如果有 ,则可以先利用c使a和b尽可能相等,然后就把问题转换成情况1。
如果没有,则把 全部累加到较小的一方即可。
本题可以直接假定a<b,如果a>=b,交换a和b即可。
C++
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
// 保证 a<=b
if (a > b) {
swap(a, b);
}
// c可以先将a和b补至相等,然后再均匀分配
if (c >= b - a) {
c -= (b - a); // 将a和b补至相等
cout << (b + c / 2) / 2 << endl; // 剩下的均匀分配即可
} else {
a += c; // 将c累加至较小的一方
cout << a / 2 << endl;
}
return 0;
}
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
int c = scanner.nextInt();
// 保证 a<=b
if (a > b) {
int temp = a;
a = b;
b = temp;
}
// c可以先将a和b补至相等,然后再均匀分配
if (c >= b - a) {
c -= (b - a); // 将a和b补至相等
System.out.println((b + c / 2) / 2); // 剩下的均匀分配即可
} else {
a += c; // 将c累加至较小的一方
System.out.println(a / 2);
}
}
}
Python
a,b,c=map(int,input().split())
if a>b: #保证 a<=b
a,b=b,a
if c>=b-a: #c可以先将a和b补至相等,然后再均匀分配
c-=(b-a) #将a和b补至相等
print((b+c//2)//2) #剩下的均匀分配即可
else:
a+=c #将c累加至较小的一方
print(a//2)
第二题: N皇后
在线测评链接:https://www.sspnote.com/oj/3/83
题目描述
薯条哥是一个热衷于国际象棋的棋手,他最近在研究 皇后问题。在国际象棋中,皇后是一种强大的棋子,能够沿着横、竖、斜线攻击其他棋子。
而在 皇后问题中,皇后也是一种强大的棋子,它能攻击同一行、同一列以及同一 度角斜线和 度角斜线上的其他皇后。
薯条哥手上拿着一个 的棋盘,上面已经放置了一些皇后。他希望再放置一个皇后,使得所有的皇后不会互相攻击。
对于一个 的棋盘,有多种不同的摆放皇后的方式,而有些摆法可能会导致皇后之间发生攻击,有些摆法则不会。
因此,薯条哥需要找到所有满足条件的摆法,以便让他更好地研究 皇后问题,你能帮薯条哥求出有多少种放置方案吗?
输入描述
第一行输入一个正整数 ,代表棋盘大小。
接下来的
行,每行输入一个仅由 .
和 *
组成的字符串,其中 *
代表放置了一个皇后, .
代表未放置皇后。
输出描述
输出薯条哥有多少种放置方案。如果给定的输入的棋盘中有两个皇后会互相攻击,则输出-1。
样例1
输入
3
. * .
. . .
. . .
输出
2
说明 只有左下角和右下角两个位置可以放置。
样例2
输入
3
. * .
. * .
. . .
输出
-1
题解:模拟
注意数据范围!这不是DFS的那道八皇后:https://www.luogu.com.cn/problem/P1219
那道题的数据范围是 ,因此是可以用DFS求解的,本题 ,必须要设计一个时间复杂度为 的算法才不会超时。
我们可以根据当前棋盘已经放置的皇后位置,来对棋盘的任意一个位置 是否可以放置皇后来做一个标记。
如果 点已经放置了皇后,那么第 行,第 列,以及 的两个对角线都不可放置皇后,对于行和列我们可以使用两个数组 和 来标记,对于两个对角线,我们可以使用 和 这两个标记数组来判断是否可以放置,举个例子,如下图所示,45度的斜线上的点,都满足 等于一个定值,因此可以用 标记数组来判断。同理可得135度斜线上的点,都满足 是一个定值,但是由于 可能小于0,因此最后还需要把数值+n,以保证映射在一个正整数区间。
这样模拟一遍,最终我们再枚举每一个位置是否可以放置皇后,输出对应的合法数量即可。
C++
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
char g[N][N];
bool col[N] , row[N] , dig[N * 2] , idig[N * 2]; // 记录行,列,正负对角线是否有皇后
int main()
{
int n;
cin >> n;
bool flag=true; //检验棋盘上的皇后是否合法
for (int i = 1 ; i <= n ; i++){
for (int j = 1 ; j <= n ; j++){
cin>>g[i][j];
if (g[i][j] == '*'){
if(row[i]||col[j]||dig[i-j+n]||idig[i+j]){
flag=false;
}
row[i]=col[j]=dig[i-j+n]=idig[i+j]=true; //将行、列、两个对角都标记为true
}
}
}
if(!flag){
puts("-1");
return 0;
}
int cnt = 0;
for (int i = 1 ; i <= n ; i++){
for (int j = 1 ; j <= n ; j++){
if (row[i] || col[j] || dig[i - j + n] || idig[i + j]) continue;
cnt ++;
}
}
cout << cnt << endl;
return 0;
}
Java
import java.util.Scanner;
public class Main {
static final int N = 1010;
static char[][] g = new char[N][N];
static boolean[] row = new boolean[N], col = new boolean[N], dig = new boolean[N * 2], idig = new boolean[N * 2];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
boolean flag = true;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
g[i][j] = scanner.next().charAt(0);
if (g[i][j] == '*') {
if (row[i] || col[j] || dig[i - j + n] || idig[i + j]) {
flag = false;
}
row[i] = col[j] = dig[i - j + n] = idig[i + j] = true;
}
}
}
if (!flag) {
System.out.println("-1");
return;
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (row[i] || col[j] || dig[i - j + n] || idig[i + j]) continue;
cnt++;
}
}
System.out.println(cnt);
}
}
Python
N = 1010
g = [['' for _ in range(N)] for _ in range(N)]
row = [False] * N
col = [False] * N
dig = [False] * (N * 2)
idig = [False] * (N * 2)
n = int(input())
flag = True
for i in range(n):
g[i]=input().split(' ')
for j in range(n):
if g[i][j] == '*':
if row[i] or col[j] or dig[i - j + n] or idig[i + j]:
flag = False
row[i] = col[j] = dig[i - j + n] = idig[i + j] = True
if not flag:
print("-1")
exit(0)
cnt = 0
for i in range(n):
for j in range(n):
if row[i] or col[j] or dig[i - j + n] or idig[i + j]:
continue
cnt += 1
print(cnt)
第三题: 最长的通讯路径
在线测评链接:https://www.sspnote.com/oj/3/84
题目描述
薯条哥是一位研究者,他在研究网络传输时遇到了一个问题。
他拿到了一张通讯网络的拓扑结构图,其中每条通讯线路被染成了红色或者蓝色。
他想找到一条长度最长的通讯路径,使得路径上相邻的两条线路颜色不同。
输入描述
第一行输入一个正整数 , 代表节点数量。
接下来的 行,每行输入两个正整数 和一个字符 ,代表节点 和节点 有一条边连接。
若为 'R'
代表这条边是红色, 'B'
代表这条边是蓝色。
保证输入的是一颗树。
输出描述
一个正整数,代表薯条哥可以选择的路径最大长度。
样例
输入
4
1 2 R
2 3 B
3 4 B
输出
2
样例解释
选择 的路径即可。
题解:树形DP
默认以1为根节点进行DFS遍历。选用其他节点也是不影响的,因为会枚举以 为根节点的所有路径。
根据题目描述,整个路径必须要满足路径上相邻的两条边的颜色不同,因此,当我们枚举以 为根节点且与 所连接的边为红色/蓝色的最大长度来求解这道题。
首先,我们根据动态规划的三要素:状态方程定义、状态转移方程、状态初始化来分析这道题
状态方程定义:定义 表示以 为根节点且与节点 所连接的边为红色/蓝色的最长路径。
状态转移方程:我们根据树形DP的核心思想:利用子节点的信息来更新父节点的信息可以得出
对于子节点 而言,如果与父节点 所连接的边为红色,则有
如果与父节点 所连接的边为蓝色,则有
对于以 为根节点的最长路径,显然是将 这两条路径连接起来,可以得到一条最长路径,如下图所示,图中1号节点的最长路径就是
状态初始化:所有状态初始化为0即可。
C++
#include <bits/stdc++.h>
using namespace std;
const int N=1E5+10;
struct Node{
int node,color; //定义边的另一个节点、边的颜色 0表示R 1表示B
};
vector<Node>g[N]; //领接表的vector写法 仅适用于点权建图
int f[N][2]; //以i为根节点且与节点i所连边的颜色为红色/蓝色的最大长度
int res;
void dfs(int u,int fa) //如果是有向图 就不需要fa这个变量
{
for(auto &x:g[u]) //访问u的所有节点
{
int son=x.node,color=x.color;
if(son==fa)continue; //无向边才需要这一句 保证每个节点只会被访问一次(不理解的可以直接背过)
dfs(son,u); //先遍历子节点,更新子节点信息,再由子节点来更新父节点信息
f[u][color]=max(f[u][color],f[son][1-color]+1); //根据状态方程,利用子节点信息更新父节点信息
res=max(res,f[u][0]+f[u][1]);
}
}
int main(){
int n;
cin>>n;
unordered_map<char,int>mp={{'R',0},{'B',1}}; //颜色的哈希映射
for(int i=1;i<n;i++){
int a,b;
char ch;
cin>>a>>b>>ch;
g[a].push_back({b,mp[ch]}); //a->b建立一条边
g[b].push_back({a,mp[ch]}); //b->a建立一条边
}
dfs(1,0); //从根节点开始,自顶向下搜索
cout<<res<<endl;
return 0;
}
Java
import java.util.*;
class Node {
int node, color; // 定义边的另一个节点、边的颜色 0表示R 1表示B
}
public class Main {
static final int N = (int)1e5 + 10;
static ArrayList<Node>[] g = new ArrayList[N]; // 邻接表的ArrayList写法,仅适用于点权建图
static int[][] f = new int[N][2]; // 以i为根节点且与节点i所连边的颜色为红色/蓝色的最大长度
static int res;
static void dfs(int u, int fa) { // 如果是有向图,就不需要fa这个变量
for(Node x : g[u]) { // 访问u的所有节点
int son = x.node, color = x.color;
if(son == fa) continue; // 无向边才需要这一句,保证每个节点只会被访问一次
dfs(son, u); // 先遍历子节点,更新子节点信息,再由子节点来更新父节点信息
f[u][color] = Math.max(f[u][color], f[son][1-color] + 1); // 根据状态方程,利用子节点信息更新父节点信息
res = Math.max(res, f[u][0] + f[u][1]);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Map<Character, Integer> mp = new HashMap<>(); // 颜色的哈希映射
mp.put('R', 0);
mp.put('B', 1);
for(int i = 1; i <= n; i++) g[i] = new ArrayList<>();
for(int i = 1; i < n; i++) {
int a = sc.nextInt(), b = sc.nextInt();
char ch = sc.next().charAt(0);
g[a].add(new Node(){{
node = b;
color = mp.get(ch);
}}); // a->b建立一条边
g[b].add(new Node(){{
node = a;
color = mp.get(ch);
}}); // b->a建立一条边
}
dfs(1, 0); // 从根节点开始,自顶向下搜索
System.out.println(res);
}
}
Python
import sys
sys.setrecursionlimit(10**6) # 设置递归深度上限
class Node:
def __init__(self, node, color): # 定义边的另一个节点、边的颜色 0表示R 1表示B
self.node = node
self.color = color
N = int(1e5) + 10
g = [[] for _ in range(N)] # 邻接表的列表写法,仅适用于点权建图
f = [[0, 0] for _ in range(N)] # 以i为根节点且与节点i所连边的颜色为红色/蓝色的最大长度
res = 0
def dfs(u, fa): # 如果是有向图,就不需要fa这个变量
global res
for x in g[u]: # 访问u的所有节点
son, color = x.node, x.color
if son == fa: continue # 无向边才需要这一句,保证每个节点只会被访问一次
dfs(son, u) # 先遍历子节点,更新子节点信息,再由子节点来更新父节点信息
f[u][color] = max(f[u][color], f[son][1-color] + 1) # 根据状态方程,利用子节点信息更新父节点信息
res = max(res, f[u][0] + f[u][1])
n = int(input())
mp = {'R': 0, 'B': 1} # 颜色的哈希映射
for i in range(1, n):
a, b, ch = input().split()
a, b = int(a), int(b)
g[a].append(Node(b, mp[ch])) # a->b建立一条边
g[b].append(Node(a, mp[ch])) # b->a建立一条边
dfs(1, 0) # 从根节点开始,自顶向下搜索
print(res)