[游记&题解]2019暑假中山纪中集训day3&4

中山纪中NOIP2019提高组模拟赛

A组 day4 solution

Date:2019.8.4

Probelm A B C
题目名称 锻造 整除 欠钱
Difficulty 1800 2200 2500
Finished Yes Yes Yes

简单游记

day 3

AM
今天是讲课的第一天。
主题是DP。
然后一来就讲联赛难度???的斜率优化…
感觉对新手很不友好,自己学过都觉得他讲的很不清楚,例题大多都是做过的…
后面的杂题选讲倒是还行。
中午去迟了,被强迫买了泡面…
PM
下午先补的上午的决策单调性,感觉比上午清楚,当好像少了点重要内容??
然后讲D(alao)P(ower)杂题选讲,选的题都很不错,还是比较经典的…
然后晚上讲到点分治套dp的时候就跑了…(毕竟根本不熟…)

day 4

AM
今天是自闭的第三天。
然后看A题,发现是之前讲过的套路题,将dp方程移个项就好了…
然后开始打,结果手残打错了个字母然后写+调用了50min。
然后看B,发现跟之前多校的的某题很像。
肯定和原根有关系…
然后就分析了一波…发现 n n n为质数的时候就是算 x m − 1 ≡ 0 (   m o d   n ) x^{m-1}≡0(\bmod n) xm10(modn)的解的个数.
之前百度看过一个结论,这个解的个数就是 gcd ⁡ ( m − 1 , n − 1 ) \gcd(m-1,n-1) gcd(m1,n1)
然后发现只有一个测试点是 c = 1 c=1 c=1???
不得不说出题人真·良·心。
本着不打暴力的原则,于是就没有打前几个暴力分。
然后看C,发现如果没有强制在线就是一个倍增大水题…
强制在线呢?
要动态更新倍增数组?
看起来很麻烦的样子…
然后就打离线的30分。
然后打完就差不多结束了…
中午吃饭…香芋扣肉是什么鬼…
PM
下午来看结果,惊呆了,只有T3有10分…
然后发现前两题CE了???
然后发现T1,T2交了一堆乱码上去…
研究了一下才发现这破OJ提交的时候多点几次,就会多交几个空代码…
然后看题解…
看完感觉这是我做过的最烂的模拟赛…
T1 卡内存+卡常好题。
T2 中国剩余定理板子题+卡常好题。
T3 LCT模板+卡常好题。
你NOIP靠卡常,卡空间,模板题选人?
然后发现T2将每个 gcd ⁡ ( m − 1 , p i − 1 ) \gcd(m-1,p_i-1) gcd(m1,pi1)乘起来就从10分变成100分了…
体验感极差.jpg

晚上莫名dalao讲学生端与教师端的神密关系??

锻造 (forging)

[题解]

算法标签:期望dp
套路题,这里不具体讲做法。
本题卡内存卡时间。
因此只能够开一个数组,不能开long long,最好用滚动数组,必须提前预处理逆元。

[实现]

#pragma GCC optimize(2)
#include
#include
#include
#include
using namespace std;
#define MAXN 10000000
#define LL long long
#define MOD 998244353
int b[MAXN+5],c[MAXN+5],cst;
int n,bx,by,cx,cy,p,inv[MAXN+5];
int f[3];
int min(int a,int b){return (a<b)?a:b;}
int max(int a,int b){return (a>b)?a:b;}
int main()
{
	freopen("forging.in","r",stdin);
	freopen("forging.out","w",stdout);
	scanf("%d%d%d%d%d%d%d",&n,&cst,&bx,&by,&cx,&cy,&p);
	b[0]=by+1;c[0]=cy+1;
	for(register int i=1;i<n;i++){
		b[i]=(1LL*b[i-1]*bx+by)%p+1;
		c[i]=(1LL*c[i-1]*cx+cy)%p+1;
	}
	inv[1]=1;
	for(register int i=2;i<p;i++)
	inv[i]=1LL*(MOD-MOD/i)*inv[MOD%i]%MOD;
	f[0]=1,f[1]=1LL*(c[0]+min(c[0],b[0]))*inv[min(c[0],b[0])]%MOD;
	for(int i=1;i<=n;i++){
	f[(i+1)%3]=1LL*(1LL*f[i%3]*c[i]%MOD*inv[min(c[i],b[i-1])]%MOD)%MOD+f[(i-1)%3]%MOD;
	f[(i-1)%3]=0;
	}
	printf("%d",1LL*f[n%3]*cst%MOD);
}

整除 (division)

[题解]

算法标签:中国剩余定理,原根

x m − 1 ≡ 0 (   m o d   n ) x^{m-1}≡0(\bmod n) xm10(modn)的解的个数,在[1,n-1]范围内就是 g c d ( m − 1 , n − 1 ) gcd(m-1,n-1) gcd(m1,n1)
原问题求 x m ≡ x (   m o d   p 1 p 2 . . . p c ) x^m≡x(\bmod p_1p_2...p_c) xmx(modp1p2...pc)
可以将其化为
{ x m − 1 ≡ 1 (   m o d   p 1 ) x m − 1 ≡ 1 (   m o d   p 2 ) . . . x m − 1 ≡ 1 (   m o d   p c ) \begin{cases} x^{m-1}≡1(\bmod p_1) \\ x^{m-1}≡1(\bmod p_2) \\ ...\\ x^{m-1}≡1(\bmod p_c) \\ \end{cases} xm11(modp1)xm11(modp2)...xm11(modpc)

注意到从每个方程的一个合法解中任取一个乘起来,就能够组成一个原方程合法的解

因此我们就只用算每个方程的解就好了…

而单个方程的解的个数就为 gcd ⁡ ( m − 1 , p − 1 ) + 1 \gcd(m-1,p-1)+1 gcd(m1,p1)+1

乘起来即可。

证明:

我们要求满足 x ∈ [ 1 , p ] x∈[1,p] x[1,p] x m ≡ x (   m o d   p ) x^m≡x (\bmod p) xmx(modp) x x x 的个数。

首先 x = p x=p x=p 一定满足,故不特殊考虑,最后答案 + 1 +1 +1 即可。接下来只考虑 x ∈ [ 1 , p − 1 ] x∈[1,p−1] x[1,p1] 的情况。由于 p p p 为质数,因此 p p p存在一个原根 g g g [ 1 , p − 1 ] [1,p−1] [1,p1]内的任意数在   m o d   p \bmod p modp 意义下都可以表示为 g y g^y gy 的形式。这样,原方程就转化为:

g m y ≡ g y (   m o d   p ) g^{my}≡g^y (\bmod p) gmygy(modp)

根据费马小定理:

m y ≡ y (   m o d   p − 1 ) ⇒ ( m − 1 ) y ≡ 0 (   m o d   p − 1 ) my≡y (\bmod p−1)⇒(m−1)y≡0 (\bmod p−1) myy(modp1)(m1)y0(modp1)

k = gcd ⁡ ( m − 1 , p − 1 ) k=\gcd(m−1,p−1) k=gcd(m1,p1),两边同时除以 kk,得:

m − 1 k y ≡ 0 ( m o d p − 1 k ) \frac{m−1}{k}y≡0 (mod \frac{p−1}{k}) km1y0(modkp1)

由于此时 g c d ( m − 1 k , p − 1 k ) = 1 gcd(\frac{m−1}{k},\frac{p−1}{k})=1 gcd(km1,kp1)=1,因此 y y y 一定有 p − 1 k ∣ y \frac{p−1}{k}|y kp1y。由于 y ∈ [ 0 , p − 2 ] y∈[0,p−2] y[0,p2],显然, p − 1 k \frac{p−1}{k} kp1 的任意小于 k k k 的非负整数倍( 0 ∼ k − 1 0∼k−1 0k1 倍)均满足条件,因此 y y y 共有 k k k 种合法取值。

因此满足 ∑ x = 1 p [ x m ≡ x   m o d   p ] ∑^p_{x=1}[x^m≡x \bmod p] x=1p[xmxmodp] i i i 共有 k + 1 k+1 k+1个。

[来自ImagineC大佬]

[实现]

#include
#include
#include
#include
using namespace std;
#define MAXN 10000000
#define LL long long
#define MOD 998244353
int T,n,m;
int gcd(int a,int b){
	return (!b)?a:gcd(b,a%b);
}
int main()
{
	freopen("division.in","r",stdin);
	freopen("division.out","w",stdout);
	scanf("%*d%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		LL ans=1;
		for(int i=1;i<=n;i++){
			int x;scanf("%d",&x);
			ans=ans*(gcd(x-1,m-1)+1)%MOD;
		}printf("%lld\n",ans);
	}
}

欠钱 (money)

[题解]

算法标签:动态倍增/LCT

其实并没有什么好说的,就是记录一下现在某个点倍增到某个位置,然后之后要用到的时候在动态更新一下就行了。

我们动态维护深度的时候用并查集就好了。

[实现]

#pragma GCC optimize(2)
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 100000
#define Pr pair
#define FR first
#define SE second
#define INF 1000000000
int n,m,id,a,b,c,lst;
int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int f[MAXN+1][20],mn[MAXN+1][20];
int fa[MAXN+5],dep[MAXN+5],cd[MAXN+5];
int xfind(int x){
	if(fa[x]==x)return x;
	int tmp=xfind(fa[x]);
	dep[x]+=dep[fa[x]];
	return fa[x]=tmp;
}
void upd(int x,int d){
	for(int i=cd[x]+1;i<=d;i++){
		upd(f[x][i-1],i-1);
		f[x][i]=f[f[x][i-1]][i-1];
		mn[x][i]=min(mn[x][i-1],mn[f[x][i-1]][i-1]);
	}
	cd[x]=max(cd[x],d);
}
int jump(int a,int b){
	int d=dep[a]-dep[b],res=INF;
	for(int i=19;i>=0;i--)
	if(d&(1<<i)){
		d^=(1<<i);
		upd(a,i);
		res=min(res,mn[a][i]);
		a=f[a][i];
	}
	if(a==b)return res;
	return 0;
}
int main()
{
	freopen("money.in","r",stdin);
	freopen("money.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	fa[i]=i;
	for(int i=1;i<=m;i++){
		id=read(),a=(read()+lst)%n+1,b=(read()+lst)%n+1;
		if(id==0){
			c=(read()+lst)%n+1;
			f[a][0]=b;mn[a][0]=c;
			fa[a]=b;dep[a]=1;
		}else{
			if(xfind(a)!=xfind(b)||dep[a]<=dep[b])lst=0;
			else lst=jump(a,b);
			printf("%d\n",lst);
		}
	}
}

你可能感兴趣的:(游记合辑)