2023 hbcpc 补题
I Step
题目大意
求最小的
t
,使得对于
k
∈
[
1
,
n
]
,
E
i
t
i
=
1..
i
为
a
k
的倍数。
求最小的 t ,使得对于 k ∈ [1, n] , E_i^t i=1.. i为 a_k的倍数。
求最小的t,使得对于k∈[1,n],Eiti=1..i为ak的倍数。
题解
设t = lcm(p1,p2…pn),那么所求即可转换为:
1+2+3+…+m = m(m+1)*0.5
求最小的m,使得满足:
m(m+1) = 2t
不妨,设2t = ab; (a|m,b|(m+1)); 且ax=m,by=m+1;
那么有,a(-x)+by = 1; 可以用exgcd来求出来最小的正整数x
关键在于对于a的枚举,可以对t进行质因数分解,枚举每个质因子是属于m还是属于m+1
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <iostream>
#include <cstring>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
#define ll long long
const int N = 1e6 + 10;
int n;
int p[N];
unordered_map<LL, LL> mp;
vector<int> v;
void fuc(LL n) {
for (LL i = 2; i <= n / i; ++ i) {
if (n % i == 0) {
mp[i] = 1;
while (n && n % i == 0) {
mp[i] *= i;
n /= i;
}
v.push_back(i);
}
}
if (n > 1) v.push_back(n), mp[n] = n;
}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
{
x = 1,y = 0;
return a;
}
ll d = exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
ll lcm(ll x,ll y)
{
return 1ll*x/__gcd(x,y)*y;
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin >> p[i];
ll t = lcm(p[1],1);
for(int i=1;i<=n;i++) t = lcm(t,p[i]);
fuc(1ll*t*2);
ll res = 0x3f3f3f3f3f3f3f3f;
//二进制枚举
for(ll i=0;i<(1<<v.size());i++)
{
ll a = 1;
for(ll j = 0;j<1ll*v.size();j++)
{
if(i>>j&1) a*=mp[v[j]];//判断第j位是1还是0
}
ll b = 2*t/a;
if(__gcd(a,b)!=1) continue;
ll x,y;
ll d = exgcd(a,b,x,y);
x = -x;
x = (x%b/d+b/d)%(b/d);//求x的最小整数解
if(1ll*a*x) res = min(res,a*x);
}
cout <<res;
return 0;
}
J Expansion
题目大意
给定长度为
n
的序列
a
n
。初始时没有资源,且已经占据第一个点,从
第一个点开始依次向右占领。假设已经占领到第
x
个点,则每秒资源增加
a
1
+
a
2
+
.
.
+
a
x
。
问保证在无穷时间内资源数量非负的情况下,最快何时能占领完这
n
个点。
给定长度为 n 的序列 {an}。初始时没有资源,且已经占据第一个点,从\\ 第一个点开始依次向右占领。假设已经占领到第x个点,则每秒资源增加\\ a_1+a_2+..+a_x。\\问保证在无穷时间内资源数量非负的情况下,最快何时能占领 完这 n 个点。
给定长度为n的序列an。初始时没有资源,且已经占据第一个点,从第一个点开始依次向右占领。假设已经占领到第x个点,则每秒资源增加a1+a2+..+ax。问保证在无穷时间内资源数量非负的情况下,最快何时能占领完这n个点。
题解
先分析什么情况下解不存在
注:pre[i] 表示a[i]的前缀和 mx[i]表示1~i中pre[i]的最大值
由题意,必须保证每时每刻的资源数必须为非负数
1.那么如果pre[n]<0,则表示资源数会变为负数,无解
2.当到一个点i时资源数为负数且mx[i]<=0时,无解
解存在时,当到一个点资源数变为负数时,为了最少时间,需要在前i个点中pre[i]最大的那个点中,即mx[i],多停留u秒
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <set>
#include<iomanip>
using namespace std;
typedef pair<int,int> PII;
const int N = 1e5+10,mod = 998244353;
#define ll long long
ll n,m;
ll pre[N]={0},mx[N]={0};
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
ll x;
cin>>x;
pre[i] = pre[i-1]+x;
mx[i] = max(mx[i-1],pre[i]);
}
if(pre[n]<0) {cout<<-1<<endl; return 0;}
ll add = 0,ans = 0;
for(int i=1;i<=n;i++)
{
ans++;
add = add + pre[i];
if(add<0)
{
if(mx[i]<=0) {cout<<-1<<endl; return 0;}
ll u = (-1*add+mx[i]-1)/mx[i];
//求最小的k,使得k*mx[i]>=abs(add);
add = add + u * mx[i];
ans = ans + u;
}
}
cout << ans;
return 0;
}
K Dice Game
题目大意
n + 1 个人每个人各投一个 m 面的骰子,点数最小的人失败。如果有多个 最小值,则继续投直到出现失败者。问一号的骰子一直投出 x,其余人随 机使得一号失败的概率,对 1 ⩽ x ⩽ m 输出答案。
题解
本题主要在于分析出[2,n]号人对于游戏的贡献是相同且独立的,所以只需要考虑一个人的情况即可
举例:
如果
2
号第一次丢骰子投出大于
x
,则
1
号直接输,
P
r
[
1
]
=
(
m
−
x
)
/
m
对于
2
号第
i
次丢骰子,前
i
−
1
次都等于
x
,第
i
次大于
x
,则
1
号输。
有概率
P
r
[
i
]
=
(
m
−
x
)
/
m
i
。
因而
1
号输的概率为
(
m
−
x
)
/
(
m
−
1
)
用等比数列计算
+
求极限
则最后的结果为
(
(
m
−
x
)
/
(
m
−
1
)
)
n
如果 2 号第一次丢骰子投出大于 x,则 1 号直接输,Pr[1] = (m−x)/m \\ 对于 2 号第 i 次丢骰子,前 i − 1 次都等于 x,第 i 次大于 x,则 1 号输。\\ 有概率 Pr[i] = (m−x)/m^i。\\ 因而 1 号输的概率为 (m-x)/(m-1) {用等比数列计算+求极限}\\ 则最后的结果为 ((m-x)/(m-1))^n
如果2号第一次丢骰子投出大于x,则1号直接输,Pr[1]=(m−x)/m对于2号第i次丢骰子,前i−1次都等于x,第i次大于x,则1号输。有概率Pr[i]=(m−x)/mi。因而1号输的概率为(m−x)/(m−1)用等比数列计算+求极限则最后的结果为((m−x)/(m−1))n
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <set>
#include<iomanip>
using namespace std;
typedef pair<int,int> PII;
const int N = 1e6+10,mod = 998244353;
#define ll long long
ll qmi(ll a,ll b)
{
ll res = 1;
while(b)
{
if(b%2) res = res*a%mod;
a = a*a%mod;
b>>=1;
}
return res;
}
ll inv(ll x)
{
return qmi(x,mod-2);
}
ll n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
ll ans = qmi((m-i)*inv(m-1)%mod,n);
cout<<ans<<" ";
}
return 0;
}