A组T1 锻造 (forging)
1.1 题目背景
勇者虽然武力值很高,但在经历了多次战斗后,发现怪物越来越难打
于是开始思考是不是自己平时锻炼没到位,于是苦练一个月后发现......自己连一个史莱姆都打不过了。
勇者的精灵路由器告诉勇者其实是他自己的武器不好,并把他指引到了锻造厂。
1.2题目描述
“欢迎啊,老朋友。”
一阵寒暄过后,厂长带他们参观了厂子四周,并给他们讲锻造的流程。
“我们这里的武器分成若干的等级,等级越高武器就越厉害,并且对每一等级的武器都有两种属性值 b 和 c,但是我们初始只能花 a 个金币来生产 1 把 0 级剑......”
“所以你们厂子怎么这么垃圾啊,不能一下子就造出来 999 级的武器吗?”勇者不耐烦的打断了厂长的话。
“别着急,还没开始讲锻造呢......那我们举例你手中有一把 x 级武器和一把 y 级武器 (y = max(x − 1, 0)),我们令锻造附加值 k = min(c x , b y ),则你有 k/cx 的概率将两把武器融合成一把 x + 1 级的武器。”
“......但是,锻造不是一帆风顺的,你同样有 1− k/cx 的概率将两把武器融合成一把 max(x − 1, 0) 级的武器......”
勇者听完后暗暗思忖,他知道厂长一定又想借此机会坑骗他的零花钱,于是求助这个村最聪明的智者——你,来告诉他,想要强化出一把 n 级的武器,其期望花费为多少?
由于勇者不精通高精度小数,所以你只需要将答案对 998244353(7 ×17 × 2 23 + 1,一个质数 ) 取模即可。
1.3 格式
1.3.1 输入格式
第一行两个整数 n, a,含义如题所示。
为了避免输入量过大,第二行五个整数 bx, by, cx, cy, p,按照下列代码
来生成 b 和 c 数组。
b[0]=by+1;c[0]=cy+1; for(int i=1;i<n;i++){ b[i]=((long long)b[i-1]*bx+by)%p+1; c[i]=((long long)c[i-1]*cx+cy)%p+1; }
1.3.2 输出格式
输出一行一个整数,表示期望花费。
1.4 样例
1.4.1 样例 1 输入
0 6432
4602677 3944535 2618884 6368297 9477531
1.4.2 样例 1 输出
6432
1.4.3 样例 2 输入
1 3639650
6136976 5520115 2835750 9072363 9302097
1.4.4 样例 2 输出
150643649
1.4.5 样例 3 输入
10 2
2 33 6 66 2333333
1.4.6 样例 3 输出
976750710
1.4.7 样例 4 输入
200 5708788
0 0 0 0 1
1.4.8 样例 4 输出
696441597
1.5数据范围
测试点 | n<= | 特殊性质 |
---|---|---|
1 | 0 | n/a |
2 | 1 | n/a |
3 | 200 | 有 |
4 | 200 | n/a |
5 | 2000 | 有 |
6 | 2000 | a/n |
7 | 10^6 | 有 |
8 | 10^6 | n/a |
9 | 10^7 | 有 |
10 | 10^7 | n/a |
对于特殊性质处标示为“有”的数据满足 p = 1。
对于 100% 的数据,0 ≤ a ≤ 10^7 , 0 ≤ bx, by, cx, cy < p < 10^7 , 0 ≤ n ≤10^7
总算是自己想出来正解来的分析
考场上本来想打暴力,打着打着就把正解推出来了。。
直接设dp[i]表示获得i级武器需要的代价,用p来表示本次合成成功的概率。看起来dp[i]=(dp[i-1]+dp[i-2])/p。为什么是除以p呢?举个例子,如果在一个袋子里有无数多个球,黑色的占20%,那么平均拿几次才能拿到黑色的球呢?显然是1÷(20%)=5次。
但这个dp式子是错的,因为合成失败后还会剩下一把i-2级的武器可以用,而我们列出的转移式子并没有把它用上。既然失败后会获得i-2级的武器,那么i-2级的武器在合成中只需要一把即可,不需要去除p,所以dp式子应该是dp[i]=dp[i-1]/p+dp[i-2]。另外,这道题卡常。。。
官方总结:
代码:
#include<cstdio>
#include<algorithm>
#define mod 998244353
using namespace std;
int n,a,bx,by,cx,cy,p,dp[10000005];
int b[10000005],c[10000005],inv[10000005];
int main()
{
//freopen("forging.in","r",stdin);freopen("forging.out","w",stdout);
inv[1]=1;
scanf("%d%d%d%d%d%d%d",&n,&a,&bx,&by,&cx,&cy,&p);
for(int i=2;i<=p+1;i++)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
b[0]=by+1;c[0]=cy+1;dp[0]=a;
for(int i=1;i<n;i++)
{
b[i]=((long long)b[i-1]*bx+by)%p+1;
c[i]=((long long)c[i-1]*cx+cy)%p+1;
dp[i]=(dp[max(i-2,0)]+1ll*dp[i-1]*c[i-1]%mod*inv[min(c[i-1],b[max(i-2,0)])]%mod)%mod;
}
printf("%d\n",dp[n]=(dp[max(n-2,0)]+1ll*dp[n-1]*c[n-1]%mod*inv[min(c[n-1],b[max(n-2,0)])]%mod)%mod);
}
A组T2 整除 (division)
2.1 题目描述
整除符号为 |,d|n 在计算机语言中可被描述为 n%d == 0。
现有一算式 n| xm − x,给定 n,m,求 [1, n] 以内 x 解的个数。
解可能很大,输出取模 998244353。
2.2 格式
2.2.1 输入格式
其中 n 的给定方式是由 c 个不超过 t 的质数的乘积给出的,c 和 t 的范围会在数据范围中给出。
第一行一个 id 表示这个数据点的标号。
多组数据,其中第二行一个整数 T 表示数据组数。
对于每一组数据:
第一行两个整数 c 和 m。
第二行 c 个整数,这些整数都是质数,且两两不同,他们的乘积即为n。
由于你可以通过输入求出 t,输入不再给出。
2.2.2 输出格式
对于每组数据输出一行,表示解的个数。
2.3 样例
2.3.1 样例输入
0
1
2 3
2 3
2.3.2 样例输出
6
根本没学过数论还是要在这里强行分析
网上说根据中国剩余定理,可知
整个方程组的解的个数便等于方程组中每个方程在(1~p)的解的个数的乘积。个人理解就是第一组方程的一个解乘上第二组方程的一个解便可获得一个解(不是很严谨)我们就可以遍历1到p来求得每个方程的解。但是m很大,就算是快速幂也会T。但是我们发现x^m可以由它的约数的m次方相乘得到,所以只需要将质数的m次方算出,再用类似线性筛的方法把其它合数的m次方算出来即可。这是原博客地址:https://blog.csdn.net/ShadyPi/article/details/83047995,还有一种更好的方法:https://www.cnblogs.com/ImagineC/p/9880891.html。
这仍然是一道卡常的好题
来自官方的总结
代码:
#include<cstdio>
int T,c,m,ans,f[10005],p[10005],vis[10005];
int qp(int a,int k,int mod)
{
int res=1;
while(k)
{
if(k&1)res=res*a%mod;
k/=2;a=a*a%mod;
}
return res;
}
int sieve(int n)
{
int cnt=2;p[0]=0;
for(int i=2;i<n;cnt+=(f[i]==i),i++)
{
if(!vis[i])p[++p[0]]=i,f[i]=qp(i,m,n);
for(int j=1;j<=p[0];j++){if(i*p[j]>n)break;vis[i*p[j]]=1;f[i*p[j]]=f[i]*f[p[j]]%n;if(i%p[j]==0)break;}
}
return cnt;
}
int main()
{
//freopen("division.in","r",stdin);freopen("division.out","w",stdout);
for(scanf("%*d%d",&T);T--;)
{
scanf("%d%d",&c,&m);ans=1;
for(int i=1,a;i<=c;i++)scanf("%d",&a),ans=1ll*ans*sieve(a)%998244353;
printf("%d\n",ans);
}
}
A组T3 欠钱 (money)
3.1 题目描述
南极的企鹅王国大学中生活着 n 只企鹅,作为 21 世纪的优秀大学生,企鹅们积极响应“大众创业,万众创新”的号召,纷纷创业。但是创业需要资金,企鹅们最近手头比较紧,只能互相借钱。
企鹅的借钱行为是有规律可循的:每只企鹅只会借一次钱,并且只会从一只企鹅那里借钱。借钱关系中不存在环(即不存在类似“金企鹅欠银企鹅钱,银企鹅欠铜企鹅钱,铜企鹅欠金企鹅钱”这种情况)。
企鹅的还钱行为也是有规律可循的:每只企鹅一旦新获得了一笔钱,就会立刻用这笔钱尽可能偿还自己欠的债务,直到债务偿清或用光这笔钱。它只会使用新获得的这笔钱,至于以前它有没有钱、有多少钱,与还钱行为无关。
企鹅们经常会做美梦。在一只企鹅 A 的梦里,它梦见自己创业成功,一下子获得了 +∞ 元钱,于是(按照上文的还钱规则)它赶快把钱用来还债,接着拿到钱的那只企鹅也赶快把钱用来还债......如此往复,直到所有获得钱的企鹅都完成了还债操作。梦醒之后,它开心地把梦的内容告诉了另外一只企鹅 B,企鹅 B 听了,也很开心,于是它问道:在你的梦里,我获得了多少钱呢?(指 B 去还债之前手里的钱,包括后来用于还债的钱和还债后B 手里剩下的钱。)
梦毕竟是梦,对实际的欠债情况没有影响。
3.2格式
3.2.1输入格式
第一行两个整数 n 和 m,表示有 n 只企鹅,m 个操作。
接下来 m 行,有两种可能的格式:
- 0 a b c:修改操作,企鹅 a 向企鹅 b 借了 c 元钱。
- 1 a b:查询操作,询问假如 a 有了 +∞ 元钱,企鹅 b 会净收入多少钱。
本题强制在线,也就是说:对于每个操作输入的变量 a, b, c(如果没有c,那就只有 a, b)都不是实际的 a, b, c,想获得实际的 a, b, c 应当经过以下操作:
a = (a + lastans) % n + 1;
b = (b + lastans) % n + 1;
c = (c + lastans) % n + 1;
其中,lastans 是上一次询问的答案。如果没有上一次询问,lastans 为0。
3.2.2输出格式
对每个询问操作,输出一行一个数表示答案。
3.3样例
3.3.1样例输入
5 9
0 1 2 1
0 0 1 2
1 0 1
1 2 4
0 2 1 1
1 2 0
0 3 1 0
1 4 2
1 3 4
3.3.2样例输出
3
2
0
1
0
3.4 数据范围
数据分为以下几种:
第一种:占 10%,n ≤ 5000 且 m ≤ 10000;
第二种:占 20%,所有借钱事件(0 开头的操作)发生在所有询问事件(1 开头的操作)之前;
第三种:占 30%,对于一只企鹅 A,最多只有一只企鹅向 A 借钱;
第四种:占 40%,没有特殊性质,n、m 大小有一定梯度。
对于所有数据,满足:n ≤ 10^5 ,m ≤ 10^6 ,0 ≤ a, b, c ≤ n 且 a != b。
分析
LCT裸题???可是我不会打。只需要维护链上最小值,连根都不用换。至于如何判断b能否到达a?先access(b),打通b到根的路径,此时lca(a,b)一定与b在同一个splay里面。再access(a),打通根到a的路径,记录最后一次打通的点,画图可知,它一定是a和b的lca。b能到达a则一定有lca(a,b)=b,判断即可。
仍然是一道卡常的题。
疯狂压行后还开O3的代码:
#pragma GCC optimize(3,"Ofast","inline")
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ls ch[x][0]
#define rs ch[x][1]
#define get(x) (ch[fa[x]][0]==x||ch[fa[x]][1]==x?ch[fa[x]][1]==x:-1)
using namespace std;
const int maxn=2e6+5;
int n,m,a,b,c,ans,ord,mn[maxn],val[maxn];
int tot,ch[maxn][2],fa[maxn];
inline void pushup(register int x){mn[x]=min(val[x],min(mn[ls],mn[rs]));}
int read(){register int x=0,f=1;char ch;while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return f*x;}
void print(int x){if(x>9)print(x/10);putchar(x%10+'0');}
void rotate(register int x)
{
register int f=fa[x],ff=fa[f],flag=get(x);
if(get(f)!=-1)ch[ff][get(f)]=x;
fa[x]=ff;fa[f]=x;if(ch[x][!flag])fa[ch[x][!flag]]=f;
ch[f][flag]=ch[x][!flag];ch[x][!flag]=f;pushup(f);
}
void splay(int x)
{
for(register int y=fa[x];get(x)!=-1;rotate(x),y=fa[x])
if(get(y)!=-1)rotate(get(x)==get(y)?y:x);
pushup(x);
}
inline int access(register int x){register int f=0;for(;x;x=fa[f=x])splay(x),rs=f,pushup(x);return f;}
inline void link(register int x,register int y){splay(x);fa[x]=y;}
int main()
{
//freopen("money.in","r",stdin);freopen("money.out","w",stdout);
scanf("%d%d",&n,&m);memset(mn,127,sizeof mn);memset(val,127,sizeof val);tot=n;
while(m--)
{
ord=read();a=(read()+ans)%n+1;b=(read()+ans)%n+1;
if(ord)access(b),(access(a)==b?print(ans=min(val[b],mn[ch[b][1]])):print(ans=0)),puts("");
else c=(read()+ans)%n+1,val[++tot]=c,mn[tot]=c,link(a,tot),link(tot,b);
}
}