关于 强连通分量 缩点 tarjan是什么 自行搜索,这里只是封了一个板子

Codeforces Round #490 (Div. 3) E题

题目一眼可得tarjan 也不是一次两次了 封了板子,最开始还想做个模板类,但仔细一想,时间复杂度导致点个数不会超int,所以如果点序号大于int先离散化再做

E题 AC CODE

板子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/* construct: vertexsize
* auto tarjan = new Tarjan(vertex_size)
*
* addpath // 1<=from_vertex,to_vertex<= vertex_size
* tarjan.addpath(from_vertex,to_vertex)
*
* prepare an result array,and work
* int res[vertex_size+1];
* tarjan.work(res);
*
* return:
* res[vertex_id] ===== after_tarjan_vertex_group_id
*/
class Tarjan{
int *low;// lowest node
int *dfn;// deep first node
int *stk;// stack
bool *instk;
bool *visited;

vector<int> * p; // one-way road
int stki;
int id;
int n;
// strongly connected components强联通分量
void scc(int v) {
low[v] = dfn[v] = ++id;
stk[stki++] = v;
instk[v] = true;
visited[v] = true;
for(auto w:p[v]){
if(!visited[w]){
scc(w);
low[v] = min(low[v],low[w]); //v或v的子树能够追溯到的最早的栈中节点的次序编号
} else if(instk[w]){ //v->w后向边
low[v] = min(low[v],dfn[w]);
}
}
int u;
if(low[v] == dfn[v]) {
do{
u = stk[--stki];
dfn[u] = v; //缩点
instk[u] = false; //出栈解除标记
}while(u != v);
}
}
public:
Tarjan(int SZ){
n = SZ;
low = new int[n+1];
stk = new int[n+1];
dfn = new int[n+1];
instk = new bool[n+1];
visited = new bool[n+1];
p = new vector<int>[n+1];
rep(i,0,n+1){
visited[i] = false;
}
id = 0;
stki = 0;
}
~Tarjan(){
delete [] low;
delete [] stk;
delete [] dfn;
delete [] instk;
delete [] visited;
delete [] p;
}
void work(int *ret){
rep(i,1,n+1){
if(!visited[i]){
scc(i);
}
}
rep(i,1,n+1)
ret[i]=dfn[i];
}
void addpath(int i,int j){
p[i].pb(j);
}
};

基本上 使用分3个步骤就好

  1. 根据size初始化

  2. 给它加单向边.addpath(from,to)

  3. 准备一个结果数组a,调用.work(a)

  4. 得到的结果数组a[原来的点序号]=缩点后的点序号

  5. emmm 需要加两个宏使用 #define rep(i,a,n) for (int i=a;i<n;i++)#define pb push_back

增强复用+简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define rep(i,a,n) for (ll i=a;i<n;i++)
#define pb push_back

class Tarjan{
vector<int> low;
vector<int> dfn;
stack<int> stk;
vector<int> res;
vector<vector<int> > p;
int n;
void scc(int v) {
static int id = 0;
low[v] = dfn[v] = ++id;
stk.push(v);
for(auto w:p[v]){
if(!dfn[w]){ // 未访问过
scc(w);
low[v] = min(low[v],low[w]);
} else if(!res[w]){ // 访问过但没有结果(在栈中)
low[v] = min(low[v],dfn[w]);
}
}
int u;
if(low[v] == dfn[v]) {
do{
res[u = stk.top()] = v;
stk.pop();
}while(u != v);
}
}
public:
Tarjan(int SZ):n(SZ){
low = vector<int>(n+1);
stk = {};
vector<int>(n+1);
dfn = vector<int>(n+1);
res = vector<int> (n+1);
p = vector<vector<int> >(n+1);
}
vector<int> calc(){
rep(i,1,n+1){
if(!res[i]){
scc(i);
}
}
return res;
}
void p2(int i,int j){
p[i].pb(j);
}
};

int main(){
int n,m;
// 点,有向边,
cin>>n>>m;
Tarjan tarjan(n);
rep(i,0,m){
int u,v;
scanf("%d %d",&u,&v);
tarjan.p2(u,v);
}
vector<int> num = tarjan.calc(); // scc 联通分量 标识
rep(i,1,n+1){
printf("%lld: %d\n",i,num[i]);
}
return 0;
}

/**
1->2->3->1
3->4->5

5 5
1 2
2 3
3 1
3 4
4 5
*/

简述操作

tarjan(u)

  1. 深搜: 标记序号, 到达最小, u进栈
  2. 子点v未访问(dfn[v] == 0), 递归tarjan(v), low[u] = min(low[u],low[v])
  3. 子点v在栈中(result[v] == 0), low[u] = min(low[u],深搜序号[v])
  4. 当low[u] == dfn[u]时, 逐个出栈直到遇到u, 这些出栈的都是属于u这个联通分量 result[i] = u

D

难过的是 D 题的 算法细节已经对了,然而还是超时

通过 学习红名大佬的代码,发现是我们共同的n平方的算法的常数不同,我的常数是map查找 hash的速度+stl的效率,而它通过离散化,真的就只有个位数常数,,,,然而仔细想想,离散化的化我也不是遇到一次两次了,感觉不该再栽倒在同一个地方。

拷贝 提取 并修改了 红名大佬代码中做离散化的部分。

1
2
3
4
sort(li.begin(), li.end());
li.resize(unique(li.begin(), li.end())-li.begin());
for(int i=0; i<n; i++)
newv[i]=lower_bound(li.begin(), li.end(), sourcev[i])-li.begin();

解释一下内容,sourcev中存的原始数据

li是 vector,把原始数据放进去

然后 通过排序 和 去重

再对每一个 原始数据找到所离散化出的新结果

参考

大佬的代码

表格整理

图片主要来源于官方的讲义PDF以及Wikipedia

监督学习

线性回归

将输入数据,如图中的红点,求得一条直线表示数据中的线性关系,并且这条直线在概率期望上达到最佳(后面算法省略这句)。

线性回归

梯度下降

找函数极值小值点,图中相同颜色线为等高线,越靠近中心高度越低,运用高数的梯度运算和梯度下降能够得到如图中蓝色x标记的逐步逼近的极值点。

梯度下降

Normal Equation

同样是解决线性回归问题,和梯度下降不同的是,运用矩阵运算,直接得到参数的表达式

Normal Equations

Logistics 回归

分类算法,对一侧数据0,另一侧数据1的训练数据建立分类器,图中的点是 训练输入,线是得到的logistics函数

logistics

高斯切线法

高数知识,二次收敛,加速点的收敛,如图 通过计算切线与坐标轴的交点作为下一次的迭代起始值。

gao si qie xian

广义线性模型GLM

按照所提出的假设模型,能够直接得到所需要的 拟合函数,可以用来证明上面 的线性回归中最小二乘法是最优,以及Logistics 回归中的函数选取。

GLM

softmax 回归

分类到对互斥的k个类别,公式推导采用带入GLM

softmax

高斯判别分析GDA

对0分布满足高斯分布,1分布也满足高斯分布的分布进行线性分类。

GDA

朴素贝叶斯

整体与特征来判断整体的分类,如垃圾邮件根据出现的词汇进行分类,很暴力直接计算概率

bayes

拉普拉斯平滑

解决朴素贝叶斯中可能出现的0除以0的情况,分子+1,分母+可分的种类数k

laplace

最优线性分类器

如图能够找到将 数据分开,并且离分割线最近的点的距离值最大的分类器。

jihejuli

拉格朗日对偶、KKT

用于具体解决 最优线性分类器的支撑方法

KKT

核函数

将变量非线性变化映射到高维空间,减小计算量,表示量,配合其它算法使用能获得高维空间性质。

Kernel

支持向量机SVM

将低维不可线性分割的 通过核函数映射到高维度,再在高维中进行最优线性分割

SVM

L1 Regularization

在有部分异常点时的分割,通过添加惩罚项解决如下图异常点导致变化过大的问题。

L1regularization

SMO

对于多个参数 每次选一个参数进行取极值点,SMO能在带等式与不等式的约束限定情况下,每次两个参数逐步逼近。

SMO

均方误差MSE

能够用于分析 过拟合 还是 欠拟合

mse

错误分析

按步骤替代/隔离分析,逐个增加或逐个减少。按训练误差 方差,实验 误差方差分析。

error

VC维、hoeffding不定式

用于证明概率下训练集和误差的上下界存在性。

验证方式、模型选择

将部分的训练数据不用于训练而用于检验模型

感知器

感知器:转换后的值小于0输出-1,大于等于0输出

无监督学习

k-means

对无标记的点进行分类(寻找分类的中心)

kmeans

高斯混合模型GMM

可以看作类似前面的高斯判别模型GDA,但是现在的输入数据是无标记的

GMM

EM算法

用于GMM等无标记的混合模型的分离,先假设隐含变量Z以及它的分布Q,和k-means的思想类似,E-step优化Q,M-step优化参数,重复直到收敛 [使用Jensen不等式],分离效果见上图

EM

因子分析

对训练集量少,维数大,分类的类别少的分布进行分类,思想是建立隐含低维度变量z,通过矩阵转化投影到高维,再加上高斯扰动误差

Factor

主成分分析PCA

对于高维空间的数据,找到其前k个相互正交的关键维度的向量,可用线性代数奇异值分解SVD进行快速计算。可以用于降维度,作为其它算法的预处理步骤,或找到关系的主要方面。

PCA

独立成分分析ICA

对于多维度,相互独立的非高斯分布成分,找到每个成分的轴,并将所有轴转换为正交轴。可用于特征提取,特征分离,如音频分离,计算人脸识别面部特征向量,对脑电波数据分离预处理去除眨眼和心跳信号。

ICA

马尔科夫模型

马尔科夫决策过程 MDP

能够学习带有状态,和基于状态动作的一类事情,学出一个策略集,如自动驾驶,需要设置奖励函数,概率函数等参数函数。策略迭代和值迭代

MDP

离散化连续状态的MDP

也就是字面意思离散化,在2维下工作一般不错,高维度后无论是维数灾难还是离散化难度,以及模型最终产物都难以普遍满意

MDPlsh

MDP中的模型模拟器

用于概率状态未知时,用实验+拟合得到模型,从而代替概率函数的位置

simulator

线性二次型调节控制LQR

解决状态依赖于前一个状态前一个动作以及时间的策略选择,在有限时间内用动规(倒着递推),多次实验线性拟合基于时间的。对于非线性函数仅能取较近的输入值,用近似的切线做近似的线性处理。通过加强奖励函数,初始值(时间T)矩阵,等限定。得出结论,动作与状态的线性相关,且计算过程中可以省去无关迭代

LQR

kalman滤波

观测值转化为概率上的真实值

kalman

LQG

LQG=LQR+kalman滤波

微分动规DDP

根据当前决策选定轨迹,做LQR,更新决策,重复。从函数上理解是函数逐步靠近,即使是一个不那么好的模拟器

DDP

pegasus策略搜索

处理非线性模型函数的情况。选取随机序列并重复使用于模型训练,在模型选择时选取非线性的模型(如logistics 函数),用极大似然去找该模型下的最优策略。

policy

课程相关链接

网易Stanford机器学习

官方网站

Github整理

部分课件

练习

笔记和练习

说明

该课程能学到什么?

基于统计模型,概率公式的不同学习方法,不涉及神经网络。

本文档不按照课的分化记录,而按照内容的分化记录。

省去具体实现,毕竟已经有足够的他人整理的资料,以及我也在pad上手写过了,没有这个重复工作的必要了。不过我会尽量把知识点依赖写清,如果心情不错的话,再提供一个相关的优秀的讲解链接。这里还是以框架和思想为主。其中部分公式省略了取值限制,具体原公式请参照讲义

依赖知识

  • 高等数学

    • 积分
    • 微分
    • 偏导数,梯度
    • 拉格朗日乘子法、KKT条件(拉格朗日对偶)
    • 牛顿切线
  • 线性代数

    • 矩阵 乘法 转置 逆 秩 迹
    • SVD
  • 概率统计

    • 贝叶斯
    • 高斯分布 多纬高斯分布
    • 0-1分布 泊松分布 等等常见分布
    • 极大似然估计
  • 算法

    • 动态规划

内容

监督学习

  • 提供样例(输入,输出(类别))数据进行训练,从而学习出一个分类器/学成一个带有功能的程序。

方法

名称 功能 对应的期望 实现 理解 知识依赖 课程集数
线性回归 拟合出一条自变量与因变量的关系直线 平方和最小 梯度下降 局部极值 高数梯度 2
Normal equations 极值点梯度=0 矩阵运算 对矩阵求梯度 2
线性回归(数据量大) 随机梯度下降 2
logistics分类 对一侧全0,另一侧全1的训练数据集建立分类器 计算对应最优参数的logistics function 梯度上升 概率期望最大 极大似然 3
softmax回归(logistics 可以看做k=2时的特例) 分为k类 找到对应的拟合函数 并且拟合出参数 找拟合函数带入GLM 拟合参数老方法了 满足GLM的假设所以可以带入GLM GLM 和 极大似然 4
高斯判别分析GDA 在n维多项高斯分布中找一条线分割训练集合 计算假设分布中对应参数 假设伯努利以及高斯分布运用极大似然算参数值 根据测试集带入模型得出的可能性的值来判断所属于的分类 n维高斯的分布矩阵形式的协方差 极大似然 贝叶斯 生成学习方法 5
朴素贝叶斯 直接概率公式计算可能性,如垃圾邮件根据词的出现进行分类 能够对含有大量特征与非特征的整体进行分类 直接算概率 通过可能性来判断是否是 贝叶斯 极大似然 生成学习方法 5
神经网络(只是提到没有深入讲) 非线性分类器 对非线性进行分类 极大似然,反向传播,梯度下降 6
最优边界分类器 也就是要找到最大几何间隔的分类器 期望在满足约束的情况下,表达式最大 在限定条件下的线性规划用对偶的方式求极值 期望在满足约束的情况下,使所有点到超平面距离的最小值最大 拉格朗日乘子法、KKT条件、拉格朗日对偶性 7
支持向量机SVM 对非线性可分割的分布进行分割 非线性分类器 说实话SVM以及SVM所依赖的知识的细节理解得还不够清晰 已经放入下面的TODO 把训练集转换到高维去,在高纬度中进行线性分割 核函数+最优边界分类器+对偶问题 6 7 8
L1 norm soft margin SVM 对于有错误分类 让带有惩罚项的表达式期望最小 数学推导 个别的错误项可以破坏支撑点 但仅仅算做惩罚 svm 极大似然 8
特征选择 例如文本分类 选取真正对文本类别有影响的特征 通过交叉验证 逐个插入检测对预测值的影响 运用实验获得模糊的相关性 贝叶斯 10

辅助方法/定义

方法 功能 章节
牛顿切线法 加快求极值点的收敛速度、二次收敛 4
GLM广义线性模型 对满足广义线性模型下的回归问题进行分析 直接得到所需要的拟合模型 4
拉普拉斯平滑 对于 处理例如朴素贝叶斯中可能出现的0/0的概率 分子+1 分母+可分的类别数k 5
函数间隔 定义,当超平面的参数等比例缩放时超平面不会变但函数间隔会变化 6 7
几何间隔 同上,区别是 几何间隔=函数间隔/超平面法向量w的模,也就是当(w,b)等比例变化时,几何间隔不变 表示到超平面的距离 6 7
核函数 在最右边界分类器中出现了<X,X> 核函数能够降低计算代价,转换特征,同时选取高纬映射的核函数就够转化为SVM问题 7 8
SMO 每次修正一个参数逐步逼近,如果有等式约束每次修正两个 8
VC维 配合训练集 概率 错误率建立 误差上下界关系 10
交叉验证等多种验证 整体思想是把训练集的部分数据不用于训练而用于检验 进行模型筛选 10
规范化 正则化 防止过拟合 11
在线学习 边学习边预测 能收敛到分界线 11
检测机器学习算法的问题 通过 误差 方差 偏差 收敛性 值函数在更优秀的数据下的表现 综合判断问题所在 11

证明

证明 方法 课程集数
线性回归中用最小二乘法最好 概率分布假设+极大似然 3
线性回归 和 logistics分类对应的 拟合函数 按照GLM的方式对应带入 4
过拟合 欠拟合 经验风险的上下界 hoeffding不等式 同时能够提供定量分析误差 9 10

无监督学习

  • 无样例(输入,输出)样本进行学习,直接根据未分类数据的分布特征,对数据进行分类/建立带有功能的程序。

方法

名称 功能 对应的期望 实现 理解 知识依赖 课程集数
k-means 对无标记的点进行分类 找到分类的中心,使(按照每个分类的中心所得到的分类)的中心都是分类的中心 平面随机选中心,以这些点进行分类,对每个分好的类进行重新计算中心,重复直到收敛 分类的中心=分类的中心 在概率上可能最大 不等式递推 12
最大期望算法EM 对无标记训练数据根据所假设的模型进行分类 在所对应假设的模型下找到最好的参数 通过假设隐藏已知中间变量计算中间变量的 E假设 概率可以看做优化Q,M步在这种情况下的分布带回去看作优化参数,再E再M直到收敛,可以说k-means也有点思路类似,也可以通过图形理解 坐标上升 贝叶斯 极大似然 12 13
因子分析模型 因子多 分类少,直接对角矩阵会丢失其它维度,用来进行分类 对无标示样本分类 加入低维隐含变量Z 猜测分布 进行升高维度加上高斯噪声再用EM 高维数据被分到低纬度的类中 可以反过来进行 映射+噪声的假设 EM 高斯 矩阵乘法 13 14
PCA主成分分析 降维投影提取真正关键的维度信息 例如LSI潜在语义索引 找多组让数据尽量分散的正交投影法向量 线性代数 matlab的SVD进行奇异值分解 找低前k个最大的向量 SVD 线性代数 14 15
ICA独立成分分析 提取分离独立成分 如音频分离 需要分布本身独立 且非高斯 数学推导 高位的平行四边形 找到轴 并把轴转换到正交轴 独立概率+sigmoid+极大似然 15

辅助方法/定义

方法 功能 课程集数
混合高斯分布GMM 无标记的空间上有多组服从独立的高斯分布的数据,用于作为EM算法的试例 12
Jensen不等式 如果f是凸函数 E[f(x)]>=f(EX) 12

强化学习RL/马尔科夫模型MDP

上面的都是分类模型,强化学习要强行分类也可以说是无监督分类,或者半监督分类。

解释

名称 功能 对应的期望 实现 理解 知识依赖 课程集数
马尔科夫决策过程MDP(状态S,动作A,{概率P(S,A)},折扣因子r,{奖励函数R(S)}) 对于给定的模型 能够根据当前状态做出模型下最优决策 如讲师所做的自动驾驶倒飞直升机XD E(R(S0)+rR(S1)+r^2 *R(S2)+...) 值迭代/策略迭代 值迭代是计算每个S下的期望最大值,再根据得到的期望得到决策;决策迭代是在过程中 决策和值共同更新,计算代价更大 小数据相对快 动态规划 期望 16

马尔科夫决策过程 变形/问题

? ! 课程集数
未知概率函数P 用实验所得尽心均值估计 16
对于维度高数量级大的 离散化难解决(数量级大 需要非均匀离散) 状态>=动作 通过建造模拟器S(t+1)=S(t)+S'(t)*dt和实验数据进行线性拟合 从而取代P的作用,同时 需要多次多步骤实验 17
R(S)变为R(S,A) 依然用期望就好 18
从无限边界变为有限边界T,去掉折扣因子r,R和决策都还需要依赖时间 线性二次调节控制LQR 通过强假设 结束的S(T) A(T),进行反向递推(动规),根据矩阵运算可以省去 噪音项 18 19
非线性函数 采用局部的 值点 19
模拟器不够好+非线性函数 局部切线值的点+LRQ ->变为 微分动态规划DDP 19
无法获得真实值,只能获得观测值 Kalman滤波,非指数级别增长 通过概率估计推得可能的真实值/极大似然 19
无法真实值的线性二次调节控制 LQG=LQR+kalman滤波 19
LQG虽然能用但效果不是最佳,目标策略非线性策略 策略搜索:随机序列+带参数策略如logistics函数+梯度上升,找所假设的策略函数的最优参数 20
随机的序列 同样输入可能不同的值 拟合较难 Pegasus 序列依然随机生成,但生成后固定反复使用于训练 20

Debug 19

症状 方案
模拟器可以 真实不行 改模拟器
人比RL好 但值函数人的低 改值函数
没有上述问题 改奖励函数

一句话能回答的知识点 TODO

梯度下降和梯度上升有什么区别?3

画三维图形,求它的梯度,从图像上看,梯度是指向上方的。所以梯度下降用减法,实际是逼近极小值,而梯度上升用加法,逼近极大值。

欠拟合与过拟合分别是什么?4

欠拟合:模型复杂度不够 训练数据少,未能真正获取主要内在关系,训练数据和测试数据得分都低。过拟合,模型过于复杂,训练数据方差小得分高,但是测试数据偏差大 方差大

判别学习法 和 生成学习方法 的区别?5

判别学习法直接对P(y|x)进行建模;生成学习方法对P(x|y) 和 p(y)进行建模 然后,y = 贝叶斯公式转换后最大的y 例如 隐马尔可夫模型HMM、朴素贝叶斯模型、高斯混合模型GMM、LDA。

GDA与Logistics模型比较?5

GDA按照概率转化的曲线和sigmoid,很相似。GDA更强的假设,对于正确的训练集,GDA效率更高 更好,但对于不正确的模型,logistic的健壮性robust更好

其它知识点 细节 TODO

LGM

对偶问题

KKT条件

核函数定义 证明 应用 具体核(高斯核)

SVM

EM

EM:Do, C. B., & Batzoglou, S. (2008). What is the expectation maximization algorithm?. Nature biotechnology, 26(8), 897.

其它TODO

添加配图

李航的《统计学习方法》刚翻了个开头,这本书先讲的泛的再具体的顺序不同,不过瞄了目录,似乎有新内容还是换了个名字?不懂2333,目前计划是先不看它,去看CSP,之后再看_(:з」∠)_

老师建议把 证明过程盖住 自己再证明

2讲矩阵梯度公式

题目

题目大意

输入 k pa pb

最初空字符串,每次增加一个字符 a 或者 b, 当子串ab的个数>=k就停止,求停止时 子串ab的个数的期望。

子串的不需要连续 比如aab 是有两个ab子串

产生a的概率为 pa/(pa+pb), 产生b的概率为 pb/(pa+pb)

期望 计算过程中 不使用小数,而是用 逆模

所有值模1000 000 007

数据范围

1<=pa pb<=1 000 000

1<=k<=1 000

解答

官方题解

意思是用dp[i][j] 表示前缀 中 有i个a ,j个ab子串的子串个数的期望值

用的是从长的推短的 反过来,首先如果 j>=k 那么 dp[i][j] = j

递推表达式 dp[i][j] = (pa * dp[i+1][j] + pb * dp[i][i+j])/(pa+pb)

也就是 根据最后一个字符来进行分化

所以最后的结果就是dp[0][0] = dp[1][0]

其中一些问题

虽然j>=k已经解决了 但是 i还是可以无限大,根据递推表达式 和 级数 的办法 可以求得dp[k][j] here

不用小数用逆模运算 wiki的最后一条

实现

我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define ten5 100000+10
#define MOD 1000000007
#define rep(i,a,n) for (int i=a;i<n;i++)
#define iif(c,t,f) ((c)?(t):(f))
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair

int intcmp(const void *v1,const void *v2){return *(int *)v1-*(int *)v2;}
long long minn(long long v1,long long v2){return v1<v2?v1:v2;}
long long maxx(long long v1,long long v2){return v1>v2?v1:v2;}

//https://zh.wikipedia.org/wiki/%E6%89%A9%E5%B1%95%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97%E7%AE%97%E6%B3%95
//https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
// [a p] 1
// [1 0] ---> ret(x1)
// [0 1] x2
ll mul_inv(ll a,ll b=MOD ){
ll matrix[3][2]={{a,b},{1,0},{0,1}};
int itr = 0;
while(matrix[0][itr^1] != 1){
ll t = matrix[0][itr]/matrix[0][1^itr] ;
rep(i,0,3)
matrix[i][itr] -= t * matrix[i][1^itr];
itr^=1;
}
return (matrix[1][itr^1] % b + b )%b;
}
int k;
// http://codeforces.com/blog/entry/56713
ll dp[1010][1010] = {0};
ll getdp(int i,int j){
return j >= k? j : dp[i][j];
}
int main(){
ll a,b;
cin>>k>>a>>b;
//http://codeforces.com/blog/entry/56713?#comment-404349
rep(i,0,k)
dp[k][i] = (i + k + a * mul_inv(b)) % MOD;
int i,j;
for(i=k-1;i>=1;i--)
for(j=k-1;j>=0;j--){
dp[i][j] = (a*getdp(i+1,j) + b * getdp(i,i+j))%MOD;
dp[i][j] = (dp[i][j] * mul_inv(a+b))%MOD;
}
cout<<getdp(1,0)<<endl;
return 0;
}
0%