A-细胞分裂
题目描述
CB不光是ACM大佬,同时也是生物领域的知名专家。现在,他正在为一个细胞实验做准备工作:培养细胞样本。
CB博士手里现在有N种细胞,编号从1~N,一个第i种细胞经过1秒钟可以分裂为Si个同种细胞(Si为正整数)。现在他需要选取某种细胞的一个放进培养皿,让其自由分裂,进行培养。一段时间以后,再把培养皿中的所有细胞平均分入
M
M
M个试管,形成
M
M
M份样本,用于实验。CB博士的试管数
M
M
M很大,普通的计算机的基本数据类型无法存储这样大的
M
M
M值,但万幸的是,
M
M
M总可以表示为
m
1
m_1
m1的
m
2
m_2
m2次方,即
M
=
m
1
m
2
M=m_1^{m_2}
M=m1m2 ,其中
m
1
,
m
2
m_1,m_2
m1,m2 均为基本数据类型可以存储的正整数。
注意,整个实验过程中不允许分割单个细胞,比如某个时刻若培养皿中有4个细胞,Hanks博士可以把它们分入2个试管,每试管内2个,然后开始实验。但如果培养皿中有5个细胞,博士就无法将它们均分入2个试管。此时,博士就只能等待一段时间,让细胞们继续分裂,使得其个数可以均分,或是干脆改换另一种细胞培养。
为了能让实验尽早开始,CB博士在选定一种细胞开始培养后,总是在得到的细胞“刚好可以平均分入M个试管”时停止细胞培养并开始实验。现在博士希望知道,选择哪种细胞培养,可以使得实验的开始时间最早。
输入描述
每组输入数据共有三行。
第一行有一个正整数
N
N
N,代表细胞种数。
第二行有两个正整数
m
1
,
m
2
m_1,m_2
m1,m2,以一个空格隔开,
m
1
m
2
{m_1}^{m_2}
m1m2即表示试管的总数
M
M
M。
第三行有N个正整数,第i个数Si表示第i种细胞经过1秒钟可以分裂成同种细胞的个数。
对于所有的数据,有
1
≤
N
≤
10000
,
1
≤
m
1
≤
30000
,
1
≤
m
2
≤
10000
,
1
≤
S
i
≤
2000000000
1≤N≤10000,1≤m_1≤30000,1≤m_2≤10000,1≤S_i≤2000000000
1≤N≤10000,1≤m1≤30000,1≤m2≤10000,1≤Si≤2000000000。
输出描述
每组输出共一行,为一个整数,表示从开始培养细胞到实验能够开始所经过的最少时间(单位为秒)。
如果无论CB博士选择哪种细胞都不能满足要求,则输出整数-1。
示例1
输入
2
24 1
30 12
输出
2
说明
下面是对样例数据的解释:
第1种细胞最早在3秒后才能均分入24个试管,而第2种最早在2秒后就可以均分(每试管
144
24
=
6
\frac{144}{24}=6
24144=6 个)。故实验最早可以在2秒后开始。
思路
此题需要一些数论知识。
假设某一种细胞的个数为
s
s
s,存在正整数
c
t
ct
ct使得
s
c
t
m
o
d
m
1
m
2
=
0
s^{ct}\bmod {{m_1}^{m_2}}=0
sctmodm1m2=0。我们将
s
s
s和
m
1
m_1
m1分解质因数,就如样例中的第二种情况:
(
2
×
2
×
3
)
2
m
o
d
(
2
×
2
×
2
×
3
)
1
=
0
{(2\times 2\times 3)}^2\bmod {(2\times 2 \times 2 \times 3)}^1=0
(2×2×3)2mod(2×2×2×3)1=0。
m
1
m_1
m1和
s
s
s的质因子种类必须完全相同,否则不可能均分;且
s
c
t
s^ct
sct每一种质因子的个数必须不小于
m
1
m
2
{m_1}^{m_2}
m1m2中对应质因子的个数;满足上述条件的
c
t
ct
ct使得
m
1
m
2
∣
s
c
t
{m_1}^{m_2}|s^{ct}
m1m2∣sct。
代码
#include<cstring>
#include<algorithm>
#include<iostream>
#define inf 0x3f3f3f3f
using namespace std;
const int N=300006;
int prime_factor[N],num[N],s[N>>1];
int cnt;
int n,m1,m2;
int ans;
void init()
{
memset(num,0,sizeof(num));
cnt=0;
ans=inf;
}
void divide(int x)//分解质因数
{
int i;
for(i=2;i*i<=x;i++)
{
if(!(x%i))
{
prime_factor[++cnt]=i;
while(!(x%i))
{
x=x/i;
num[i]++;
}
num[i]*=m2;
}
}
if(x!=1)
{
prime_factor[++cnt]=x;
num[x]=m2;
}
}
int solve()
{
init();
cin>>m1>>m2;
divide(m1);
int i;
for(i=1;i<=n;i++) cin>>s[i];
int step=1;
next:for(i=step;i<=n;i++)//防止goto死循环,整活step。
{
int j;
int res=0;
for(j=1;j<=cnt;j++)
{
if(s[i]%prime_factor[j])
{
step=++i;
goto next;
}
else
{
int temp=s[i];
int ct=0;
while(!(temp%prime_factor[j]))//统计s[i]质因子prime_factor[j]的个数
{
temp=temp/prime_factor[j];
ct++;
}
/*
对质因子prime_factor[j]取模,至少需要的天数为
num[prime_factor[j]]/ct+(num[prime_factor[j]]%ct!=0)
*/
res=max(num[prime_factor[j]]/ct+(num[prime_factor[j]]%ct!=0),res);
}
}
ans=min(res,ans);//更新答案
}
if(ans==inf) ans=-1;//没有更新过
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin>>n) cout<<solve()<<endl;
return 0;
}
B-A题
题目描述
A要去B的城市游玩,A在城市1居住,B在城市X居住,现在有一些神奇的传送门和一些奇神的传送门。
已知,神奇的传送门可以从编号小的城市传送往编号大的城市,奇神的传送门可以从编号大的城市传送往编号小的城市,但是在某些城市没有某种传送门。
那么已知数组
a
a
a,其中
a
i
a_i
ai代表着城市i是否有神奇的传送门(
a
i
=
1
a_i=1
ai=1代表有,
a
i
=
0
a_i=0
ai=0代表没有),以及数组
b
b
b,其中
b
i
b_i
bi代表着城市i是否有奇神的传送门。
神奇的海螺想知道A能不能去B的城市玩。
输入描述
多组测试样例。
每组测试样例的第一行有两个数字N和X,代表了城市的数量和B的居住地址
(
2
<
=
N
<
=
1000
,
2
<
=
X
<
=
1000
)
(2 <= N <= 1000 ,2 <= X <= 1000)
(2<=N<=1000,2<=X<=1000)
第二行给出数组
a
a
a,第三行给出数组
b
b
b。
输出描述
可行,则输出YES。
否则,输出NO。
示例1
输入
5 3
1 1 1 1 1
1 1 1 1 1
5 4
1 0 0 0 1
0 1 1 1 1
5 2
0 1 1 1 1
1 1 1 1 1
输出
YES
YES
NO
说明
第二组样例路线:1->5->4
思路1
由题意,X比1大,所以a[1]必须为1,否则不能到达。如果X有神奇传送门,那么就能从1直接到达,如果没有,就要找一个比X还大并且两个门都有的城市作为中转站。
代码1
#include<string>
#include<iostream>
using namespace std;
string solve(int n,int x)
{
const int N=1002;
bool a[N],b[N];
int i;
for(i=1;i<=n;i++) cin>>a[i];
for(i=1;i<=n;i++) cin>>b[i];
string ans;
if(!a[1]) return "NO";//不能到编号大的城市
else
{
if(a[x]) return "YES";//有神奇传送门,直接过去
else if(!b[x]) return "NO";//根本没有通道
else //有奇神传送门,需要中转。
{
for(i=x+1;i<=n;i++)
{
if(a[i]&&b[i])
{
return "YES";
}
}
return "NO";
}
}
}
int main()
{
int n,x;
while(cin>>n>>x) cout<<solve(n,x)<<endl;
return 0;
}
思路2
建图,DFS一遍看是否连通。
代码2
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1002;
bool vis[N],can[N];
vector<int>mp[N];
bool ok;
int x,n;
int a[N],b[N];
void init()
{
vector<int>e[N];
swap(e,mp);
ok=false;
memset(vis,0,sizeof(vis));
memset(can,true,sizeof(can));
}
void dfs(int now,int x)
{
if(now==x)
{
ok=true;
return;
}
int i;
for(i=0;i<mp[now].size();i++)
{
if(ok) return;
if(!vis[mp[now][i]]&&can[mp[now][i]])
{
vis[mp[now][i]]=true;
dfs(mp[now][i],x);
vis[mp[now][i]]=false;
}
}
can[now]=false;//用来剪枝,如果循环结束ok为false,说明now不能到达终点。
}
void solve()
{
int i,j;
for(i=1;i<=n;i++) cin>>a[i];
for(i=1;i<=n;i++) cin>>b[i];
for(i=1;i<=n;i++)
{
if(a[i])
{
for(j=i+1;j<=n;j++)
{
if(a[j]) mp[i].push_back(j);
}
}
if(b[i])
{
for(j=1;j<=i-1;j++)
{
if(b[j]) mp[i].push_back(j);
}
}
}
dfs(1,x);
if(ok) puts("YES");
else puts("NO");
}
int main()
{
while(cin>>n>>x)
{
init();
solve();
}
return 0;
}
思路3
建图,BFS一遍看是否连通。复杂度较DFS更低。
代码3
#include<iostream>
#include<cstring>
#include<queue>
#include<cstdio>
#include<vector>
using namespace std;
const int N=1002;
bool vis[N];
vector<int>mp[N];
bool ok;
int x,n;
int a[N],b[N];
void init()
{
vector<int>e[N];
swap(e,mp);
ok=false;
memset(vis,0,sizeof(vis));
}
void bfs(int x)
{
queue<int>q;
q.push(1);
int i;
while(!q.empty())
{
int now=q.front();
for(i=0;i<mp[now].size();i++)//所有相连节点入队
{
if(vis[mp[now][i]]) continue;
else
{
if(mp[now][i]==x)
{
ok=1;
return;
}
else
{
q.push(mp[now][i]);
vis[mp[now][i]]=1;
}
}
}
q.pop();//根节点出队
}
}
void solve()
{
int i,j;
for(i=1;i<=n;i++) cin>>a[i];
for(i=1;i<=n;i++) cin>>b[i];
for(i=1;i<=n;i++)
{
if(a[i])
{
for(j=i+1;j<=n;j++)
{
if(a[j]) mp[i].push_back(j);
}
}
if(b[i])
{
for(j=1;j<=i-1;j++)
{
if(b[j]) mp[i].push_back(j);
}
}
}
bfs(x);
if(ok) puts("YES");
else puts("NO");
}
int main()
{
while(cin>>n>>x)
{
init();
solve();
}
return 0;
}
C-均分糖果
题目描述
有
N
N
N堆糖果,编号分别为
1
,
2
,
.
.
.
,
N
1,2,...,N
1,2,...,N。每堆上有若干个,但糖果总数必为
N
N
N的倍数。可以在任一堆上取若干个糖果,然后移动。
移动规则为:在编号为
1
1
1的堆上取的糖果,只能移到编号为
2
2
2的堆上;在编号为
N
N
N的堆上取的糖果,只能移到编号为
N
−
1
N-1
N−1的堆上;其他堆上取的糖果,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上糖果数都一样多。
例如N=4,4堆糖果数分别为:
① 9 ② 8 ③ 17 ④ 6
移动3次可达到目的:
从③取4个糖放到④(9 8 13 10)->从③取3个糖放到②(9 11 10 10)->从②取1个糖放到①(10 10 10 10)。
输入描述
每个测试文件包含多组测试数据,每组输入的第一行输入一个整数
N
(
1
<
=
N
<
=
100
)
N(1<=N<=100)
N(1<=N<=100),表示有
N
N
N堆糖果。
接下来一行输入
N
N
N个整数
A
1
A
2
.
.
.
A
n
A_1 A_2...A_n
A1A2...An,表示每堆糖果初始数,
1
<
=
A
i
<
=
10000
1<=A_i<=10000
1<=Ai<=10000。
输出描述
对于每组输入数据,输出所有堆均达到相等时的最少移动次数。
示例1
输入
4
9 8 17 6
输出
3
思路
相邻两堆
A
i
A_i
Ai和
A
i
+
1
A_{i+1}
Ai+1成对调整,若不足平均,则补;若高于平均,则扣。
这样能尽可能避免拆数量为平均数的堆,是最优解。
代码
#include<iostream>
using namespace std;
int solve(int n)
{
int i;
int a[102];
int sum=0,ans=0;
for(i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
int average=sum/n;
for(i=1;i<n;i++)
{
a[i+1]=a[i+1]+a[i]-average;//始终是这个式子
if(a[i]!=average) ans++;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
while(cin>>n) cout<<solve(n)<<endl;
return 0;
}
D-B题
题目描述
有一个连通图 包含
n
n
n 个点
n
n
n 条无向边 其中每个点都与其他的两个点直接相连 (即这是一个环)
现在这个环的边变成了有向边 变成了有向边后得到的有向图不一定是强连通的
(强连通图是指一个有向图中任意两点v1、v2间存在v1到v2的路径及v2到v1的路径的图)
所以现在给出 n 条有向边和把某条有向边转换方向后的代价, 问要使输入的有向图变成一个强连通图
例如输入
3
1 3 1
1 2 1
3 2 1
表示有一条有向边 1 -> 3 如果把这条边变成 3 -> 1 的代价是 1
表示有一条有向边 1 -> 2 如果把这条边变成 2 -> 1 的代价是 1
表示有一条有向边 3 -> 2 如果把这条边变成 2 -> 3 的代价是 1
对于输入的这个有向图是不存在 2 -> 3 的路径的 所以可以把 有向边 1 -> 2 变为 2 -> 1 这样图中任意两点均相互可达
输入描述
多组测试数据。
第一行给出数字
n
n
n,代表顶点数量
(
3
≤
n
≤
100
)
(3 ≤ n ≤ 100)
(3 ≤n≤ 100)。
接下来
n
n
n行给出路径。
每行给出三个数字
a
i
,
b
i
,
c
i
(
1
≤
a
i
,
b
i
≤
n
,
a
i
≠
b
i
,
1
≤
c
i
≤
100
)
a_i, b_i, c_i (1 ≤ a_i, b_i ≤ n, a_i ≠ b_i, 1 ≤ c_i ≤ 100)
ai,bi,ci(1 ≤ai,bi≤n,ai=bi, 1 ≤ci≤ 100) — 代表
a
i
a_i
ai 指向
b
i
b_i
bi。代价是
c
i
c_i
ci。
输出描述
输出最小代价。
示例1
输入
3
1 3 1
1 2 1
3 2 1
3
1 3 1
1 2 5
3 2 1
6
1 5 4
5 3 8
2 4 15
1 6 16
2 3 23
4 6 42
输出
1
2
39
思路
一个有向环要成为强连通图就需要从任意一点
p
p
p出发,能绕环一圈走回原点。
我们将节点
1
1
1设为出发点,从该节点开始搜索,若找到与节点
n
o
w
now
now连接且没有走过的节点,试情况是否要改变路径的方向。当走了
n
−
1
n-1
n−1步时,我们就需要走到原点,然后更新代价的最小值。
代码
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int n,mp[N][N];
bool vis[N];
int ans;
void dfs(int now,int step,int w)
{
vis[now]=1;
if(step==n-1)
{
if(mp[now][1]) ans=min(ans,w);
else if(mp[1][now]) ans=min(ans,w+mp[1][now]);
}
else
{
int i;
for(i=1;i<=n;i++)
{
if(mp[now][i]&&!vis[i])
{
dfs(i,step+1,w);
vis[i]=0;
}
else if(mp[i][now]&&!vis[i])
{
dfs(i,step+1,w+mp[i][now]);
vis[i]=0;
}
}
}
}
void init()
{
memset(mp,0,sizeof(mp));
memset(vis,0,sizeof(vis));
ans=0x3f3f3f3f;
}
void solve()
{
init();
int i;
for(i=1;i<=n;i++)
{
int u,v,w;
cin>>u>>v>>w;
mp[u][v]=w;
}
dfs(1,0,0);
cout<<ans<<endl;
}
int main()
{
while(cin>>n) solve();
return 0;
}
E-很简单的题。。。。。。
题目描述
统计某个给定范围 [ L , R ] [L, R] [L,R] 的所有整数中,数字 2 2 2出现的次数。比如给定范围 [ 2 , 22 ] [2, 22] [2,22] ,数字 2 2 2在数 2 2 2中出现了 1 1 1次,在数 12 12 12中出现 1 1 1次,在数 20 20 20中出现 1 1 1次,在数 21 21 21中出现 1 1 1次,在数 22 22 22中出现 2 2 2次,所以数字 2 2 2在该范围内一共出现了 6 6 6次。
输入描述
每组输入数据共 1 1 1行,为两个正整数 L L L和 R R R,之间用一个空格隔开。 ( 1 ≤ L ≤ R ≤ 10000 ) (1≤L≤R≤10000) (1≤L≤R≤10000)
输出描述
每组输出数据共1行,表示数字2出现的次数。
示例1
输入
2 100
输出
20
思路
给一个数
p
p
p,知道
p
p
p含有几个
2
2
2,非常容易,只需要对
10
10
10取模,拿出最低位的数,再除以
10
10
10抛弃最后一位的数,如此循环计数即可。
我们只要统计
i
i
i个数2出现的次数,然后做一次前缀和操作,就能在
O
(
1
)
O(1)
O(1)内查询
[
L
,
R
]
[L,R]
[L,R]区间2出现的次数。
代码
#include<iostream>
using namespace std;
const int N=10001;
int cnt[N];
void init()
{
int i;
for(i=1;i<N;i++)
{
int p=i;
while(p)
{
if(p%10==2) cnt[i]++;
p=p/10;
}
cnt[i]=cnt[i]+cnt[i-1];
}
}
void solve(int l,int r)
{
init();
cout<<cnt[r]-cnt[l-1];
}
int main()
{
int l,r;
while(cin>>l>>r) solve(l,r);
return 0;
}
F-最大公约数和最小公倍数问题
题目描述
输入2个正整数
x
0
,
y
0
(
2
<
=
x
0
<
100000
,
2
<
=
y
0
<
=
1000000
)
x_0,y_0(2<=x_0<100000,2<=y_0<=1000000)
x0,y0(2<=x0<100000,2<=y0<=1000000),求出满足下列条件的
P
,
Q
P,Q
P,Q的个数。
条件:
①
P
,
Q
P,Q
P,Q是正整数;
②要求
P
,
Q
P,Q
P,Q以
x
0
x_0
x0为最大公约数,以
y
0
y_0
y0为最小公倍数。
试求:
满足条件的所有可能的两个正整数的个数。
输入描述
每个测试文件包含不超过5组测试数据,每组两个正整数 x 0 x_0 x0和 y 0 ( 2 < = x 0 < 100000 , 2 < = y 0 < = 1000000 ) y_0(2<=x_0<100000,2<=y_0<=1000000) y0(2<=x0<100000,2<=y0<=1000000)。
输出描述
对于每组输入数据,输出满足条件的所有可能的两个正整数的个数。
下面是对样例数据的说明:
输入3 60
此时的P Q分别为:
3 60
15 12
12 15
60 3
所以,满足条件的所有可能的两个正整数的个数共4种。
示例1
3 60
4
思路
由于 g c d ( p , q ) = x 0 , l c m ( p , q ) = y 0 gcd(p,q)=x_0,lcm(p,q)=y_0 gcd(p,q)=x0,lcm(p,q)=y0,所以 x 0 y 0 = p q x_0y_0=pq x0y0=pq。我们的任务就是在 O ( x 0 y 0 ) O(\sqrt {x_0y_0}) O(x0y0)内查找所有 x 0 y 0 x_0y_0 x0y0的因子 i i i,并检验 g c d ( i , x 0 y 0 i ) gcd(i,\frac{x_0y_0}{i}) gcd(i,ix0y0)是否为 x 0 x_0 x0。
代码
#include<iostream>
#include<algorithm>
using namespace std;
void solve(long long x,long long y)
{
long long k=x*y;
long long i;
long long ans=0;
for(i=1;i*i<k;i++)
{
if(k%i==0)
{
long long g=__gcd(i,k/i);
if(g==x) ans++;
}
}
ans=ans*2;
if(i*i==k&&x==i) ans++;//完全平方数检验
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
long long x,y;
while(cin>>x>>y) solve(x,y);
return 0;
}
G-毕业生的纪念礼物
题目描述
现在有n个纪念品,每个纪念品都有一个种类r[i],现在要求对每个毕业生分发三个种类不同的纪念品,现在需要你来计算下共可以发给多少个毕业生?
输入描述
第一行一个整数
n
n
n,
1
≤
n
≤
100000
1≤n≤100000
1≤n≤100000,代表纪念品的个数;
第二行包含
n
n
n个整数,分别是
r
[
1
]
,
r
[
2
]
,
r
[
3
]
.
.
.
.
.
.
r
[
n
]
,
1
≤
r
[
i
]
≤
1
0
9
r[1], r[2], r[3]......r[n],1≤r[i]≤10^9
r[1],r[2],r[3]......r[n],1≤r[i]≤109,表示每个纪念品所属的种类。
输出描述
输出一个整数,代表最多能够分发给的毕业生人数。
示例1
输入
14
1 1 2 2 3 3 4 4 4 4 5 5 5 5
输出
4
示例2
输入
7
1 2 3 4 5 6 7
输出
2
思路
题目就是说有
n
n
n种物品,每一种有
r
[
i
]
r[i]
r[i]个,要求每次挑选三种,每种拿一个,问最多能挑选几次。
显然,我们每一次都要选剩余物品数量最多的那三种,这样可以挑选的次数是最多的。每挑选一次就要进行一次排序,因此考虑优先队列。
代码
#include<map>
#include<queue>
#include<iostream>
using namespace std;
void solve()
{
int r[100002];
priority_queue<int>now;//STL默认大顶堆
int i;
int n;
cin>>n;
map<int,int>cnt;
for(i=1;i<=n;i++)
{
cin>>r[i];
cnt[r[i]]++;//记录每一种物品的个数
}
map<int,int>::iterator it;
//遍历map,把每一种的个数push进队列。
for(it=cnt.begin();it!=cnt.end();it++) now.push(it->second);
int ans=0;
int a,b,c;
while(now.size()>=3)
{
//选取个数最多的三种
a=now.top();
now.pop();
b=now.top();
now.pop();
c=now.top();
now.pop();
a--;
b--;
c--;
if(a>0) now.push(a);
if(b>0) now.push(b);
if(c>0) now.push(c);
ans++;
}
cout<<ans;
}
int main()
{
solve();
return 0;
}
H-毕业生的序列游戏
题目描述
对于三个给定的正整数
k
,
p
a
,
p
b
k, p_a, p_b
k,pa,pb, 现在有一个序列构造算法: 在初始条件下,有一个空序列,之后每次你会在该序列的末尾添加一个字母’a’或’b’,添加’a’的概率是
p
a
p
a
+
p
b
\frac{p_a}{p_a+p_b}
pa+pbpa,添加’b’的概率是
p
b
p
a
+
p
b
\frac{p_b}{p_a+p_b}
pa+pbpb。当在该序列中有至少k个子序列为"ab"的时候,该构造算法结束。
现在,你需要求出该算法所构造出来的序列中"ab"子序列的期望个数为多少。显然,该结果可以用
P
Q
\frac {P}{Q}
QP来表示,其中
P
P
P和
Q
Q
Q互质,并且
Q
≠
0
Q≠0
Q=0,
P
P
P和
Q
Q
Q模数为
1
0
9
+
7
10^9+7
109+7。你需要打印出
P
Q
m
o
d
(
1
0
9
+
7
)
\frac {P}{Q}\bmod (10^9+7)
QPmod(109+7)。
注意,子序列是可以不连续的。
输入描述
第一行包含三个整数 k , p a , p b ( 1 ≤ k ≤ 1000 , 1 ≤ p a , p b ≤ 1000000 ) k,p_a,p_b(1≤k≤1000,1≤p_a,p_b≤1000000) k,pa,pb(1≤k≤1000,1≤pa,pb≤1000000)。
输出描述
输出一个整数。
示例1
输入
1 1 1
输出
2
思路
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为当前构造的序列有
i
i
i个’a’,
j
j
j个子序列"ab"时,出现子序列"ab"的次数的期望。
考虑比当前序列多一个字符的串,若多出的字符为’a’(即
d
p
[
i
+
1
]
[
j
]
dp[i+1][j]
dp[i+1][j]),则需要
p
a
p
a
+
p
b
\frac {p_a}{p_a+p_b}
pa+pbpa的概率到达
d
p
[
i
+
1
]
[
j
]
dp[i+1][j]
dp[i+1][j];若多出的字符为’b’(即
d
p
[
i
]
[
j
+
i
]
dp[i][j+i]
dp[i][j+i]),则需要
p
b
p
a
+
p
b
\frac {p_b}{p_a+p_b}
pa+pbpb的概率到达
d
p
[
i
]
[
j
+
i
]
dp[i][j+i]
dp[i][j+i]。
已有字符数量少的字符串理应拥有更大的可能性。
状态转移方程:
d
p
[
i
]
[
j
]
=
d
p
[
i
+
1
]
[
j
]
×
p
a
p
a
+
p
b
+
d
p
[
i
]
[
j
+
i
]
×
p
b
p
a
+
p
b
dp[i][j]=dp[i+1][j]\times \frac {p_a}{p_a+p_b}+dp[i][j+i]\times \frac {p_b}{p_a+p_b}
dp[i][j]=dp[i+1][j]×pa+pbpa+dp[i][j+i]×pa+pbpb
只需要通过记忆化搜索,递归至最底层,然后自底向上递推即可。
下面来考虑两种极限情况:
①出现无穷多’b’,即一开始的序列是"bbbbbbbbbbb…"这样的,就是不出现’a’,这样的序列是废掉的,显然在第一个’a’之前的所有’b’都没有任何用处,为了结束添加字母,则必定会出现a,所以目标状态一定会出现前缀有一个’a’,没有"ab"的情况,如果求
d
p
[
0
]
[
0
]
dp[0][0]
dp[0][0]必定超时,因此我们转而求
d
p
[
1
]
[
0
]
dp[1][0]
dp[1][0]。
②出现无穷多’a’,即一开始的序列是"ababaaaaaaaaaaaa…b"这样的,就是不出现’b’,这样的序列,当出现一个’b’,就能实现大于等于
k
k
k个"ab"子序列的构造。
事实上,只要达到状态
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]且
i
+
j
⩾
k
i + j \geqslant k
i+j⩾k时,增加一个’b’,就能完成大于等于
k
k
k个"ab"子序列的构造。那么即使后面一坨’a’也无妨。下面算的就是后面一坨‘a’的情况。
列一个表,表示添加$\xi $个’a’,再添加’b’实现条件的概率。
ξ = 0 \xi=0 ξ=0 | ξ = 1 \xi =1 ξ=1 | ξ = 2 \xi =2 ξ=2 | ξ = . . . \xi=... ξ=... | |
---|---|---|---|---|
P ( ξ ) P(\xi) P(ξ) | p b p a + p b \frac{{{p_b}}}{{{p_a} + {p_b}}} pa+pbpb | p a p a + p b p b p a + p b \frac{{{p_a}}}{{{p_a} + {p_b}}} \frac{{{p_b}}}{{{p_a} + {p_b}}} pa+pbpapa+pbpb | ( p a p a + p b ) 2 p b p a + p b {\left( {\frac{{{p_a}}}{{{p_a} + {p_b}}}} \right)^2}\frac{{{p_b}}}{{{p_a} + {p_b}}} (pa+pbpa)2pa+pbpb | P ( ξ ) = . . . P(\xi)=... P(ξ)=... |
"ab"子序列个数 | i + j i+j i+j | i + j + 1 i+j+1 i+j+1 | i + j + 2 i+j+2 i+j+2 | i + j + . . . i+j+... i+j+... |
由期望定义,得:
d p [ i ] [ j ] = p b p a + p b ∑ ξ = 0 ∞ ( i + j + ξ ) ( p a p a + p b ) ξ dp[i][j] = \frac{{{p_b}}}{{{p_a} + {p_b}}}{\sum\limits_{\xi = 0}^\infty {(i + j + \xi) (\frac{{{p_a}}}{{{p_a} + {p_b}}})} ^{\xi}} dp[i][j]=pa+pbpbξ=0∑∞(i+j+ξ)(pa+pbpa)ξ
上述无穷级数可用错位相减法求得极限
等式两边同时乘
p
a
p
a
+
p
b
\frac {p_a}{p_a+p_b}
pa+pbpa得:
p a p a + p b d p [ i ] [ j ] = p b p a + p b ∑ ξ = 0 ∞ ( i + j + ξ ) ( p a p a + p b ) ξ + 1 \frac{{{p_a}}}{{{p_a} + {p_b}}}dp[i][j] = \frac{{{p_b}}}{{{p_a} + {p_b}}}\sum\limits_{\xi = 0}^\infty {(i + j + \xi )} {\left( {\frac{{{p_a}}}{{{p_a} + {p_b}}}} \right)^{\xi + 1}} pa+pbpadp[i][j]=pa+pbpbξ=0∑∞(i+j+ξ)(pa+pbpa)ξ+1
错位相减得:
( 1 − p a p a + p b ) d p [ i ] [ j ] = ( i + j ) p b p a + p b + p b p a + p b ∑ ξ = 1 ∞ ( p a p a + p b ) ξ \left( {1 - \frac{{{p_a}}}{{{p_a} + {p_b}}}} \right)dp[i][j] = (i + j)\frac{{{p_b}}}{{{p_a} + {p_b}}} + \frac{{{p_b}}}{{{p_a} + {p_b}}}\sum\limits_{\xi = 1}^\infty {{{\left( {\frac{{{p_a}}}{{{p_a} + {p_b}}}} \right)}^\xi }} (1−pa+pbpa)dp[i][j]=(i+j)pa+pbpb+pa+pbpbξ=1∑∞(pa+pbpa)ξ
解得:
d p [ i ] [ j ] = i + j + p a p b ( i + j ⩾ k ) dp[i][j] = i + j + \frac{{{p_a}}}{{{p_b}}}(i + j \geqslant k) dp[i][j]=i+j+pbpa(i+j⩾k)
接下来就是分情况进行记忆化搜索,得到
d
p
[
1
]
[
0
]
dp[1][0]
dp[1][0]
需要注意的是所有概率运算都在模
1
0
9
+
7
10^9+7
109+7的意义下进行,即计算逆元。
代码
#include<iostream>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=1003;
ll k,pa,pb;
ll dp[N][N];
ll quick_pow(ll a,ll b)//快速幂
{
ll res=1;
while(b)
{
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
ll inv(ll x)//求逆元
{
return quick_pow(x,mod-2);
}
ll dfs(ll x,ll y)
{
if(dp[x][y]) return dp[x][y];
if(x+y>=k)
{
dp[x][y]=(x+y+pa*inv(pb)%mod)%mod;
return dp[x][y];
}
dp[x][y]=(pa*inv(pa+pb)%mod*dfs(x+1,y)%mod+pb*inv(pa+pb)%mod*dfs(x,y+x)%mod)%mod;
return dp[x][y];
}
void solve()
{
ios::sync_with_stdio(false);
cin>>k>>pa>>pb;
cout<<dfs(1,0);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
I-你的粪坑v1
题目描述
剪刀石头布,谁输谁吃屎。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5BP6GKux-1645100581613)(https://i.niupic.com/images/2020/02/29/6XaG.png)]
输入描述
第一行一个整数T,代表测试数据个数。
每个测试数据包括两行数据。
第一行数据为两个字符串A,X1,代表A同学的出拳为X1。
第二行数据为两个字符串B,X2,代表B同学的出拳为X2。
其中A,B是字符串,长度为[0,10],代表同学名字。
其中X1,X2是字符串,内容为"jiandao",“shitou”,“bu"三者之一。
输出描述:
对每个测试数据,
A同学赢输出"B chishi.”,其中B代表B同学名字,
B同学赢输出"A chishi.",其中A代表A同学名字,
如果平局,输出"yi qi chi shi."。
示例1
输入
复制
3
jiang jiandao
wang shitou
jiang bu
wang jiandao
jiang shitou
wang shitou
输出
复制
jiang chishi.
jiang chishi.
yi qi chi shi.
说明
注意空格
思路
模拟一遍剪刀石头布的规则,注意输出末尾的句号和句中的空格。
代码
#include<string>
#include<iostream>
using namespace std;
void solve()
{
string a,x1;
cin>>a>>x1;
string b,x2;
cin>>b>>x2;
if(x1=="jiandao")
{
if(x2=="shitou") cout<<a<<' '<<"chishi."<<endl;
else if(x2=="bu") cout<<b<<' '<<"chishi."<<endl;
else cout<<"yi qi chi shi."<<endl;
}
else if(x1=="shitou")
{
if(x2=="jiandao") cout<<b<<' '<<"chishi."<<endl;
else if(x2=="bu") cout<<a<<' '<<"chishi."<<endl;
else cout<<"yi qi chi shi."<<endl;
}
else if(x1=="bu")
{
if(x2=="jiandao") cout<<a<<' '<<"chishi."<<endl;
else if(x2=="shitou") cout<<b<<' '<<"chishi."<<endl;
else cout<<"yi qi chi shi."<<endl;
}
}
int main()
{
int t;
cin>>t;
while(t--) solve();
return 0;
}
J-你的粪坑v2
题目描述
一听说这是一场正经比赛,都没人用昵称了?好吧,那就用“你”吧,请自行代入聚聚名字!
这是一道充满味道的题目。
今天举办程序设计比赛,2点30分开始,然而你睡到了2点25分,紧张的你将头发梳成大人模样,敷上一层最贵的面膜,穿着滑板鞋,以飞一般的速度奔向计算机学院准备参加程序设计竞赛!冠军是你的!
然而路上稍不留神,你不小心掉进了一个大粪坑,大粪坑是一个
N
×
N
N \times N
N×N的方格矩阵,每个方格存在着
X
X
X坨粪,一开始你处在
A
11
A_{11}
A11的粪坑位,你可以选择向下移动或者向右移动,目标是逃离大粪坑到达
A
N
N
A_{NN}
ANN。
此外!!敲重点!!每经过一个粪坑,你会触及粪量
X
X
X(粗俗的说法叫做吃shi),而且每更改一次方向,传说中的粪皇会向你丢粪!!
粪皇是个学过二进制的优雅美男子,所以他丢粪也是相当的儒雅随和。第一次他会向你丢
1
1
1坨,第二次他会向你丢
2
2
2坨哦,第三次他会向你丢
4
4
4坨哦!第四次他会向你丢
8
8
8坨哦!第五次他会向你丢
16
16
16坨哦!…,第
N
N
N次他会向你丢
2
N
−
1
2^{N-1}
2N−1坨哦!嘤嘤嘤~~~~~~~
机智的你绝不会向粪皇低头!所以你拿起手中的笔记本,打开Codeblocks,写下#include<bits/stdc++.h>,开始计算如何掉最少的发,吃最少的shi,冲出粪坑,到达计院,拿下冠军!
输入描述:
第一行是一个整数T,代表测试数据个数。
对每个测试数据第一行是一个整数
N
N
N,代表粪坑大小为
N
×
N
(
1
≤
N
≤
100
)
N\times N (1 ≤ N ≤ 100)
N×N(1≤N≤100) 。
接下来
N
N
N行每行
N
N
N个整数,代表粪坑矩阵A中每个粪坑位的粪量
(
1
≤
A
i
j
≤
100
)
(1 ≤ A_{ij} ≤ 100)
(1≤Aij≤100)。
输出描述
最少吃shi量
示例1
输入
1
3
1 4 6
1 1 3
6 1 1
输出
10
思路
你当前的状态有五维:
- 横坐标
- 纵坐标
- 转弯的次数
- 行走的方向
- 吃屎量
记
d
p
[
i
]
[
j
]
[
k
]
[
v
]
=
s
h
i
dp[i][j][k][v]=shi
dp[i][j][k][v]=shi为处于
(
i
,
j
)
(i,j)
(i,j)位置,已经转弯了
k
k
k次,正在朝
v
v
v方向走,已经吃了shi坨屎的状态(
v
=
0
v=0
v=0代表向右,
v
=
1
v=1
v=1代表向左)。
d
p
[
i
]
[
j
]
[
k
]
[
0
]
dp[i][j][k][0]
dp[i][j][k][0]的来源有两种:
- 位于 ( i , j − 1 ) (i,j-1) (i,j−1)处向右走一步到达,与当前方向一致,那么前一个状态是 d p [ i ] [ j − 1 ] [ k ] [ 0 ] dp[i][j-1][k][0] dp[i][j−1][k][0]
- 位于 ( i − 1 , j ) (i-1,j) (i−1,j)处向下走一步到达,与当前方向不一致,那么前一个状态是 d p [ i − 1 ] [ j ] [ k − 1 ] [ 1 ] dp[i-1][j][k-1][1] dp[i−1][j][k−1][1]
d p [ i ] [ j ] [ k ] [ 1 ] dp[i][j][k][1] dp[i][j][k][1]的来源有两种:
- 位于 ( i − 1 , j ) (i-1,j) (i−1,j)处向下走一步到达,与当前方向一致,那么前一个状态是 d p [ i − 1 ] [ j ] [ k ] [ 1 ] dp[i-1][j][k][1] dp[i−1][j][k][1]
- 位于 ( i , j − 1 ) (i,j-1) (i,j−1)处向下走一步到达,与当前方向不一致,那么前一个状态是 d p [ i ] [ j − 1 ] [ k − 1 ] [ 0 ] dp[i][j-1][k-1][0] dp[i][j−1][k−1][0]
代码
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
void solve()
{
const int N=102;
int dp[N][N][12][2];
memset(dp,0x3f,sizeof(dp));
int n;
int a[N][N];
int i,j,k;
cin>>n;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
cin>>a[i][j];
}
}
dp[1][1][0][0]=dp[1][1][0][1]=a[1][1];
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
for(k=0;k<=min(n,10);k++)
{
dp[i][j][k][0]=min(dp[i][j][k][0],dp[i][j-1][k][0]+a[i][j]);
if(k)
dp[i][j][k][0]=min(dp[i][j][k][0],dp[i-1][j][k-1][1]+a[i][j]+(1<<(k-1)));
//加上转弯的贡献,第k次转弯吃2^(k-1)坨屎。
dp[i][j][k][1]=min(dp[i][j][k][1],dp[i-1][j][k][1]+a[i][j]);
if(k)
dp[i][j][k][1]=min(dp[i][j][k][1],dp[i][j-1][k-1][0]+a[i][j]+(1<<(k-1)));
//加上转弯的贡献,第k次转弯吃2^(k-1)坨屎。
}
}
}
int ans=0x3f3f3f3f;
for(k=0;k<=min(n,10);k++) ans=min({ans,dp[n][n][k][1],dp[n][n][k][0]});
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--) solve();
}
K-你的Alice
题目描述
今天你Bob和你的Alice进行一场比赛。
有N根棒,你和你的Alice轮流取棒,规定你们每人一次取K个,当不够K个的时候,你们把余下的扔掉并停止游戏。
你比较疼你的Alice,所以Alice先取,问最终Alice能否取得更多?
输入描述
单组测试数据。
包括2个整数N和K,代表有N根棒,以及Alice和Bob每次取K个棒。
1
<
=
N
<
=
100000000000
1<=N<=100000000000
1<=N<=100000000000
1
<
=
k
<
=
100
1<=k<=100
1<=k<=100
输出描述
如果Alice最终拥有更多的棒,输出YES,否则输出NO。(全大写)
示例1
输入
10 4
输出
NO
思路
两人交替拿棒子,一组记为 2 k 2k 2k。如果一直交替着拿,把棒子拿光,那就必定平局了。然而这样交替着拿最多进行 n 2 k \frac{n}{2k} 2kn次,然后会剩下 l e f t = n m o d 2 k ( 0 < l e f t < 2 k ) left=n\bmod 2k(0<left<2k) left=nmod2k(0<left<2k)。如果剩下的棒子还有 k k k个,那么Alice拿了就赢了,否则就是个平局。
代码
#include<iostream>
using namespace std;
void solve()
{
long long n,k;
cin>>n>>k;
if(n%(2*k)>=k) cout<<"YES";
else cout<<"NO";
}
int main()
{
solve();
return 0;
}