“华为杯”中国矿业大学程序设计学科竞赛

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 m1m2,以一个空格隔开, 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 1N100001m1300001m2100001Si2000000000

输出描述

每组输出共一行,为一个整数,表示从开始培养细胞到实验能够开始所经过的最少时间(单位为秒)。
如果无论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} m1m2sct

代码

#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 12...N。每堆上有若干个,但糖果总数必为 N N N的倍数。可以在任一堆上取若干个糖果,然后移动。
移动规则为:在编号为 1 1 1的堆上取的糖果,只能移到编号为 2 2 2的堆上;在编号为 N N N的堆上取的糖果,只能移到编号为 N − 1 N-1 N1的堆上;其他堆上取的糖果,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上糖果数都一样多。
例如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) N1<=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) (3n100)
接下来 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(1ai,bin,ai=bi,1ci100) — 代表 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 n1步时,我们就需要走到原点,然后更新代价的最小值。

代码

#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) 1LR10000

输出描述

每组输出数据共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) x0y02<=x0<1000002<=y0<=1000000,求出满足下列条件的 P , Q P,Q PQ的个数。
条件:
P , Q P,Q PQ是正整数;
②要求 P , Q P,Q PQ 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) y02<=x0<1000002<=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 1n100000,代表纪念品的个数;
第二行包含 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]1r[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) kpapb1k10001papb1000000

输出描述

输出一个整数。

示例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+jk时,增加一个’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 }} (1pa+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+jk)

接下来就是分情况进行记忆化搜索,得到 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} 2N1坨哦!嘤嘤嘤~~~~~~~
机智的你绝不会向粪皇低头!所以你拿起手中的笔记本,打开Codeblocks,写下#include<bits/stdc++.h>,开始计算如何掉最少的发,吃最少的shi,冲出粪坑,到达计院,拿下冠军!
输入描述:
第一行是一个整数T,代表测试数据个数。
对每个测试数据第一行是一个整数 N N N,代表粪坑大小为 N × N ( 1 ≤ N ≤ 100 ) N\times N (1 ≤ N ≤ 100) N×N(1N100)
接下来 N N N行每行 N N N个整数,代表粪坑矩阵A中每个粪坑位的粪量 ( 1 ≤ A i j ≤ 100 ) (1 ≤ A_{ij} ≤ 100) (1Aij100)

输出描述

最少吃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,j1)处向右走一步到达,与当前方向一致,那么前一个状态是 d p [ i ] [ j − 1 ] [ k ] [ 0 ] dp[i][j-1][k][0] dp[i][j1][k][0]
  • 位于 ( i − 1 , j ) (i-1,j) (i1,j)处向下走一步到达,与当前方向不一致,那么前一个状态是 d p [ i − 1 ] [ j ] [ k − 1 ] [ 1 ] dp[i-1][j][k-1][1] dp[i1][j][k1][1]

d p [ i ] [ j ] [ k ] [ 1 ] dp[i][j][k][1] dp[i][j][k][1]的来源有两种:

  • 位于 ( i − 1 , j ) (i-1,j) (i1,j)处向下走一步到达,与当前方向一致,那么前一个状态是 d p [ i − 1 ] [ j ] [ k ] [ 1 ] dp[i-1][j][k][1] dp[i1][j][k][1]
  • 位于 ( i , j − 1 ) (i,j-1) (i,j1)处向下走一步到达,与当前方向不一致,那么前一个状态是 d p [ i ] [ j − 1 ] [ k − 1 ] [ 0 ] dp[i][j-1][k-1][0] dp[i][j1][k1][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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用turtle库的penup()和pendown()方法来控制画笔的移动和停止,还可以使用goto()方法来移动画笔到指定的位置。以下是使用turtle库画出“中国矿业大学”的示例代码: ```python import turtle # 设置画笔 turtle.penup() turtle.goto(-200, 0) turtle.pendown() turtle.pensize(10) # 画“中” turtle.left(90) turtle.forward(200) turtle.right(90) turtle.forward(100) turtle.left(90) turtle.forward(200) # 画“国” turtle.penup() turtle.goto(-50, 0) turtle.pendown() turtle.left(90) turtle.forward(200) turtle.right(90) turtle.forward(100) turtle.right(90) turtle.forward(100) turtle.right(90) turtle.forward(100) # 画“矿” turtle.penup() turtle.goto(100, 100) turtle.pendown() turtle.left(90) turtle.forward(200) turtle.right(90) turtle.forward(50) turtle.right(90) turtle.forward(100) turtle.right(90) turtle.forward(50) turtle.right(90) turtle.forward(100) # 画“业” turtle.penup() turtle.goto(100, -100) turtle.pendown() turtle.left(90) turtle.forward(200) turtle.right(90) turtle.forward(100) turtle.right(90) turtle.forward(100) turtle.right(90) turtle.forward(100) # 画“大” turtle.penup() turtle.goto(250, 0) turtle.pendown() turtle.left(90) turtle.forward(200) turtle.right(90) turtle.forward(100) turtle.right(90) turtle.forward(200) turtle.right(90) turtle.forward(100) # 画“学” turtle.penup() turtle.goto(400, 0) turtle.pendown() turtle.left(90) turtle.forward(200) turtle.right(90) turtle.forward(100) turtle.right(90) turtle.forward(100) turtle.right(90) turtle.forward(100) ``` 执行上述代码后,就可以在turtle窗口中看到绘制出来的“中国矿业大学”字样了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值