NewOJ Week 8题解

本文介绍了编程竞赛中的几道题目,包括猜数字、升降数字、黑白配、最短缺失子序列和房间划分。题目涉及概率计算、贪心策略以及期望动态规划等算法,通过实例解析了如何运用这些方法解决问题。对于猜数字问题,求解x+y为奇数的概率;升降数字中找到最大的升降数字;黑白配游戏求期望回合数;最短缺失子序列判断;房间划分寻找平分面积的直线。每个问题都提供了详细的思路和代码实现。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

比赛链接:http://oj.ecustacm.cn/contest.php?cid=1022

题目总览

题目TAG难度来源补题链接
猜数字概率计算 C o d e C h e f CodeChef CodeChefhttp://oj.ecustacm.cn/problem.php?id=1826
升降数字贪心☆☆ P A C N W   2021 PACNW \ 2021 PACNW 2021http://oj.ecustacm.cn/problem.php?id=1827
黑白配期望 D P DP DP、状态压缩 D P DP DP☆☆☆☆ P A C N W   2021 PACNW \ 2021 PACNW 2021http://oj.ecustacm.cn/problem.php?id=1828
最短缺失子序列思维题、贪心、字符匹配☆☆☆☆ P A C N W   2021 PACNW \ 2021 PACNW 2021http://oj.ecustacm.cn/problem.php?id=1829
房间划分计算几何、三角形面积☆☆☆ P A C N W   2021 PACNW \ 2021 PACNW 2021http://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的期望回合数,在此情况下进行一轮游戏,存在两种可能:

  1. 游戏状态发生改变,即有人淘汰,记概率为 t o t tot tot
  2. 游戏状态未发生改变,即没有人淘汰,概率为 ( 1 − t o t ) (1-tot) (1tot)

那么状态 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]=totE[y]+(1tot)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=1kpyiE[x2yi]+(1i=1kpyi)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} x2yi

为了求解 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 totE[x]=i=1kpyiE[x2yi]+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 房间划分

题意: 给定一个凸多边形房间,现在需要将房间划分成等面积的两部分。
房间只有一个门,可以视为一个顶点,需要沿着直线将房间划分成两部分。
直线的一端必须是门,另一端必须是墙角或者墙壁。
如下图所示,最终需要求解出直线另一端点的坐标。

img

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=2n1SΔ(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]xp[i]=S2S12SS1

#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;
}

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

傅志凌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值