【日常小测】生日礼物

相关链接

题目传送门:http://oi.cyo.ng/wp-content/uploads/2017/03/4237864728368.png
斯特林数相关:https://en.wikipedia.org/wiki/Stirling_number

解题报告

答案显然就是两个序列卷积起来
而其中一个序列就是一个组合数一样的东西
另一个序列则需要处理出第二类斯特林数
众所周知,预处理特殊的第二类斯特林数可以用$NTT$优化
于是就搞两次$NTT$就可以了,时间复杂度$O(n \log n)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 600009;
const int MOD = 786433;
const int RT = 13;
 
int g[N],f[N],POW[N],REV[N],pos[N];
int n1,n2,m,q,vis[N],a[N],b[N];
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret*f;
}
 
inline int Pow(int w, int t) {
    int ret = 1;
    for (;t;t>>=1,w=(LL)w*w%MOD)
        if(t&1)ret=(LL)ret*w%MOD;
    return ret;
}   
 
inline int C(int a, int b) {
    if (a > b) return 0;
    return ((LL)POW[b] * REV[a] % MOD) * REV[b-a] % MOD;
}
 
inline void prework() {
    for (int i=2;i<N;i++) {
        for (int j=i*2;j<N;j+=i) {
            vis[j] = 1;
        }
    } vis[1] = 1;
    POW[0] = REV[0] = 1;
    for (int i=1;i<N;i++) POW[i] = (LL)POW[i-1] * i % MOD;
    REV[N-1] = Pow(POW[N-1], MOD-2);
    for (int i=N-2;i;i--) REV[i] = REV[i+1] * (i+1ll) % MOD;
}

inline void ntt(int *a, int len, int rev = 0) {
	for (int i=0;i<len;i++) if (pos[i]<i) swap(a[i], a[pos[i]]);
	for (int l=2;l<=len;l<<=1) {
		int wn = Pow(RT, MOD / l);
		if (rev) wn = Pow(wn, MOD-2);
		for (int i=0,w=1,tmp;i<len;i+=l,w=1) {
			for (int j=0;j<(l>>1);j++,w=(LL)w*wn%MOD) {
				tmp = (LL)w * a[i+j+(l>>1)] % MOD;
				a[i+j+(l>>1)] = (a[i+j] - tmp) % MOD;
				a[i+j] = (a[i+j] + tmp) % MOD;
			}
		}
	}
	if (rev) for (int i=1,Rev=Pow(len,MOD-2);i<=len;i++) 
		a[i] = ((LL)a[i] * Rev % MOD + MOD) % MOD;
}

inline void solve(int l, int *a, int *b) {
	int t = -1, len = 1;
	while (len < (l+1)) len <<= 1, t++;
	for (int i=0;i<len;i++) {
		pos[i] = pos[i>>1]>>1;
		if (i&1) pos[i] |= 1<<t;
	} 
	ntt(a, len); ntt(b, len);
	for (int i=0;i<len;i++) a[i] = (LL)a[i] * b[i] % MOD;
	ntt(a, len, 1);
}
 
inline void update() {
	memset(f,0,sizeof(f)); memset(g,0,sizeof(g));
	memset(a,0,sizeof(a)); memset(b,0,sizeof(b));
    for (int i=1;i<=n2;i++) 
        g[i] = (LL)C(i-1, n2-1) * C(i, m) % MOD;
    for (int i=0;i<=n1;i++) {
		a[i] = (i&1)? MOD-REV[i]: REV[i];
		b[i] = (LL)Pow(i, n1) * REV[i] % MOD;
	}
	solve(n1 << 1, a, b);
    for (int i=1;i<=n1;i++) {
        if (vis[i]) f[i] = i==n1? 1: C(i-1, n1);
        else f[i] = a[i]; 
    }
}
 
int main() {
    prework();
    for (int T=read();T;T--) {
        n1 = read(); n2 = read(); 
        m = read(); update();
        solve(n1 + n2, f, g);
        for (int q=read();q;q--) 
            printf("%d\n",(f[read()]+MOD)%MOD);
    }
    return 0;
}

【BZOJ 3509】[CodeChef] COUNTARI

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3509
原题传送门:https://www.codechef.com/problems/COUNTARI
神犇题解:http://blog.miskcoo.com/2015/04/bzoj-3509

解题报告

这题如果没有$i<j<k$那么撸一发FFT就可以了
现在考虑$i<j<k$的限制,我们可以分块!

设块的大小为$S$,那么对于$j,k$或$i,j$在同一个块内的,我们可以$O(S^2)$暴力
对于$i,k$都不与$j$在同一个块的情况,我们可以用FFT做到$O(\frac{n}{S} \cdot v \log v)$
然后复杂度分析要准确的话应该搞倒数,但我不会QwQ

XeHoTh似乎用一份巨强暴力,卡到了BZOJ和CC的Rank 1
伏地膜啊!太强大了 _(:з」∠)_

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int M = 70009;
const int N = 100009;
const int T = 15;
const int L = 1 << T + 1;
const double EPS = 0.5;
const double PI = acos(-1);
 
int n,S,ed,arr[N],tot[M],pos[M];
struct COMPLEX{
	double a,b;
	inline COMPLEX() {}
	inline COMPLEX(double x, double y):a(x),b(y) {}
	inline COMPLEX operator - (const COMPLEX &B) {return COMPLEX(a-B.a,b-B.b);}
	inline COMPLEX operator + (const COMPLEX &B) {return COMPLEX(a+B.a,b+B.b);}
	inline COMPLEX operator * (const COMPLEX &B) {return COMPLEX(a*B.a-b*B.b,a*B.b+b*B.a);}
}a1[M],a2[M],bas(1,0);
LL vout,cnt[M];
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret*f;
}
 
inline void FFT(COMPLEX *a, int T = 1) {
    for (int i=0;i<L;i++) if (pos[i]<i) swap(a[pos[i]],a[i]);
    for (int l=2;l<=L;l<<=1) {
        COMPLEX wn(cos(2*PI/l),sin(2*PI/l)*T),w(1,0);
        for (int i=0;i<L;i+=l,w=bas) {
            for (int j=i;j<(i+(l>>1));j++,w=w*wn) {
                COMPLEX tmp = w * a[j+(l>>1)];
                a[j+(l>>1)] = a[j] - tmp;
                a[j] = a[j] + tmp;
            }
        }
    }   
}
 
int main() {
    n = read(); S = 4000;
    for (int i=1;i<=n;i++) arr[i] = read();
    for (int i=1;i<L;i++) pos[i] = (pos[i>>1]>>1)|((i&1)<<T);
    for (int i=S+1;i+S<=n;i+=S) { 
        memset(a1,0,sizeof(a1));
        memset(a2,0,sizeof(a2));
        for (int j=1;j<i;j++) a1[arr[j]] = a1[arr[j]] + bas;
        for (int j=i+S;j<=n;j++) a2[arr[j]] = a2[arr[j]] + bas;
        FFT(a1); FFT(a2);
        for (int j=0;j<L;j++) a1[j] = a1[j] * a2[j];
        FFT(a1, -1);
        for (int j=0;j<L;j++) cnt[j] = a1[j].a / L + EPS;
        for (int j=i;j<i+S;j++) vout += cnt[arr[j]<<1];
    }
    for (int i=1,t;i<=n;i+=S) {
        ed = max(ed, i);
        for (int j=i,lim=min(n+1,i+S);j<lim;j++) {
            for (int k=j+1;k<lim;k++) {
                t = (arr[j] << 1) - arr[k];
                vout += t<M&&t>0? tot[t]: 0;
            }
            tot[arr[j]]++;
        }
    }
    memset(tot,0,sizeof(tot));
    for (int i=ed,t;i>0;i-=S) {
        for (int j=min(n,i+S-1);j>=i;j--) {
            for (int k=j-1;k>=i;k--) {
                t = (arr[j] << 1) - arr[k];
                vout += t<M&&t>0? tot[t]: 0;
            }
        }
        for (int j=min(n,i+S-1);j>=i;j--) tot[arr[j]]++;
    } 
    printf("%lld\n",vout);
    return 0;
}

【BZOJ 4762】最小集合

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4762

吐槽

这题之前在绍一集训的时候就考过一次,今天又考了
但还是写炸了,或许老年选手不适合写代码吧!

解题报告

为了代码好写,我们先按照二进制位取反
这样的话,问题变为选出一些数,使其或起来为全集,且少一个都不行

我们考虑$f[i][j]$为将前$i$个数加入集合之后,或起来为$j$的方案数
这样可以防止一个数被之前的数代替,但对于之后的数却无能为力
于是我们加一维$f[i][j][k]$,其中$k$表示后面的数或起来为$k$

这样就可以使用类似容斥的思想进行两种转移

  1. $f[i][j][k] \to f[i+1][j|a_i][k-(k \& a_i)]$表示不强制非法
  2. $-f[i][j][k] \to f[i+1][j|a_i][(k-(k \& a_i))|(a_i-(a_i \& j))]$表示强制非法
    ps: 根据容斥原理,第二种转移会配上$-1$的系数

此时我们已经可以进行$O(n \cdot 4^{\omega})$的DP了
再仔细想想,第三维一定是第二维的子集,于是复杂度降到$O(n \cdot 3^{\omega})$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 1001;
const int M = (1 << 10) - 1;
const int MOD = 1000000007;

int n,vout,a[N],f[1<<10][1<<10];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

int main() {
	n = read();
	for (int i=1;i<=n;i++) a[i] = read() ^ M;
	f[0][0] = 1; 
	for (int t=1;t<=n;t++) {
		for (int i=M,ti,tj,uni;~i;--i) {
			if ((ti = (a[t] | i)) > i) {
				uni = ti - i;
				for (int j=i;;(--j)&=i) {
					tj = (j - (j & a[t]));
					f[ti][tj] = (f[ti][tj] + f[i][j]) % MOD;
					f[ti][tj|uni] = (f[ti][tj|uni] - f[i][j]) % MOD;
					if (!j) break;
				}
			}
		}
	}
	printf("%d\n",(f[M][0]+MOD)%MOD);
	return 0;
}

【日常小测】随机游走

题目大意

给定一棵$n(n \le 10^5)$个点的无根树,再给定$m(m \le 10^5)$个询问
每次询问给定起点和终点,从起点开始XJB走到终点的期望步数是多少?
定义XJB走为:每次完全随机选择一条出边走出去,可以走回头路

解题报告

如果定义$f_{u,v}$为当前在$u$点,最终走到$v$点的期望步数
那肯定搞一个高斯消元就可以了,但这题数据太大搞不了 QwQ

但我们仔细观察一下,这里的XJB走非常妙啊!目标点、来源点完全不影响决策
于是我们定义$f_u$为这个点走到父亲的期望步数,$g_u$为这个点的父亲走到它的期望步数
那我们就可以推出以下式子:
$$f_u=\deg u + \sum\limits_{v \in son_u}{f_v}$$
$$g_u=\deg fa_u + \sum\limits_{v \in son_{fa_u},v \ne u}{f_v} + g_{fa_{fa_u}}$$
于是我们DFS两遍,然后记个前缀和什么的
询问的时候求出lca然后加加减减就可以辣!
时间复杂度:$O(n \log n)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 100009;
const int M = N << 1;

int n,m,head[N],nxt[M],to[M];
int in[N],dep[N],fa[N][20];
LL f[N],g[N],PreF[N],PreG[N];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

inline void AddEdge(int u, int v) {
	static int E = 1; in[u]++; in[v]++;
	to[++E] = v; nxt[E] = head[u]; head[u] = E;
	to[++E] = u; nxt[E] = head[v]; head[v] = E;
}

inline int LCA(int u, int v) {
	if (dep[u] < dep[v]) swap(u, v);
	for (int i=19;~i;i--) if (dep[fa[u][i]]>=dep[v]) u = fa[u][i];
	if (u == v) return u;
	for (int i=19;~i;i--) if (fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}

void DFS1(int w, int anc) {
	fa[w][0] = anc;	f[w] = in[w]; 
	dep[w] = dep[anc] + 1;
	for (int i=head[w];i;i=nxt[i]) {
		if (to[i] != anc) {
			DFS1(to[i], w);
			f[w] += f[to[i]];
		}
	}
}

void DFS2(int w, int anc) {
	PreF[w] = PreF[anc] + f[w];
	PreG[w] = PreG[anc] + g[w];
	for (int i=head[w];i;i=nxt[i]) {
		if (to[i] != anc) {
			g[to[i]] = f[w] - f[to[i]] + g[w];
			DFS2(to[i], w);
		}
	}	
}

int main() {
	n = read();
	for (int i=1;i<n;i++) AddEdge(read(),read());
	DFS1(1, 1);
	DFS2(1, 1);
	for (int j=1;j<20;j++) {
		for (int i=1;i<=n;i++) {
			fa[i][j] = fa[fa[i][j-1]][j-1];
		}
	}
	m = read();
	for (int i=1,u,v,lca;i<=m;i++) {
		u = read(); v = read(); lca = LCA(u, v);
		printf("%lld\n",PreF[u]-PreF[lca]+PreG[v]-PreG[lca]);
	}
	return 0;
}

【BZOJ 2437】[NOI2011] 兔兔与蛋蛋

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2437
神犇题解:http://blog.csdn.net/qpswwww/article/details/45368587

解题报告

我们先将整个棋盘黑白染色,将空格棋子所在方格的颜色分类
在可相互转化的状态之间连边,那么这就是一张二分图了

那么原题的问题转换为:

给定一张二分图,每次沿边移动一次
每个点只能到达一次,最后谁不能动谁就输了
询问每一个点作为起点时是否先手必胜

考虑一个点如果在任意一种最大匹配当中,那么先手每一次走匹配边,则后手必败
换一句话来说,如果一个点一定在最大匹配当中,那么这个点是先手必胜的
再考虑一下,如果一个点不一定在最大匹配当中,那么删掉这个点之后存在最大匹配,那后手走到那个点去就可以了!
再换一句话来说,如果一个点不一定在最大匹配中,那么这个点先手必败

现在我们只需要求出一个点是否在最大匹配中就可以了!
我们可以枚举这个点,将其删掉后跑最大匹配验证,但复杂度是$O(n^4)$的
或者我们可以用今年冬令营的那个一般图匹配的算法来做,时间复杂度是$O(n^3)$的

不过我们可以又一个常数更小的算法

我们可以先求出一个最大匹配,然后假设当点在点$a$
将其删掉后,看$a$的匹配点$b$还能不能匹配即可

吐槽

博弈题居然还能这么玩!
真的是长见识了 _(:з」∠)_
不过代码好难写啊,不想写代码 ┑( ̄Д  ̄)┍

【Codeforces 757E】Bash Plays with Functions

相关链接

题目传送门:http://codeforces.com/problemset/problem/757/E
官方题解:http://codeforces.com/blog/entry/49743

解题报告

这题窝萌需要打很多的表来发现以下规律:

Ⅰ. 设$x$的素因子种类数为$g(x)$那么$f_0(x)$等于$2^{g(x)}$
Ⅱ. 由规律Ⅰ可得,$f_0(x)$为积性函数,又$f_i(x)=f_{i-1} * 1(x)$,于是我们可用归纳证明得$f_i(x)$均为积性函数
Ⅲ. 设$p$为素数,$f_r(p^k)$与$p$无关,且满足递推式:$f_r(p^k)=\sum\limits_{i=0}^{k}{f_{r-1}(p^i)}$

于是我们预处理出$f_r(p^k)$
对于每次询问,使用积性函数的性质,拆成很多质因数的幂次来做可以辣!
时间复杂度:$O((q+r) \log n)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 1000009;
const int MOD = 1000000007;

int tot,vis[N],pri[N],sur[N],g[N][21];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline void prework() {
	for (int i=2;i<N;i++) {
		if (!vis[i]) pri[++tot] = i, sur[i] = i;
		for (int j=1;j<=tot&&i*pri[j]<N;j++) {
			vis[i*pri[j]] = 1; sur[i*pri[j]] = pri[j];
			if (i % pri[j] == 0) break;
		}
	}	
	g[0][0] = 1;
	for (int i=1;i<=20;i++) g[0][i] = 2;
	for (int i=1;i<N;i++) {
		for (int j=0;j<=20;j++) {
			g[i][j] = ((j? g[i][j-1]: 0) + g[i-1][j]) % MOD;
		}
	}
}	

inline int solve(int a, int b) {
	int ret = 1;
	for (int cnt=0,p=sur[b];b>1;cnt=0,p=sur[b]) {
		while (b % p == 0) b /= p, cnt++;
		ret = (LL)ret * g[a][cnt] % MOD;
	}
	return ret;
}

int main() { 
	prework();
	for (int T=read(),a,b;T;T--) {
		a = read(); b = read();
		printf("%d\n",solve(a,b));
	}
	return 0;
}

【日常小测】计数

相关链接

题目传送门:http://oi.cyo.ng/wp-content/uploads/2017/03/20170301145817_34363.png
官方题解:http://oi.cyo.ng/wp-content/uploads/2017/03/20170301150237_94661.png

一句话题意

给$n_1$个$A$,$n_2$个$B$,$n_3$个$C$,$n_4$个$D$
问有多少种排列方式,使得相邻两项全部不同
$n_1,n_2,n_3,n_4 \le 10^3$

吐槽

因为做过魔法的碰撞
所以这题卡在$O(n^3)$的复杂度上卡了三个半小时 QwQ
最后暴力还$MLE$了 QwQ

解题报告

假设我们知道了将$A,B$分成$i$段、每一段中相邻两项不同的方案数为$f_i$
知道了将$C,D$分成$i$段、每一段中相邻两项不同的方案数为$g_i$
那么答案显然为$\sum\limits_{i=1}^{n_1+n_2}{f_i \cdot (g_{i-1}+2g_i+g_{i+1})}$
至于中间那一项为什么要乘以2? 因为多出来那一段我们既可以放段首,也可以放段尾

现在唯一的问题就是如何求出$f_i,g_i$了
考虑仅由$A,B$组成的字符串,一定为以下四种情况之一

[1]ABAB…A
[2]BABA…B
[3]ABAB…B
[4]BABA…A

假如我们枚举第一种情况出现了$i$次
那么第二种情况的出现次数$j=i-(a-b)$
那剩下的$k$次,就是第三种和第四种了,不难发现我们乘上$2^k$之后他们就可以视为等价
于是我们先在第一种情况的位置上插入$A$,第二种情况插入$B$,第三、四种情况插入$AB(BA)$
之后我们相当于把剩下的$A,B$两两配对,然后分成$x$组,允许空集
那么这就是插板法的板题了

于是我们就用上述算法处理出$f_i,g_i$即可
时间复杂度:$O(n^2)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int MOD = 1000000007;
const int N = 2009;

int vout,f[N],g[N],C[N][N],PW2[N];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline int solve(int a, int b, int c) {
	if (a < b) swap(a, b);
	if (!a && b) return b == c;
	int ret = 0;
	for (int i=a-b,j,k;i<=c;++i) {
		j = i - a + b; k = c - i - j;
		if (j >= 0 && k >= 0 && a >= i + k ) {
			(ret += (((LL)C[i] * C[j][c-i] % MOD) * PW2[k] % MOD) * C[c-1][a-i-k+c-1] % MOD) %= MOD;
		}
	}
	return ret;
}

int main() {
	PW2[0] = 1; for (int i=1;i<N;i++) PW2[i] = (PW2[i-1] << 1) % MOD;
	C[0][0] = 1; for (int i=1;i<N;i++) {
		C[0][i] = 1; for (int j=1;j<=i;j++) {
			C[j][i] = (C[j-1][i-1] + C[j][i-1]) % MOD;
		}
	} 
	int a=read(),b=read(),c=read(),d=read();
	if (a + b == 0) f[1] = 1;
	else for (int i=1;i<=a+b;i++) f[i] = solve(a, b, i);
	if (c + d == 0) g[1] = 1;
	else for (int i=1;i<=c+d;i++) g[i] = solve(c, d, i);
	for (int i=1;i<=a+b;i++) (vout += f[i] * (g[i-1] + 2ll * g[i] + g[i+1]) % MOD) %= MOD;
	printf("%d\n",vout);
	return 0;
}

【TopCoder】[TCO2013 2A] The Powers

相关链接

题目传送门:https://arena.topcoder.com/#/u/practiceCode/15610/27970/12185/1/316868
数据生成器:http://paste.ubuntu.com/24089972/
官方题解:https://apps.topcoder.com/wiki/display/tc/TCO+2013+Round+2A
神犇题解:http://picks.logdown.com/posts/208980-solutions-to-topcoder-open-2013s-hard-problems

解题报告

我们想办法来统计重复出现的次数,然后用$A \cdot B$减掉这个数就是答案了
那么考虑$x^a=y^b$,我们直接枚举$x,y$肯定会算重,于是我们枚举$\gcd (x,y)$
这样的话,枚举的$\gcd$之间就不会互相影响了

假设当前枚举的$\gcd$为$d$,那么我们就是要统计$(d^i)^a=(d^j)^b$重复了多少
但直接统计比较麻烦,于是我们统计出现的不同个数有多少
于是问题转化为:$i \cdot a$有多少种不同的取值,其中$a \in [1,10^9]$,$i \in [1,\log_d 10^9]$

因为$i$的范围只有不到$30$于是我们就可以用打个表
但打表是不优雅的,于是我们搞一搞优化:

不妨设我们求$x^y \in [B(k-1),Bk]$时的解
不难发现,若$i,j \in [k,\log_d 10^9]$且$i$是$j$的倍数
那么$i$能凑出来的,$j$也能凑出来,于是我们就可以删去$i$
这时我们搞一搞发现:删掉之后剩余元素的个数不超过$15$
于是我们就可以愉快地容斥啦!

这样搞的话,时间复杂度是$O((\sqrt A + 2^{15}) \log^2A )$的
还是跑得挺快的

另外,这题的$A,B$可以出到$10^{18}$去
但是我不会QwQ
这里扔一份代码吧:http://paste.ubuntu.com/24089979/
哪位神犇如果看懂了,给我讲讲可好 _(:з」∠)_

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 32000;

int vis[N],pri[N],tot;
LL ans[40]; 
vector<int> que;

class ThePowers{
	public:
		long long find(LL A, LL B) {
			prework(N-1, B); LL ret = B - 1;
			for (LL r=2,t,vi;r*r<=A;r++) {
				if (judge(r)) { 
					for (t=0,vi=r;vi<=A;t++,vi*=r);
					ret += t * B - ans[t]; 
				}
			} 
			return (LL)A * B - ret;
		}
	private:
		LL DFS(LL t, LL B, LL cnt, LL lcm) {
			if (!t) {
				if (cnt & 1) return (LL)B / lcm;
				else if (cnt) return -(LL)B / lcm;
				else return 0; 
			} else {
				return 
				DFS(t-1, B, cnt, lcm) + 
				DFS(t-1, B, cnt+1, lcm / Gcd(lcm, que[t-1]) * que[t-1]);
			}
		}
		inline void prework(LL n, LL B) {
			for (int i=2;i<=n;i++) {
				if (!vis[i]) vis[i] = i, pri[++tot] = i;
				for (int j=1;j<=tot&&pri[j]*i<=n;j++) {
					vis[i*pri[j]] = pri[j];
					if (i % pri[j] == 0) break;
				}
			}
			for (int i=1;i<30;i++) {
				for (int j=1;j<=i;j++) {
					que.clear();
					for (int k=j,tag=0;k<=i;k++,tag=0) {
						for (int l=que.size()-1;l>=0;l--) 
							if (k % que[l] == 0) 
								{tag = 1; break;}
						if (!tag) que.push_back(k);
					}
					ans[i] += DFS(que.size(), j * B, 0, 1);
					ans[i] -= DFS(que.size(), j * B - B, 0, 1);
				}
					cout<<ans[i]<<endl;;
			}
		}
		inline LL Gcd(LL a, LL b) {
			return b? Gcd(b, a%b): a;	
		}
		inline bool judge(int w) {
			int GCD = 0;
			for (int t=0,p=vis[w];w>1;t=0,p=vis[w]) {
				while (w % p == 0) t++, w /= p;
				if (!GCD) GCD = t;
				else GCD = Gcd(GCD, t);
			}
			return GCD == 1;
		}
};

吐槽

今天两道数论题就坑了我一天
上午那个二次剩余的东西,简直没法调试
下午+晚上全™坑这题上面了,也是简直了

【日常小测】题目1

相关链接

题目传送门:http://paste.ubuntu.com/24087405/
数据生成器:http://paste.ubuntu.com/24087409/
std:http://paste.ubuntu.com/24087412/

题目大意

求$\sum_{i=1}^n{f_i^k}$
其中$f_x$为第$x$项$Fibonacci$数
$n \le 1e18, k \le 1e5$

解题报告

之前鏼爷讲过二次剩余,于是看到这个模数就知道方向了
在暴力求出$\sqrt 5$的二次剩余后,我们可以推出答案长这样
$$\sum_{j=0}^{k}{(-1)^{k-j} \cdot C_k^j \sum_{i-1}^n{(A^jB^{k-j})^i}}$$
于是我们搞一搞组合数,逆元什么的这题就做完啦!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int MOD = 1000000009;
const int A = 691504013;
const int B = 308495997;
const int REV_SQRT_FIVE = 276601605;
const int N = 100009;

int k,vout,PA[N],PB[N];

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline int Pow(int w, LL t) {
	int ret = 1;
	for (;t;t>>=1,w=(LL)w*w%MOD) 
		if (t & 1) ret = (LL)ret * w % MOD;
	return ret;
}

inline int Mul(int a, LL b) {
	int ret = 0;
	for (;b;b>>=1,(a<<=1)%=MOD)
		if (b & 1) (ret += a) %= MOD;
	return ret;
}

int main() {
	freopen("A.in","r",stdin); 
	freopen("A.out","w",stdout); 
	LL n; cin>>n; k = read();
	PA[0] = PB[0] = 1;
	for (int i=1;i<=k;i++) {
		PA[i] = (LL)PA[i-1] * A % MOD;
		PB[i] = (LL)PB[i-1] * B % MOD;
	}
	for (int i=0,c=1,v;i<=k;i++) {
		v = (LL)PA[i] * PB[k-i] % MOD; 
		if (v == 1) v = Mul(v, n);
		else v = ((LL)Pow(v, n+1) - v) * Pow(v-1, MOD-2) % MOD;
		if (k-i & 1) (vout -= (LL)c * v % MOD) %= MOD;
		else (vout += (LL)c * v % MOD) %= MOD;
		c = ((LL)c * (k - i) % MOD) * Pow(i+1, MOD-2) % MOD; 
	} 
	printf("%d\n",((LL)vout*Pow(REV_SQRT_FIVE,k)%MOD+MOD)%MOD);
	return 0;
}

【BZOJ 4641】基因改造

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4641
神犇题解:http://blog.csdn.net/neither_nor/article/details/52218134

解题报告

据说可以KMP,具体可以看这里:传送门
不过,考虑一个更优美的做法:

这题显然可以转化为带通配符的字符串匹配问题
于是我们再撸一发FFT就可以了!

什么?复杂度过不去?
那我也没办法 _(:з」∠)_

【HDU 5194】DZY Loves Balls

相关链接

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5194
中文题面:http://blog.csdn.net/u014086857/article/details/44724335

解题报告

这题主要就是用到一个“期望的可加性”吧!
什么是可加性呢?

期望的和,等于和的期望

这里不需要期望之间相互独立
证明的话,你可以参见这里:http://blog.csdn.net/grooowing/article/details/45000205

于是这题我们分开考虑每一个位置出现01的概率就好了
于是推一推式子发现答案至于n,m相关,于是写一个gcd就好了

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

int GCD(int x, int y) {
	return y? GCD(y, x % y): x;
} 

int main() {
	for (int n,m,gcd;~scanf("%d%d",&n,&m);) {
		gcd = GCD(n*m, n+m);
		printf("%d/%d\n", n*m/gcd, (n+m)/gcd);
	}
	return 0;
}

【51NOD 1135】原根

相关链接

题目传送门:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1135
神犇题解Ⅰ:http://blog.csdn.net/u013486414/article/details/47781857
神犇题解Ⅱ:http://littleclown.github.io/2016/05/16/Study-Math-Mod-Root/

解题报告

就是一个求原根的模板题
相关的证明在这里:http://blog.csdn.net/zhang20072844/article/details/11541133

值得注意的是,验证原根现在可以做到$O(logn)$了
不过找原根的复杂度似乎没有比较紧的上界?
wiki上给出了这样一个上界:$O(n^{0.499})$,但$n$要大于$e^{e^{24}}$,所以对于OI题没什么用QwQ
不过一般来讲,质数的原根都比较小,就直接枚举加验证啦!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
inline int read() {
    char c=getchar(); int f=1,ret=0;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret * f;
}
 
inline int Get_Primitive_Root(int w) {
    vector<int> pri;
    for (int i=2,lim=ceil(sqrt(w)),cur=w-1;i<lim;i++) {
        if (cur % i == 0) {
            pri.push_back(i);
            while (cur % i == 0) 
                cur /= i;
        }
        if (cur > 1 && i == lim - 1) 
            pri.push_back(cur);
    }
    static auto Pow = [](int w, int t, int MOD) {
        int ret = 1;
        for (;t;t>>=1,w=(LL)w*w%MOD) 
            if (t & 1) ret = (LL)ret * w % MOD;
        return ret;
    };
    if (!pri.size()) return 2;
    for (int i=2;i<=w;i++) {
        for (int j=pri.size()-1;~j;j--) {
            if (Pow(i,w/pri[j],w) == 1) break;
            if (!j) return i;
        }
    }
}
 
int main() {
    printf("%d\n",Get_Primitive_Root(read())); 
    return 0;
}

【BZOJ 3994】[SDOI2015] 约数个数和

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3994
数据生成器:http://paste.ubuntu.com/23992676/
神犇题解:https://blog.sengxian.com/solutions/bzoj-3994

解题报告

这题要用到一个结论: $d(xy) = \sum\limits_{i|x} {\sum\limits_{j|y} {[\gcd (i,j) = 1]} } $
这个结论似乎没有办法从意义上去推导,证明也只能是展开后发现刚好相等
但这个结论前不久集训的时候就考过一次,然而做这题的时候一点印象都没有
我要是下一次还记不住这个结论就直播吃键盘!

好了现在开始说正解
知道上面的结论以后,答案就成了 $\sum\limits_{k = 1}^n {\mu (k)\sum\limits_{i = 1}^{\frac{n}{k}} {d(i)\sum\limits_{j = 1}^{\frac{m}{k}} {d(j)} } } $
然后设$f(i)$为除数函数的前缀和,答案就变成了: $\sum\limits_{k = 1}^n {\mu (k)f(\frac{n}{k})f(\frac{m}{k})} $
于是直接下底函数分块就可以啦!

Code

除数函数可以线筛,不过偷懒写的欧拉筛法

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 50000+9; 

int n,m,tot,pri[N],mu[N],d[N],sum[N],f[N];
bool vis[N]; LL vout;

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline void prework() {
	mu[1] = 1;
	for (int i=2;i<N;i++) {
		if (!vis[i]) pri[++tot] = i, mu[i] = -1;
		for (int j=1;j<=tot&&pri[j]*i<N;j++) {
			vis[i*pri[j]] = 1;
			if (i % pri[j]) mu[i*pri[j]] = -mu[i];
			else {mu[i*pri[j]] = 0; break;}
		}
	}
	for (int i=1;i<N;i++) 
		for (int j=i;j<N;j+=i) 
			d[j]++;
	for (int i=1;i<N;i++) {
		f[i] = f[i-1] + d[i];
		sum[i] = sum[i-1] + mu[i];
	}
}	

int main() {
	prework();
	for (int T=read();T;T--,vout=0) {
		n = read(); m = read();
		if (m > n) swap(n, m);
		for (int l=1,r;l<=m;l=r+1) {
			r = min(n / (n / l), m / (m / l));
			vout += (LL)(sum[r] - sum[l-1]) * f[n/l] * f[m/l];
		}
		printf("%lld\n",vout);
	}
	return 0;
}