第一题: 甜甜花酿鸡
在线测评链接:http://121.196.235.151/p/P1082
题目描述
薯条哥是一名厨艺达人,最近特别喜欢做甜甜花酿鸡,因为这种菜式的口感鲜美且具有一定的艺术性。
薯条哥的厨房里,堆满了新鲜的食材,其中包括大量的鸡肉和甜甜花。
然而,当他想要烹制一些甜甜花酿鸡时,他发现他的材料可能不够用。他有 a a a 个甜甜花, b b b 个鸡肉,以及 c c c 个神秘的魔法食材,可以当作甜甜花或者鸡肉使用。
薯条哥知道做一只甜甜花酿鸡需要 2 2 2 个甜甜花和 2 2 2 个鸡肉。他希望尽可能多地制作甜甜花酿鸡,而不浪费任何食材。
现在他需要你的帮助,来计算他最多能制作多少只甜甜花酿鸡。
输入描述
输入三个整数
a
,
b
,
c
a,b,c
a,b,c , 用空格隔开。
1
≤
a
,
b
,
c
≤
1
0
9
1\le a,b,c\le 10^9
1≤a,b,c≤109
输出描述
一个整数,代表可以制作的甜甜花酿鸡的最大数量。
样例
输入
3 3 3
输出
2
样例解释
可以将两个万能食材当作一个甜甜花和一个鸡肉,所以是 4 4 1
可以制作两个甜甜花酿鸡。
题解:分类讨论
首先,由于 c c c是魔法药材,我们先不考虑,我们分类讨论 a a a和 b b b的大小关系
- 情况1: a = b a=b a=b,那么显然,不利用 c c c的情况下,可以制作 ⌊ a 2 ⌋ \left \lfloor \frac{a}{2} \right \rfloor ⌊2a⌋个甜甜花酿鸡,如果利用 c c c的话,总共可以制作 ⌊ a + c 2 ⌋ \left \lfloor \frac{a+c}{2} \right \rfloor ⌊2a+c⌋个甜甜花酿鸡
- 情况2: a ≠ b a\ne b a=b,可以利用 c c c尽可能去平衡 a a a和 b b b的值
如果有 c ≥ ∣ a − b ∣ c\ge |a-b| c≥∣a−b∣,则可以先利用c使a和b尽可能相等,然后就把问题转换成情况1。
如果没有,则把 c c c全部累加到较小的一方即可。
本题可以直接假定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皇后
在线测评链接:http://121.196.235.151/p/P1083
题目描述
薯条哥是一个热衷于国际象棋的棋手,他最近在研究 n n n 皇后问题。在国际象棋中,皇后是一种强大的棋子,能够沿着横、竖、斜线攻击其他棋子。
而在 n n n 皇后问题中,皇后也是一种强大的棋子,它能攻击同一行、同一列以及同一 45 45 45 度角斜线和 135 135 135 度角斜线上的其他皇后。
薯条哥手上拿着一个 n × n n\times n n×n 的棋盘,上面已经放置了一些皇后。他希望再放置一个皇后,使得所有的皇后不会互相攻击。
对于一个 n × n n\times n n×n 的棋盘,有多种不同的摆放皇后的方式,而有些摆法可能会导致皇后之间发生攻击,有些摆法则不会。
因此,薯条哥需要找到所有满足条件的摆法,以便让他更好地研究 n n n 皇后问题,你能帮薯条哥求出有多少种放置方案吗?
输入描述
第一行输入一个正整数 n n n ,代表棋盘大小。
接下来的
n
n
n 行,每行输入一个仅由 .
和 *
组成的字符串,其中 *
代表放置了一个皇后, .
代表未放置皇后。
1 ≤ n ≤ 1000 1\le n\le 1000 1≤n≤1000
输出描述
输出薯条哥有多少种放置方案。如果给定的输入的棋盘中有两个皇后会互相攻击,则输出-1。
样例1
输入
3
. * .
. . .
. . .
输出
2
说明
只有左下角和右下角两个位置可以放置。
样例2
输入
3
. * .
. * .
. . .
输出
-1
题解:模拟
注意数据范围!这不是DFS的那道八皇后:https://www.luogu.com.cn/problem/P1219
那道题的数据范围是 n ≤ 13 n\le 13 n≤13,因此是可以用DFS求解的,本题 n ≤ 1000 n\le 1000 n≤1000,必须要设计一个时间复杂度为 O ( n 2 ) O(n^2) O(n2)的算法才不会超时。
我们可以根据当前棋盘已经放置的皇后位置,来对棋盘的任意一个位置 ( i , j ) (i,j) (i,j)是否可以放置皇后来做一个标记。
如果 ( x , y ) (x,y) (x,y)点已经放置了皇后,那么第 x x x行,第 y y y列,以及 ( x , y ) (x,y) (x,y)的两个对角线都不可放置皇后,对于行和列我们可以使用两个数组 r o w [ x ] row[x] row[x]和 c o l [ y ] col[y] col[y]来标记,对于两个对角线,我们可以使用 d i g [ x − y ] dig[x-y] dig[x−y]和 i d i g [ x + y ] idig[x+y] idig[x+y]这两个标记数组来判断是否可以放置,举个例子,如下图所示,45度的斜线上的点,都满足 x + y x+y x+y等于一个定值,因此可以用 i d i g [ x + y ] idig[x+y] idig[x+y]标记数组来判断。同理可得135度斜线上的点,都满足 x − y x-y x−y是一个定值,但是由于 x − y x-y x−y可能小于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)
第三题: 最长的通讯路径
在线测评链接:http://121.196.235.151/p/P1084
题目描述
薯条哥是一位研究者,他在研究网络传输时遇到了一个问题。
他拿到了一张通讯网络的拓扑结构图,其中每条通讯线路被染成了红色或者蓝色。
他想找到一条长度最长的通讯路径,使得路径上相邻的两条线路颜色不同。
输入描述
第一行输入一个正整数 n n n , 代表节点数量。
接下来的 n − 1 n-1 n−1 行,每行输入两个正整数 u , v u,v u,v 和一个字符 c h r chr chr ,代表节点 u u u 和节点 v v v 有一条边连接。
若为 'R'
代表这条边是红色, 'B'
代表这条边是蓝色。
1 ≤ n ≤ 1 0 5 1\le n \le 10^5 1≤n≤105
1 ≤ u , v ≤ n 1\le u,v\le n 1≤u,v≤n
保证输入的是一颗树。
输出描述
一个正整数,代表薯条哥可以选择的路径最大长度。
样例
输入
4
1 2 R
2 3 B
3 4 B
输出
2
样例解释
选择 1 − 2 − 3 1-2-3 1−2−3 的路径即可。
题解:树形DP
默认以1为根节点进行DFS遍历。选用其他节点也是不影响的,因为会枚举以 i i i为根节点的所有路径。
根据题目描述,整个路径必须要满足路径上相邻的两条边的颜色不同,因此,当我们枚举以 i i i为根节点且与 i i i所连接的边为红色/蓝色的最大长度来求解这道题。
首先,我们根据动态规划的三要素:状态方程定义、状态转移方程、状态初始化来分析这道题
状态方程定义:定义 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示以 i i i为根节点且与节点 i i i所连接的边为红色/蓝色的最长路径。
状态转移方程:我们根据树形DP的核心思想:利用子节点的信息来更新父节点的信息可以得出
对于子节点 v v v而言,如果与父节点 i i i所连接的边为红色,则有 f [ i ] [ 0 ] = m a x ( f [ i ] [ 0 ] , f [ v ] [ 1 ] + 1 ) f[i][0]=max(f[i][0],f[v][1]+1) f[i][0]=max(f[i][0],f[v][1]+1)
如果与父节点 i i i所连接的边为蓝色,则有 f [ i ] [ 1 ] = m a x ( f [ i ] [ 1 ] , f [ v ] [ 0 ] + 1 ) f[i][1]=max(f[i][1],f[v][0]+1) f[i][1]=max(f[i][1],f[v][0]+1)
对于以 i i i为根节点的最长路径,显然是将 f [ i ] [ 0 ] , f [ i ] [ 1 ] f[i][0],f[i][1] f[i][0],f[i][1]这两条路径连接起来,可以得到一条最长路径,如下图所示,图中1号节点的最长路径就是 f [ 1 ] [ 0 ] + f [ 1 ] [ 1 ] = 3 + 1 = 4 f[1][0]+f[1][1]=3+1=4 f[1][0]+f[1][1]=3+1=4
状态初始化:所有状态初始化为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)