Date:2019.8.4
Probelm | A | B | C |
---|---|---|---|
题目名称 | 锻造 | 整除 | 欠钱 |
Difficulty | 1800 | 2200 | 2500 |
Finished | Yes | Yes | Yes |
AM
今天是讲课的第一天。
主题是DP。
然后一来就讲联赛难度???的斜率优化…
感觉对新手很不友好,自己学过都觉得他讲的很不清楚,例题大多都是做过的…
后面的杂题选讲倒是还行。
中午去迟了,被强迫买了泡面…
PM
下午先补的上午的决策单调性,感觉比上午清楚,当好像少了点重要内容??
然后讲D(alao)P(ower)杂题选讲,选的题都很不错,还是比较经典的…
然后晚上讲到点分治套dp的时候就跑了…(毕竟根本不熟…)
AM
今天是自闭的第三天。
然后看A题,发现是之前讲过的套路题,将dp方程移个项就好了…
然后开始打,结果手残打错了个字母然后写+调用了50min。
然后看B,发现跟之前多校的的某题很像。
肯定和原根有关系…
然后就分析了一波…发现 n n n为质数的时候就是算 x m − 1 ≡ 0 ( m o d n ) x^{m-1}≡0(\bmod n) xm−1≡0(modn)的解的个数.
之前百度看过一个结论,这个解的个数就是 gcd ( m − 1 , n − 1 ) \gcd(m-1,n-1) gcd(m−1,n−1)。
然后发现只有一个测试点是 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(m−1,pi−1)乘起来就从10分变成100分了…
体验感极差.jpg
晚上莫名dalao讲学生端与教师端的神密关系??
算法标签:期望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);
}
算法标签:中国剩余定理
,原根
x m − 1 ≡ 0 ( m o d n ) x^{m-1}≡0(\bmod n) xm−1≡0(modn)的解的个数,在[1,n-1]范围内就是 g c d ( m − 1 , n − 1 ) gcd(m-1,n-1) gcd(m−1,n−1)。
原问题求 x m ≡ x ( m o d p 1 p 2 . . . p c ) x^m≡x(\bmod p_1p_2...p_c) xm≡x(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} ⎩⎪⎪⎪⎨⎪⎪⎪⎧xm−1≡1(modp1)xm−1≡1(modp2)...xm−1≡1(modpc)
注意到从每个方程的一个合法解中任取一个乘起来,就能够组成一个原方程合法的解
因此我们就只用算每个方程的解就好了…
而单个方程的解的个数就为 gcd ( m − 1 , p − 1 ) + 1 \gcd(m-1,p-1)+1 gcd(m−1,p−1)+1。
乘起来即可。
证明:
我们要求满足 x ∈ [ 1 , p ] x∈[1,p] x∈[1,p]时 x m ≡ x ( m o d p ) x^m≡x (\bmod p) xm≡x(modp) 的 x x x 的个数。
首先 x = p x=p x=p 一定满足,故不特殊考虑,最后答案 + 1 +1 +1 即可。接下来只考虑 x ∈ [ 1 , p − 1 ] x∈[1,p−1] x∈[1,p−1] 的情况。由于 p p p 为质数,因此 p p p存在一个原根 g g g, [ 1 , p − 1 ] [1,p−1] [1,p−1]内的任意数在 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) gmy≡gy(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) my≡y(modp−1)⇒(m−1)y≡0(modp−1)
令 k = gcd ( m − 1 , p − 1 ) k=\gcd(m−1,p−1) k=gcd(m−1,p−1),两边同时除以 kk,得:
m − 1 k y ≡ 0 ( m o d p − 1 k ) \frac{m−1}{k}y≡0 (mod \frac{p−1}{k}) km−1y≡0(modkp−1)
由于此时 g c d ( m − 1 k , p − 1 k ) = 1 gcd(\frac{m−1}{k},\frac{p−1}{k})=1 gcd(km−1,kp−1)=1,因此 y y y 一定有 p − 1 k ∣ y \frac{p−1}{k}|y kp−1∣y。由于 y ∈ [ 0 , p − 2 ] y∈[0,p−2] y∈[0,p−2],显然, p − 1 k \frac{p−1}{k} kp−1 的任意小于 k k k 的非负整数倍( 0 ∼ k − 1 0∼k−1 0∼k−1 倍)均满足条件,因此 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[xm≡xmodp] 的 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);
}
}
算法标签:动态倍增
/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);
}
}
}