第一题:棋盘
在线测评链接:http://121.196.235.151/p/P1114
题目描述
薯条哥有一个 n×mn\times mn×m 的棋盘,一次移动可以选择上下左右四个方向移动一次,不同于普通棋盘,这个棋盘是循环的。
即 (x,m)(x, m)(x,m) 和 (x,1)(x, 1)(x,1) 两个点可以一步到达,其中 1≤x≤n1\leq x\leq n1≤x≤n 。同样的, (n,y)(n, y)(n,y) 和 (1,y)(1, y)(1,y) 两个点也可以一步到达,其中 1≤y≤m1\leq y\leq m1≤y≤m 。
现在薯条哥需要从 AAA 点先走到 BBB 点,再从 BBB 点走到 CCC 点,问最小移动次数是多少。
输入描述
第一行两个整数,nnn 和 mmm 。
接下来三行,第一行是点 AAA 的坐标 (xA,yA)(x_A,y_A)(xA,yA),第二行是点 BBB 的坐标 (xB,yB)(x_B,y_B)(xB,yB) ,第三行是点 CCC 的坐标 (xC,yC)(x_C,y_C)(xC,yC)。
1≤n,m≤109,1≤xA,xB,xC≤n,1≤yA,yB,yC≤m1\leq n,m\leq 10^9, 1\leq x_A,x_B,x_C\leq n, 1\leq y_A,y_B,y_C\leq m1≤n,m≤109,1≤xA,xB,xC≤n,1≤yA,yB,yC≤m
输出描述
输出从 AAA 到 BBB ,再从 BBB 到 CCC 的最小移动次数。
样例
输入
4 4
1 2
1 3
1 4
输出
2
题解:模拟
考虑任意点(a,b)(a,b)(a,b)到(c,d)(c,d)(c,d)的最小移动距离
首先,我们可以考虑从aaa到ccc的移动距离
- 方式1:直接到达:dist=abs(a−c)dist=abs(a-c)dist=abs(a−c)
- 方式2:先走到nnn,再由nnn走到1,再由1走到ccc,dist=n−abs(a−c)dist=n-abs(a-c)dist=n−abs(a−c)
两种方式的移动取最小值,dist=min(abs(a−c),n−abs(a−c))dist=min(abs(a-c),n-abs(a-c))dist=min(abs(a−c),n−abs(a−c))
因此(a,b)(a,b)(a,b)到(c,d)(c,d)(c,d)的最小移动距离dist=min(abs(a−c),n−abs(a−c))+min(abs(b−d),m−abs(b−d))dist=min(abs(a-c),n-abs(a-c))+min(abs(b-d),m-abs(b-d))dist=min(abs(a−c),n−abs(a−c))+min(abs(b−d),m−abs(b−d))
按照上述公式模拟即可
C++
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
int xA,yA,xB,yB,xC,yC;
cin>>xA>>yA;
cin>>xB>>yB;
cin>>xC>>yC;
int res=0;
res+=min(abs(xA-xB),n-abs(xA-xB));
res+=min(abs(yA-yB),m-abs(yA-yB)); //计算A->B最短距离
res+=min(abs(xB-xC),n-abs(xB-xC));
res+=min(abs(yB-yC),m-abs(yB-yC)); // 计算B->C最短距离
cout<<res<<endl;
return 0;
}
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int xA = scanner.nextInt();
int yA = scanner.nextInt();
int xB = scanner.nextInt();
int yB = scanner.nextInt();
int xC = scanner.nextInt();
int yC = scanner.nextInt();
int res = 0;
res += Math.min(Math.abs(xA - xB), n - Math.abs(xA - xB));
res += Math.min(Math.abs(yA - yB), m - Math.abs(yA - yB)); // 计算A到B的最短距离
res += Math.min(Math.abs(xB - xC), n - Math.abs(xB - xC));
res += Math.min(Math.abs(yB - yC), m - Math.abs(yB - yC)); // 计算B到C的最短距离
System.out.println(res);
}
}
Python
n, m = map(int, input().split())
xA, yA = map(int, input().split())
xB, yB = map(int, input().split())
xC, yC = map(int, input().split())
res = 0
res += min(abs(xA - xB), n - abs(xA - xB))
res += min(abs(yA - yB), m - abs(yA - yB)) # 计算A到B的最短距离
res += min(abs(xB - xC), n - abs(xB - xC))
res += min(abs(yB - yC), m - abs(yB - yC)) # 计算B到C的最短距离
print(res)
第二题:有根树的节点个数
在线评测链接:http://121.196.235.151/p/P1061
题目描述
薯条哥有一个 nnn 个节点的树,树根编号为 111 。
薯条哥可以在叶子节点上添加一个新的儿子节点,添加后,添加的节点变成了新的叶子节点。
若干次操作后,薯条哥想问你距离树根不超过 kkk 的节点最多可以有多少个。
输入描述
第一行,一个正整数nnn 表示树中节点个数,kkk 表示不超过树根的距离,
接下来 n−1n-1n−1 行,每行输入两个整数 uuu 和 vvv ,表示节点 uuu 和 vvv 之间有一条边。
1≤n≤105,1≤k≤109,1≤u,v≤n1 \leq n \leq 10^5, 1\leq k\leq 10^9, 1\leq u,v\leq n1≤n≤105,1≤k≤109,1≤u,v≤n
输出描述
一个整数,表示若干次操作后距离树根不超过 kkk 的节点最大数量。
样例
输入
4 2
1 2
1 3
1 4
输出
7
说明
样例解释
本身有4个节点到根节点的距离不超过k(1,2,3,4)
叶子节点是(2,3,4) 还可以再添加一个节点
因此总共的数量为4+3=7
题解:树形DFS
本题需要用到贡献法计数的思想:就是考虑每一个节点对答案的贡献。
如果不进行任意的操作,那么对应的答案就是树中所有与根节点距离≤k\le k≤k的节点总数cntcntcnt
我们现在需要去考虑:可以通过对叶子节点新增节点对答案的贡献。
首先,我们需要找到叶子结点,那什么样的节点是叶子结点?
显然,出度为0的节点就是叶子节点(入度为0的节点是根节点),但是本题给的是无向边,没办法去统计出度,我们换一个思路,如果一个节点是叶子结点,那么它有且仅有1条边与之相连(就是它的父节点和它构成的边)
知道了如何判断叶子节点,那么我们就需要考虑,对于每一个叶子节点,如何计算其对答案的贡献,也就是需要考虑其可以添加几个节点。这其实就跟叶子结点距离根节点的距离有关。
我们定义dist[i]dist[i]dist[i]表示节点iii到根节点的距离,初始化dist[1]=0dist[1]=0dist[1]=0
那么,在我们进行DFS遍历的时候,如果uuu是vvv的子节点,一定有dist[u]=dist[v]+1dist[u]=dist[v]+1dist[u]=dist[v]+1
因此,我们按照上一道题:子树节点个数的dfs代码,可以预处理出distdistdist数组的值。
然后,对于每一个叶子结点,我们可以把答案累加上ans=ans+max(0,k−dist[i])ans=ans+max(0,k-dist[i])ans=ans+max(0,k−dist[i])
注意:本题数据范围较大,ansansans变量需要取long long
C++
#include <bits/stdc++.h>
using namespace std;
const int N=1E5+10;
vector<int>g[N]; //领接表的vector写法 仅适用于点权建图
int dist[N]; //dist[i]记录节点i到根节点的距离
long long ans;
int n,k;
void dfs(int u,int fa) //如果是有向图 就不需要fa这个变量
{
if(dist[u]<=k){
ans++;
}
if(g[u].size()==1&&u!=1){ //u节点的度为1,且u节点不为根节点,说明该节点为叶子节点
ans+=max(0,k-dist[u]); //方案数累加
}
for(int &v:g[u]){
if(v==fa)continue;
dist[v]=dist[u]+1;
dfs(v,u);
}
}
int main(){
cin>>n>>k;
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
g[a].push_back(b); //a->b建立一条边
g[b].push_back(a); //b->a建立一条边
}
dist[1]=0; //初始化根节点的值
dfs(1,0); //从根节点开始,自顶向下搜索
cout<<ans<<endl;
return 0;
}
Java
import java.util.*;
public class Main {
static final int N = (int)1e5+10;
static List<Integer>[] g = new ArrayList[N]; // 邻接表的ArrayList写法,仅适用于点权建图
static int[] dist = new int[N]; // dist[i]记录节点i到根节点的距离
static long ans;
static int n, k;
static void dfs(int u, int fa) { // 如果是有向图,就不需要fa这个变量
if(dist[u] <= k) {
ans++;
}
if(g[u].size() == 1 && u != 1) { // u节点的度为1,且u节点不为根节点,说明该节点为叶子节点
ans += Math.max(0, k - dist[u]); // 方案数累加
}
for(int v : g[u]) {
if(v == fa) continue;
dist[v] = dist[u] + 1;
dfs(v, u);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
k = sc.nextInt();
for(int i = 1; i <= n; i++) {
g[i] = new ArrayList<>();
}
for(int i = 1; i < n; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
g[a].add(b); // a->b建立一条边
g[b].add(a); // b->a建立一条边
}
dist[1] = 0; // 初始化根节点的值
dfs(1, 0); // 从根节点开始,自顶向下搜索
System.out.println(ans);
}
}
Python3
import sys
sys.setrecursionlimit(10**6) # 设置递归深度上限
from collections import defaultdict
N = int(1e5) + 10
g = defaultdict(list) # 邻接表的字典写法,仅适用于点权建图
dist = [0] * N # dist[i]记录节点i到根节点的距离
ans = 0
n, k = 0, 0
def dfs(u, fa): # 如果是有向图,就不需要fa这个变量
global ans
if dist[u] <= k:
ans += 1
if len(g[u]) == 1 and u != 1: # u节点的度为1,且u节点不为根节点,说明该节点为叶子节点
ans += max(0, k - dist[u]) # 方案数累加
for v in g[u]:
if v == fa:
continue
dist[v] = dist[u] + 1
dfs(v, u)
n, k = map(int, input().split())
for _ in range(1, n):
a, b = map(int, input().split())
g[a].append(b) # a->b建立一条边
g[b].append(a) # b->a建立一条边
dist[1] = 0 # 初始化根节点的值
dfs(1, 0) # 从根节点开始,自顶向下搜索
print(ans)
第三题:抽卡程序
在线测评链接:http://121.196.235.151/p/P1115
题目描述
薯条哥很喜欢抽卡的感觉,但是他又没有那么多钱给游戏充钱,所以他写了个抽卡程序。
普通抽卡过程有三种抽卡结果,5星常驻卡、5星限定卡和非5星卡。
抽到5星常驻卡和5星限定卡的概率均为 p2\frac{p}{2}2p ,抽到非5星卡的概率为 1−p1-p1−p 。
如果抽到了5星常驻卡,则之后的抽卡结果将发生变化,变为 ppp 的概率抽到5星限定卡, 1−p1-p1−p 的概率抽到非5星卡。
如果连续89次都未抽到5星卡,则第90抽必然会抽到一张5星常驻卡或5星限定卡。
现在薯条哥想问你,他写的这个抽卡程序抽到5星限定卡的抽卡次数期望是多少?
输入描述
一个小数 ppp 表示抽卡概率,0<p<10 < p < 10<p<1
输出描述
一个小数,表示抽到5星限定卡的抽卡次数期望,请你将输出结果保留小数点后7位。
样例
输入
0.001
输出
129.1649522
题解:树形DP
本题有一个非常巧妙的做法, 但考虑到让大家更好地理解期望DP,我还是从一个最基本的DP的思想来跟大家讲解这道题的思路。
首先,显然如果薯条哥非常非,他需要抽满180次,才能保证自己抽到5星限定卡,也就是说,我们如果定义f[i]f[i]f[i]为第iii次抽到5星限定卡的概率,那么总的期望就为E=∑i=1180f[i]×iE=\sum_{i=1}^{180}f[i]\times iE=∑i=1180f[i]×i
我们先考虑一个简单的情况,也就是前90抽的情况
我们发现一个很重要的结论:5星常驻卡和5星限定卡的出货概率是独立且相同的,各占50%!
我们根据动态规划的三要素:状态方程定义、状态初始化、状态转移方程来分析这道题
状态方程定义
为了便于我们后面计算,我们定义f[i]f[i]f[i]为抽卡次数为iii抽到5星卡的概率。
状态初始化
所有状态初始化为0即可。
状态转移方程
我们发现,第iii次抽到5星卡的概率,说明前i−1i-1i−1次都没有抽到5星卡,并且第iii次抽到了5星卡
因此有f[i]=(1−∑j=1j=i−1f[j])×pf[i]=(1-\sum_{j=1}^{j=i-1}f[j])\times pf[i]=(1−∑j=1j=i−1f[j])×p
然后,对于第iii次抽到5星限定卡的期望就等于f[i]×i×12f[i]\times i\times \frac{1}{2}f[i]×i×21
特殊情况:i=90i=90i=90的时候,ppp为固定值0.5。
然后我们考虑从91~180抽的期望的计算方式
我们可以发现,这个是有非常多的情况,我们拿81抽出货举例,有以下几种情况
- 情况1:第1抽就出了5星卡,但是歪到了常驻5星,然后又接连抽了90抽,才抽到限定5星
- 情况2:第2抽就出了5星卡,但是歪到了常驻5星,然后又接连抽了89抽,才抽到限定5星
- 情况3:第3抽就出了5星卡,但是歪到了常驻5星,然后又接连抽了88抽,才抽到限定5星
因此,我们可以写一个双重循环,来枚举这些情况,注意,歪了常驻5星之后,后面每次抽到限定5星的概率不需要除以2(大保底必定不歪!!)
具体可以参考下面代码和代码中的注释
C++
#include <bits/stdc++.h>
using namespace std;
double f[91]; //f[i]表示抽卡次数为i的时候,出五星卡的概率
double s[91]; //s[i]表示f[i]的前缀和数组
double p; //出五星的概率
int main() {
double res=0;
cin>>p;
for(int i=1;i<90;i++){ //先计算1~89抽的期望,并更新f[i]和s[i]数组
f[i]=(1-s[i-1])*p;
res+=f[i]*i/2.0; //累加期望(出限定五星的概率仅为50%)
s[i]=s[i-1]+f[i]; //前缀和数组更新
}
f[90]=(1-s[89])*1.0; //第90抽必出五星
res+=90*f[90]/2.0; //第90抽的期望,出限定五星的概率仅为50%
for(int i=1;i<=90;i++){ //考虑小保底之后出货的期望
for(int j=1;j<=90;j++){
double p1=f[i]/2.0,p2=f[j]; //p1为第i抽抽到常驻五星的概率,p2为第i抽之后,抽到第j抽抽到限定五星的概率
res+=(i+j)*p1*p2;
}
}
cout << fixed;
cout.precision(7); //保留小数点后7位
cout <<res<<endl;
return 0;
}
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
double[] f = new double[91]; // f[i]表示抽卡次数为i的时候,出五星卡的概率
double[] s = new double[91]; // s[i]表示f[i]的前缀和数组
double p; // 出五星的概率
Scanner scanner = new Scanner(System.in);
p = scanner.nextDouble();
double res = 0;
for (int i = 1; i < 90; i++) { // 先计算1~89抽的期望,并更新f[i]和s[i]数组
f[i] = (1 - s[i - 1]) * p;
res += f[i] * i / 2.0; // 累加期望(出限定五星的概率仅为50%)
s[i] = s[i - 1] + f[i]; // 前缀和数组更新
}
f[90] = (1 - s[89]) * 1.0; // 第90抽必出五星
res += 90 * f[90] / 2.0; // 第90抽的期望,出限定五星的概率仅为50%
for (int i = 1; i <= 90; i++) { // 考虑小保底之后出货的期望
for (int j = 1; j <= 90; j++) {
double p1 = f[i] / 2.0, p2 = f[j]; // p1为第i抽抽到常驻五星的概率,p2为第i抽之后,抽到第j抽抽到限定五星的概率
res += (i + j) * p1 * p2;
}
}
System.out.printf("%.7f\n", res); // 保留小数点后7位
}
}
Python
f = [0.0] * 91 # f[i]表示抽卡次数为i的时候,出五星卡的概率
s = [0.0] * 91 # s[i]表示f[i]的前缀和数组
p = float(input()) # 出五星的概率
res = 0.0
for i in range(1, 90): # 先计算1~89抽的期望,并更新f[i]和s[i]数组
f[i] = (1 - s[i - 1]) * p
res += f[i] * i / 2.0 # 累加期望(出限定五星的概率仅为50%)
s[i] = s[i - 1] + f[i] # 前缀和数组更新
f[90] = (1 - s[89]) * 1.0 # 第90抽必出五星
res += 90 * f[90] / 2.0 # 第90抽的期望,出限定五星的概率仅为50%
for i in range(1, 91): # 考虑小保底之后出货的期望
for j in range(1, 91):
p1 = f[i] / 2.0 # p1为第i抽抽到常驻五星的概率
p2 = f[j] # p2为第i抽之后,抽到第j抽抽到限定五星的概率
res += (i + j) * p1 * p2
print("{:.7f}".format(res)) # 保留小数点后7位
173

被折叠的 条评论
为什么被折叠?



