比赛链接:http://oj.ecustacm.cn/contest.php?cid=1022
题目总览
| 题目 | TAG | 难度 | 来源 | 补题链接 |
|---|---|---|---|---|
| 猜数字 | 概率计算 | ☆ | C o d e C h e f CodeChef CodeChef | http://oj.ecustacm.cn/problem.php?id=1826 |
| 升降数字 | 贪心 | ☆☆ | P A C N W 2021 PACNW \ 2021 PACNW 2021 | http://oj.ecustacm.cn/problem.php?id=1827 |
| 黑白配 | 期望 D P DP DP、状态压缩 D P DP DP | ☆☆☆☆ | P A C N W 2021 PACNW \ 2021 PACNW 2021 | http://oj.ecustacm.cn/problem.php?id=1828 |
| 最短缺失子序列 | 思维题、贪心、字符匹配 | ☆☆☆☆ | P A C N W 2021 PACNW \ 2021 PACNW 2021 | http://oj.ecustacm.cn/problem.php?id=1829 |
| 房间划分 | 计算几何、三角形面积 | ☆☆☆ | P A C N W 2021 PACNW \ 2021 PACNW 2021 | http://oj.ecustacm.cn/problem.php?id=1830 |
A 猜数字
题意: A l i c e Alice Alice从数字 [ 1 , N ] [1,N] [1,N]中随机选择一个整数作为 x x x, B o b Bob Bob从数字 [ 1 , M ] [1,M] [1,M]中随机选择一个整数作为 y y y。求 x + y x+y x+y等于奇数的概率是多少?
Tag: 概率计算
难度: ☆
来源: C o d e C h e f CodeChef CodeChef
思路: 总共存在 N M NM NM种选择情况。要求和为奇数,要么 x x x奇 y y y偶,或者 x x x偶 y y y奇。
可以直接分类讨论求出 [ 1 , N ] [1,N] [1,N]的奇数、偶数数量,也可以根据数学规律计算出 [ 1 , N ] [1,N] [1,N]中的奇数个数为 ⌊ N + 1 2 ⌋ \lfloor \frac{N+1}{2}\rfloor ⌊2N+1⌋,偶数个数为 ⌊ N 2 ⌋ \lfloor \frac{N}{2} \rfloor ⌊2N⌋。
最终要对答案进行约分,求出最大公因数即可。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T;
cin >> T;
while(T--)
{
long long n, m;
cin >> n >> m;
//奇数:(n + 1) / 2,偶数n / 2
long long p = ((n + 1) / 2) * (m / 2) + ((m + 1) / 2) * (n / 2);
long long q = n * m;
long long g = __gcd(p, q);
cout<<p/g<<"/"<<q/g<<endl;
}
return 0;
}
B 升降数字
题意: 升降数字:一个数字可以被分成两部分(可能有一部分是空的),第一部分属于非递减,第二部分属于非递增。现在给定数字 n n n,请求出不超过 n n n的最大的升降数字。
Tag: 贪心
难度: ☆☆
来源: P A C N W 2021 PACNW \ 2021 PACNW 2021
思路: 对于给定的数字 n n n,直接从前往后找出最远的满足非递减的部分,然后再接着找出最远的满足非递增部分。
例如数字 12345666544342 12345666544342 12345666544342,首先从前往后找非递减部分: 12345666 12345666 12345666;然后接着找非递增, 123456665443 123456665443 123456665443,后面的部分是不满足的部分。
题目要求第一个是要求比数字 n n n小,第二个是要把后半部分变成非递增。则最优情况是将后面所有非法的全部变成最后一个合法的数字。
上面的例子也就是 12345666544333 12345666544333 12345666544333。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
char s[maxn];
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%s", s);
int n = strlen(s);
int idx = 0;
//非递减部分
while(s[idx] <= s[idx + 1] && idx + 1 < n)
idx++;
//非递增部分
while(s[idx] >= s[idx + 1] && idx + 1 < n)
idx++;
//非法部分
for(int i = idx; i < n; i++)
s[i] = s[idx];
printf("%s\n", s);
}
return 0;
}
C 黑白配
题意: 黑白配是一个经典的游戏,在每一轮中,孩子们将手朝上(白色)或者朝下(黑色)。
如果所有的孩子做出相同的选择,只有一个例外,那么剩下的孩子将会被淘汰。
游戏重复进行,直到只剩下两个孩子停止。
每个孩子有一个固定的概率独立选择是否将手朝上。
给定
n
(
≤
20
)
n(\le 20)
n(≤20)个孩子的概率,请输出游戏期望回合数是多少。
Tag: 期望 D P DP DP、状态压缩 D P DP DP
难度: ☆☆☆☆
来源: P A C N W 2021 PACNW \ 2021 PACNW 2021
思路: 初始状态是 n n n个人都参与游戏,终止状态是参与人数等于 2 2 2个人。由于 n n n很小,用二进制数字 x x x表示 n n n个人的淘汰情况。
例如 x = 3 = ( 111 ) 2 x=3=(111)_2 x=3=(111)2表示1、2、3均未淘汰,而 x = 2 = ( 010 ) 2 x=2=(010)_2 x=2=(010)2则表示 1 1 1、 3 3 3被淘汰。
d p [ x ] dp[x] dp[x]表示状态为 x x x的期望回合数,在此情况下进行一轮游戏,存在两种可能:
- 游戏状态发生改变,即有人淘汰,记概率为 t o t tot tot
- 游戏状态未发生改变,即没有人淘汰,概率为 ( 1 − t o t ) (1-tot) (1−tot)
那么状态
x
x
x的期望轮次等于:
E
[
x
]
=
t
o
t
⋅
E
[
y
]
+
(
1
−
t
o
t
)
⋅
E
[
x
]
+
1
E[x]=tot \cdot E[y]+(1-tot)\cdot E[x]+1
E[x]=tot⋅E[y]+(1−tot)⋅E[x]+1
上式中的
y
y
y表示
x
x
x状态淘汰某个人后变成的状态,实际上可能存在
k
k
k种转移情况:
E
[
x
]
=
∑
i
=
1
k
p
y
i
⋅
E
[
x
−
2
y
i
]
+
(
1
−
∑
i
=
1
k
p
y
i
)
⋅
E
[
x
]
+
1
E[x]=\sum_{i=1}^k p_{y_i}\cdot E[x-2^{y_i}] + \left(1-\sum_{i=1}^kp_{y_i}\right)\cdot E[x]+1
E[x]=i=1∑kpyi⋅E[x−2yi]+(1−i=1∑kpyi)⋅E[x]+1
上式的
∑
i
=
1
k
p
y
i
\sum_{i=1}^kp_{y_i}
∑i=1kpyi就等于
t
o
t
tot
tot,总共存在
k
k
k种淘汰情况,其中
k
k
k表示
x
x
x中1的个数,
x
x
x的第
y
i
y_i
yi位等于1。
第 y i y_i yi个人淘汰后,状态由 x x x转变成 x − 2 y i x-2^{y_i} x−2yi。
为了求解
E
[
x
]
E[x]
E[x],将
E
[
x
]
E[x]
E[x]放在一边,用
t
o
t
tot
tot简化上式:
t
o
t
⋅
E
[
x
]
=
∑
i
=
1
k
p
y
i
⋅
E
[
x
−
2
y
i
]
+
1
tot\cdot E[x] =\sum_{i=1}^k p_{y_i}\cdot E[x-2^{y_i}]+1
tot⋅E[x]=i=1∑kpyi⋅E[x−2yi]+1
这样暴力用二进制枚举求解
E
E
E数组即可。注意
p
y
i
p_{y_i}
pyi表示的是在状态
x
x
x下第
y
i
y_i
yi个人淘汰的概率,存在两种情况:其他人全白第
y
i
y_i
yi个人黑,或者反过来。
#include<bits/stdc++.h>
using namespace std;
int n;
double p[20];
double dp[(1 << 20) + 10];
//dp[i]表示剩余人数状态为i时的期望轮数
inline int bin_count(int x)
{
int ans = 0;
while(x)
++ans, x -= (x & (-x));
return ans;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++)
cin >> p[i];
for(int x = 0; x < (1 << n); x++)
{
if(bin_count(x) <= 2)continue;
//当前状态下所有人出白的概率,出黑的概率
double aw = 1.0, ab = 1.0;
for(int i = 0; i < n; i++)if(x & (1 << i))
aw *= p[i], ab *= (1 - p[i]);
//tot表示当前状态x可以转移到其他状态的概率(有人淘汰的概率)
double tot = 0.0;
for(int i = 0; i < n; i++)if(x & (1 << i))
{
//pi表示淘汰第i个人的概率
double pi = aw / p[i] * (1 - p[i]) + ab / (1 - p[i]) * p[i];
dp[x] += pi * dp[x - (1 << i)];
tot += pi;
}
dp[x] = (dp[x] + 1) / tot;
}
cout<<fixed<<setprecision(12)<<dp[(1 << n) - 1]<<endl;
return 0;
}
D 最短缺失子序列
题意:字符串
t
t
t是字符串
s
s
s的子序列:字符串
s
s
s删除
0
0
0个或者多个字符可变成字符
t
t
t。
字符串
t
t
t是字符串
s
s
s的缺失子序列:字符串
t
t
t不是字符串
s
s
s的子序列,但是字符串
t
t
t中出现的字母,均在
s
s
s中出现过。
字符串
t
t
t是字符串
s
s
s的最短缺失子序列:字符串
t
t
t是字符串
s
s
s的缺失子序列,同时长度是最短的。
现在给定字符串
s
s
s,询问
m
m
m次,每次询问一个字符串
t
t
t是否为
s
s
s的最短缺失子序列。
Tag: 思维题、贪心、字符匹配
难度: ☆☆☆☆
来源: P A C N W 2021 PACNW \ 2021 PACNW 2021
思路: 首先最短缺失子序列长度是固定的,因此第一步是求出这个长度。
对于仅由前 c c c个字母组成的字符串 s s s,最短缺失子序列长度是多少呢?
例如仅有前 3 3 3个字母组成的字符串 " a b c c b a " "abccba" "abccba",可以发现在其中长度为1的子序列不是缺失子序列,长度为2的子序列 a a , a b , a c , b a , b b , b c , c a , c b , c c aa,ab,ac,ba,bb,bc,ca,cb,cc aa,ab,ac,ba,bb,bc,ca,cb,cc也不是缺失子序列。
什么情况下长度为 i i i的子序列全都不是缺失子序列?
前 c c c个字母恰好出现 i i i次?更确切的说应该是存在 i i i段,每段包含前 c c c个字母至少 1 1 1次。
例如上述字符串 a b c c b a abccba abccba,可以分成两端 a b c abc abc、 c b a cba cba,正好 a b c abc abc都出现 1 1 1次,这样长度为 2 2 2的子序列肯定都可以构造出,只要每段选一个字母即可。
再例如字符串 a b c c a b c c a abccabcca abccabcca,最多分成两部分 a b c abc abc、 c a b c c a cabcca cabcca,每部分 a b c abc abc都至少出现 1 1 1次,说明最短缺失子序列长度为 3 3 3。
所以给定字符串 s s s,直接贪心从左往右每次从 l l l开始找一个最靠左的端点 r r r,使得 [ l , r ] [l,r] [l,r]满足前 c c c个字母至少出现1次。找出的段数+1就是最短缺失子序列长度。
求出长度之后,对于每次询问 t t t只需要判断两件事:
1、字符串 t t t的长度是否等于最短缺失子序列
2、 t t t是否不是 s s s的子序列。
判断字符串 t t t是否不是 s s s的子序列,可以利用数组 N e x t [ i ] [ j ] Next[i][j] Next[i][j]在线性时间内判断。
N e x t [ i ] [ j ] Next[i][j] Next[i][j]表示第 i i i个位置之后第 j j j个字母的位置,在输入的时候就可以预处理好。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
char v[30], s[maxn], t[maxn];
///Next[i][j]表示第i位后字符'A'+j的位置
int Next[maxn][26];
///Next[0]存储每个字符第一次出现的位置
int main()
{
scanf("%s", v + 1);
scanf("%s", s + 1);
int vlen = strlen(v + 1), slen = strlen(s + 1);
//求最短缺失子序列长度len
int K = 0, len = 1;
for(int i = 1; i <= vlen; i++)
K |= (1 << (v[i] - 'A'));
int tK = 0;
for(int i = 1; i <= slen; i++)
{
tK |= (1 << (s[i] - 'A'));
if(tK == K)len++, tK = 0;
//对于字符s[i],往前暴力更新Next数组
for(int j = i - 1; j >= 0; j--)
{
Next[j][s[i] - 'A'] = i;
//直到找到上一个s[i]停止
if(s[j] == s[i])break;
}
}
int n;
scanf("%d", &n);
while(n--)
{
scanf("%s", t + 1);
int tlen = strlen(t + 1);
int ok = 0;
if(tlen == len)//保证最短
{
int pos = 0;
for(int i = 1; i <= tlen; i++)
{
pos = Next[pos][t[i] - 'A'];
if(!pos)break;
}
//pos等于0说明是无法匹配的
//此时为缺失子序列
ok = (pos == 0);
}
printf("%d\n", ok);
}
return 0;
}
E 房间划分
题意: 给定一个凸多边形房间,现在需要将房间划分成等面积的两部分。
房间只有一个门,可以视为一个顶点,需要沿着直线将房间划分成两部分。
直线的一端必须是门,另一端必须是墙角或者墙壁。
如下图所示,最终需要求解出直线另一端点的坐标。

Tag: 计算几何、三角形面积
难度: ☆☆☆
来源: P A C N W 2021 PACNW \ 2021 PACNW 2021
思路: 题目相当于给一个凸多边形,找一条直线使得面积平分。直线的一点在第一个端点上,请求出另一个在凸多边形边上的端点。
利用三角形面积公式可以求出凸多边形面积 S = ∑ i = 2 n − 1 S Δ ( p [ 1 ] , p [ i ] , p [ i + 1 ] ) S=\sum_{i=2}^{n-1}S_\Delta(p[1],p[i],p[i+1]) S=∑i=2n−1SΔ(p[1],p[i],p[i+1])。
求出 S S S后,求第几个三角形加入后,面积恰好从小于 S 2 \frac{S}{2} 2S变成大于等于 S 2 \frac{S}{2} 2S。
假设
Δ
(
p
[
1
]
,
p
[
i
]
,
p
[
i
+
1
]
)
\Delta(p[1],p[i],p[i+1])
Δ(p[1],p[i],p[i+1])后恰好满足上述条件,记加入前面积为
S
1
S_1
S1,加入后面积为
S
2
S_2
S2,则在线段
[
p
[
i
]
,
p
[
i
+
1
]
]
[p[i],p[i+1]]
[p[i],p[i+1]]等比例找一个点
x
x
x满足:
∣
∣
x
−
p
[
i
]
∣
∣
∣
∣
p
[
i
+
1
]
−
p
[
i
]
∣
∣
=
S
2
−
S
1
S
2
−
S
1
\frac{||x-p[i]||}{||p[i+1]-p[i]||} = \frac{\frac{S}{2}-S_1}{S_2-S_1}
∣∣p[i+1]−p[i]∣∣∣∣x−p[i]∣∣=S2−S12S−S1
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
typedef long long ll;
int n;
struct Point
{
double x, y;
Point(){}
Point(double x, double y):x(x), y(y){}
void input()
{
scanf("%lf%lf", &x, &y);
}
Point operator -(const Point& a)const
{
return Point(x - a.x, y - a.y);
}
Point operator +(const Point& a)const
{
return Point(x + a.x, y + a.y);
}
Point operator *(double p)const
{
return Point(x * p, y * p);
}
}node[maxn];
typedef Point Vector;
//叉积
inline double Cross(Vector A, Vector B){
return A.x*B.y - A.y*B.x;
}
//三角形面积
inline double Area(Point A, Point B, Point C){
return 0.5 * Cross(B-A, C-A);
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
node[i].input();
double half_area = 0;
for(int i = 2; i <= n - 1; i++)
half_area += Area(node[1], node[i], node[i + 1]);
half_area *= 0.5;
double now_area = 0;
for(int i = 2; i <= n - 1; i++)
{
double next_area = now_area + Area(node[1], node[i], node[i + 1]);
if(now_area <= half_area && next_area >= half_area)
{
//cout<<i<<" "<<now_area<<" "<<half_area<<" "<<next_area<<endl;
//AB = AM + MB
//(half_area - now_area) : (next_area - now_area) = AM : AB
double p = (half_area - now_area) / (next_area - now_area);
Point delta = (node[i + 1] - node[i]) * p;
Point ans = node[i] + delta;
printf("%.9f %.9f\n", ans.x, ans.y);
break;
}
now_area = next_area;
}
return 0;
}
本文介绍了编程竞赛中的几道题目,包括猜数字、升降数字、黑白配、最短缺失子序列和房间划分。题目涉及概率计算、贪心策略以及期望动态规划等算法,通过实例解析了如何运用这些方法解决问题。对于猜数字问题,求解x+y为奇数的概率;升降数字中找到最大的升降数字;黑白配游戏求期望回合数;最短缺失子序列判断;房间划分寻找平分面积的直线。每个问题都提供了详细的思路和代码实现。
1152

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



