【日常小测】Hope

题目大意

给定排列的长度$n(n\le10^5)$和常数$k(k\le10^5)$
对于单个排列$A$中的每$i$位:我们连一条从$i$出发,到其右边第一个比它大的位,如果没有就不连
定义$A$的花费为 $\sum$其中每一个连通块的大小$^k$
询问所有长度为$n$的排列的花费和$(\bmod 998244353)$

解题报告

设$f_i$为排列长度为$i$的答案,假设我们已经求出了$f_{i-1}$
我们考虑枚举$i$放在哪里,那么$i$之前的数全部连向$i$,之后的数是递归一个子问题
于是我们可以写出暴力的$DP$:${f_i} = \sum\limits_{j = 1}^i {{j^k}(j – 1)!C_{i – 1}^{j – 1}{f_{i – j}}} $
我们化一化式子可以得到$\frac{f_i}{(i-1)!}=\sum\limits_{j=1}^{i}{j^k \cdot \frac{f_{i-j}}{(i-j)!}}$
这显然是一个卷积的形式,于是可以用$NTT$优化

另外,对于$f_i$来讲,$f_{1 \sim i-1}$都会对它有贡献
那么这有是一个典型的$CDQ$分治的模型,于是我们还需要套上一个$CDQ$分治

在另外的话,我们回顾一下推出暴力$DP$的过程
不难发现我们是以最大值的插入作为突破口
那么这种题目与最大值相关的问题,这应该算是套路之一吧!
例如:HDU 5575

—————————— UPD 2017.3.17 ——————————
Claris说这题在OEIS上能找到$O(n)$的递推式 QwQ
Claris太强啦! _(:з」∠)_

【51NOD 1195】斐波那契数列的循环节

相关链接

题目传送门:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1195
二次剩余相关:http://staff.ustc.edu.cn/~lvmin05/jssl/Chap3%20%B6%FE%B4%CE%CA%A3%D3%E0.ppt
二次剩余相关:http://pan.baidu.com/s/1geBDodH
神犇题解:http://blog.csdn.net/acdreamers/article/details/10983813

解题报告

我们分三步走:

  1. 将模数质因数分解
  2. 对于每一个$p_i^m$我们计算其循环节$l_i$
  3. 将所有$l_i$取$lcm$

显然第一步和第三步没有难度
我们考虑第二步,这里有一个神奇得结论:
若$G(p)$为Fibonacci数列在模素数$p$意义下的最短循环节
那么$\bmod p^m$的最短循环节为$G(p)\cdot p^{m-1}$

现在问题转化为求$G(p)$,这里又有一个神奇的结论:
若$5$为$p$的二次剩余,则$G(p)$为$p-1$的因子。否则为$2(p+1)$的因子
然后我们就可以通过从小到大枚举因数+$O(\log n)$判断的方法得到答案了

另外的话,这个算法似乎没有比较准确的复杂度。不过我们可以假装他是$O(\sqrt{n} \log n)$的
再另外的话,这份代码会$T$,但我又不想优化了,反正这个东西也不可能考 QwQ

Code

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

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;
}

class Fibonacci{
	int ans[4],tra[4],MOD;
	public:
		inline bool cal(int t, int mod) {
			ans[1] = tra[1] = tra[2] = tra[3] = 1;
			ans[0] = ans[2] = ans[3] = tra[0] = 0; MOD = mod;
			Pow(tra, tra, t - 2); Mul(ans, ans, tra);
			return ans[1] == 1 && !ans[0];
		}
	private:
		inline void Pow(int *ans, int *a, int t) {
			static int ret[4],cur[4]; 
			ret[0]=ret[3]=1; ret[1]=ret[2]=0;
			memcpy(cur,a,sizeof(cur));
			for (;t;t>>=1,Mul(cur,cur,cur))
				if (t&1) Mul(ret,cur,ret);
			memcpy(ans, ret, sizeof(ret));
		}
		inline void Mul(int *ans, int *a, int *b) {
			static int ret[4];
			ret[0] = ((ll)a[0] * b[0] + (ll)a[1] * b[2]) % MOD;
			ret[1] = ((ll)a[0] * b[1] + (ll)a[1] * b[3]) % MOD;
			ret[2] = ((ll)a[2] * b[0] + (ll)a[3] * b[2]) % MOD;
			ret[3] = ((ll)a[2] * b[1] + (ll)a[3] * b[3]) % MOD;
			memcpy(ans, ret, sizeof(ret));
		}
}fib;

ll GCD(ll a, ll b) {
	return b? GCD(b, a%b): a;
}

int 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;
}

inline ll G(ll p) {
	if (p == 2) return 3;
	else if (p == 3) return 8;
	else if (p == 5) return 20;
	static ll LIM = 0; stack<int> stk;
	if (Pow(5, (p-1)/2, p) == 1) LIM = p - 1;
	else LIM = p + 1 << 1; 
	for (int i=1;i*i<=LIM;i++) {
		if (LIM % i == 0) {
			if (fib.cal(i+2, p)) return i;
			else stk.push(LIM / i);
		}
	}
	for (int ret;!stk.empty();) {
		ret = stk.top(); stk.pop();
		if (fib.cal(ret+2, p)) return ret;
	}
}

inline ll cal(int a, int b) {
	static ll INF = 4e18, ret;
	for (ret=G(a);b>1;b--) ret = ret * a;
	return ret;
}

inline ll solve(int mod) {
	static const int N = 1e5;
	static int pri[N],cnt[N],tot;
	if (mod == 1) return 1; tot = 0; 
	for (int i=2;i*i<=mod;i++) {
		if (mod % i == 0) {
			pri[++tot] = i; cnt[tot] = 0;
			for (;mod%i==0;mod/=i) ++cnt[tot];
		}
	} if (mod>1) pri[++tot] = mod, cnt[tot]=1;
	ll ret = 1;
	for (int i=1,tmp;i<=tot;i++) {
		tmp = cal(pri[i], cnt[i]);
		ret = ret / GCD(ret, tmp) * tmp;
	} return ret;
}

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

【日常小测】超级绵羊异或

题目大意

求$(b)\oplus(b+a)\oplus(b+2a)\oplus\cdots\oplus(b+(n-1)a)$
其中$n,a,b\le10^9$

解题报告

因为是二进制,所以我们每一位分开考虑
又因为数$a$二进制下第$k$位的奇偶性与$a>>(k-1)$的奇偶性相同
所以对于第$k$位,我们实际是要求$\sum\limits_{x=0}^{n-1}{\left\lfloor {\frac{{ax + b}}{{{2^k}}}} \right\rfloor}$,我们将其一般化:求解$f(a,b,c,n)=\sum\limits_{x=0}^{n}{\left\lfloor {\frac{{ax + b}}{{{c}}}} \right\rfloor}$

设$s(x)=\sum\limits_{i=1}^{x}{i}$。为了只讨论$a,b<c$的情况,我们先来预处理一下:

  1. 若$a \ge c$那么显然$f(a,b,c,n) = f(a \% c,b,c,n) + \lfloor\frac{a}{c}\rfloor \cdot s(n)$
  2. 若$b \ge c$那么显然$f(a,b,c,n) = f(a,b\%c,c,n) + \lfloor\frac{b}{c}\rfloor \cdot n$

之后我们就可以施展膜法了:
设$m=\lfloor\frac{an+b}{c}\rfloor$
那么原式$=\sum\limits_{x=0}^{n}{\sum\limits_{y=0}^{m}{[y<\lfloor\frac{ax+b}{c}\rfloor]}}$
把$y<\lfloor\frac{ax+b}{c}\rfloor$提出来,可以化简:
$y<\lfloor\frac{ax+b}{c}\rfloor = c(y+1) \le ax+b = cy+c-b-1<ax=x>\lfloor\frac{cy+c-b-1}{a}\rfloor$
那么原式$=\sum\limits_{y=0}^{m}{\sum\limits_{x=0}^{n}{[x>\lfloor\frac{cy+c-b-1}{a}\rfloor]}}=n(m+1)-\sum\limits_{y=0}^m{\lfloor\frac{cy+c-b-1}{a}\rfloor}$
相当于$f(a,b,c,n)=n(m+1)-f(c,c-b\%c-1,a\%c,m)$
窝萌发现,这货的形式简直和辗转相处搞$gcd$一模一样
于是这货的复杂度也是$O(\log n)$的

当然这题还有一种数形结合的推法
推出来式子略有不同,不过时间复杂度仍然为$O(\log n)$
不过本文这种推法似乎更优?据敦敦敦讲,这货可以推广到高维
但我不会 QwQ

Code

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

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;
}

LL cal(LL a, LL b, LL c, LL n) { 
	if (a >= c) return (((n-1)*n/2)*(a/c)&1) ^ cal(a%c, b, c, n);
	if (b >= c) return (n * (b/c) & 1) ^ cal(a, b%c, c, n);
	if (!a) return (b/c) * n & 1;
	LL nw = (a * (n - 1) + b) / c; 
	return ((n-1) * nw & 1) ^ cal(c, c - b - 1, a, nw);
}

int main() {
	for (int T=read(),a,b,n;T;T--) {
		n = read(); b = read(); 
		a = read(); LL c = 1, ans = 0;
		if (!b) {printf("%d\n",(n&1)?a:0); continue;} 
		for (int i=0;i<=60;i++,c<<=1) 
			ans += cal(a, b, c, n) * c;
		printf("%lld\n",ans);
	}
	return 0;
}

【BZOJ 3992】[SDOI2015] 序列统计

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3992
Latex题面:http://paste.ubuntu.com/23997878/
数据生成器:http://paste.ubuntu.com/24006205/
神犇题解:http://www.cnblogs.com/gromah/p/4431016.html
NTT相关:http://blog.miskcoo.com/2015/04/polynomial-multiplication-and-fast-fourier-transform#i-13

解题报告

这题$O(m^3logn)$的基于矩阵快速幂的算法大家都会吧?
然而只能通过 $60\% $ 的数据 QwQ

然后就需要一点黑科技了
考虑模数这么奇怪,于是可能是用元根来搞一搞
然后我们发现,我们可以用元根的幂次来表示 $0 \sim m – 1$ 的每一个数
于是我们就可以把乘法换成幂次的加法,于是就可以搞NTT了!
于是用NTT来搞生成函数,复杂度:$O(nmlogm)$
然后再套上一开始讲的快速幂,那么就是$O(mlogmlogn)$的复杂度啦!

Code

话说这似乎是第一次撸$NTT$呢!
还有一点小激动啊!
不过这一份代码封装太多了,好慢啊 QwQ

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

const int N = 9000;
const int M = N << 2;
const int MOD = 1004535809;

int l,n,m,x,pos[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, 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;
}

inline int Get_Primitive_Root(int w) {
	vector<int> pri; int cur = w - 1;
	for (int i=2,lim=ceil(sqrt(w));i<=cur&&i<=lim;i++) {
		if (cur % i == 0) {
			pri.push_back(i);
			while (cur % i == 0) cur /= i;
		}
	}
	if (cur > 1) pri.push_back(cur);
	if (pri.empty()) return 2;  
	for (int i=2;;i++) {
		for (int j=pri.size()-1;j>=0;j--) {
			if (Pow(i, w/pri[j], w) == 1) break;
			if (!j) return i;
		}
	}
}

struct Sequence{
	int a[M],POS[M],len;
	inline Sequence() {}
	inline Sequence(int l) {
		memset(a,0,sizeof(a));
		len = l; a[0] = 1;
	}
	inline Sequence(Sequence &B) {
		memcpy(this, &B, sizeof(B));
		len=B.len;
	}
	inline void NTT(int f) {
		static const int G = Get_Primitive_Root(MOD);
		int l = -1; for (int i=len;i;i>>=1) l++;
		if (!POS[1]) {
			for (int i=1;i<len;i++) { 
				POS[i] = POS[i>>1]>>1;
				if (i & 1) POS[i] |= 1 << l-1;
			} 
		}
		for (int i=0;i<len;i++) if (POS[i] > i) 
			swap(a[i], a[POS[i]]);
		for (int seg=2;seg<=len;seg<<=1) {
			int wn = Pow(G, MOD/seg, MOD);
			if (f == -1) wn = Pow(wn, MOD-2, MOD);
			for (int i=0;i<len;i+=seg) {
				for (int k=i,w=1;k<i+seg/2;k++,w=(LL)w*wn%MOD) {
					int tmp = (LL)w * a[k+seg/2] % MOD;
					a[k+seg/2] = ((a[k] - tmp) % MOD + MOD) % MOD;
					a[k] = (a[k] + tmp) % MOD;
				}
			}
		}
		if (f == -1) { 
			for (int i=0,rev=Pow(len,MOD-2,MOD);i<len;i++) 
				a[i] = (LL)a[i] * rev % MOD;
		}
	}
	inline void Multiply(Sequence B) {
		NTT(1); B.NTT(1);
		for (int i=0;i<len;i++)
			a[i] = (LL)a[i] * B.a[i] % MOD;
		NTT(-1); 
		for (int i=m-1;i<len;i++) 
			(a[i-m+1] += a[i]) %= MOD, a[i] = 0;
	} 
	inline void pow(int t) {
		Sequence ret(len),w(*this);
		for (;t;t>>=1,w.Multiply(w)) 
			if (t & 1) ret.Multiply(w);
		memcpy(this, &ret, sizeof(ret));
	}
}arr;

int main() {
	l = read(); m = read();
	x = read() % m; n = read();
	int PRT = Get_Primitive_Root(m);
	for (int cur=1,i=0;i<m-1;i++,cur=cur*PRT%m) pos[cur] = i;
	for (int i=0,t;i<n;i++) t=read(), t?arr.a[pos[t%m]]++:0;
	int len = 1; while (len < m) len <<= 1;
	arr.len = len << 1; arr.pow(l); 
	printf("%d\n",(arr.a[pos[x]]+MOD)%MOD);
	return 0;
}

【BZOJ 2821】作诗(Poetize)

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2821
神犇题解Ⅰ:http://blog.csdn.net/PoPoQQQ/article/details/40372067
神犇题解Ⅱ:http://www.cnblogs.com/JSZX11556/p/5031198.html

解题报告

这货最开始想用高级数据结构来做
想了半个小时,一点思路都没有 QwQ

好吧,看完题解发现全™写的是分块
不过这题的姿势又有一点不一样

一般的分块是:

大块直接扫一遍,顺便统计答案

这题的分块得预处理块与块之间的关系

$ f[i][j]$ 表示第i个大块到第j个大块的答案

似乎这样的复杂度才是对的
时间复杂度: $ O({n^{1.5}}\log (n))$

【日常小测】生物进阶

相关链接

题目传送门:http://oi.cyo.ng/wp-content/uploads/2016/12/26312367.pdf
官方题解:http://oi.cyo.ng/wp-content/uploads/2016/12/324324132.pdf

解题报告

之前鏼爷讲课的时候讲过了
于是这一次直接开始写了

就是每种字母都分开做一遍
符合条件/是该字母就置为1
否则置为0,之后FFT就好
似乎NTT就可以做了?然而不会 QwQ

Code

#include<bits/stdc++.h>
#define E complex<double>
using namespace std;

const int N = 1100000+9;
const double EPS = 1e-3;
const double PI = acos(-1);

int n,m,K,stp,len,a1[N],a2[N],sum[N];
char pat[N]; int vout[N],pos[N];
complex<double> s1[N],s2[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 prework() {
	for (int tmp=1;tmp<=len;tmp<<=1,stp++);
	len = (1<<stp);
	for (int i=1,hig=1<<stp-1;i<=len;i++) {
		pos[i] = pos[i>>1] >> 1;
		if (i & 1) pos[i] |= hig;
	}
}

inline void FFT(complex<double> *arr, int t) {
	for (int i=0;i<len;i++) 
		if (pos[i] < i) swap(arr[pos[i]], arr[i]);
	for (int i=1,num=2;i<=stp;i++,num<<=1) {
		complex<double> wn(cos(2*PI/num),sin(t*2.0*PI/num));
		for (int j=0;j<len;j+=num) {
			complex<double> w(1,0),buf;
			for (int i=j,lim=num/2+j;i<lim;i++,w*=wn) {
				buf = w * arr[i+num/2];
				arr[i+num/2] = arr[i] - buf;
				arr[i] += buf;
			}
		}
	}
	if (!~t) for (int i=0;i<len;i++)
		s1[i].real() /= len;
}

int main() {
	freopen("biology.in","r",stdin);
	freopen("biology.out","w",stdout);
	n = read(); m = read(); 
	K = read(); len = n + m;
	scanf("%s",pat);
	for (int i=0;i<n;i++) {
		if (pat[i] == 'A') a1[i] = 1;
		else if (pat[i] == 'C') a1[i] = 2;
		else if (pat[i] == 'G') a1[i] = 3;
		else if (pat[i] == 'T') a1[i] = 4;
	}
	scanf("%s",pat);
	for (int i=0;i<m;i++) {
		if (pat[i] == 'A') a2[i] = 1;
		else if (pat[i] == 'C') a2[i] = 2;
		else if (pat[i] == 'G') a2[i] = 3;
		else if (pat[i] == 'T') a2[i] = 4;
	}
	
	prework();
	for (int j=1;j<=4;j++) {
		memset(s1,0,sizeof(s1));
		memset(s2,0,sizeof(s2));
		for (int i=0;i<n;i++) 
			sum[i] = (i>0?sum[i-1]:0) + (a1[i] == j);
		for (int i=0;i<n;i++) 
			s1[i].real() = (sum[min(n-1,i+K)]!=((i-K-1)>=0?sum[i-K-1]:0));
		for (int i=0;i<m;i++) 
			s2[i].real() = (a2[i] == j);
		for (int l=0,r=m-1;l<r;l++,r--) 
			swap(s2[l], s2[r]);
		
		FFT(s1,1); FFT(s2,1);
		for (int i=0;i<len;i++)
			s1[i] = s1[i] * s2[i];
		FFT(s1,-1);
		for (int i=1;i<=n;i++)
			vout[i] += s1[i+m-2].real() + EPS;
	}
	int ret = 0;
	for (int i=1;i<=n;i++)
		ret += (vout[i] == m);
	printf("%d\n",ret);
	return 0;
}

【日常小测】Fibonacci

相关连接

题目传送门:http://oi.cyo.ng/wp-content/uploads/2016/10/2016.10.19.pdf
官方题解:http://oi.cyo.ng/wp-content/uploads/2016/10/Fibonacci.pdf

解题报告

这个题目啊!有一个神奇的结论:\({F_i}|{F_j} \Rightarrow i|j\)
现在让我们来证明这个结论:

不难发现我们只需要证明:\({F_i}|{F_{i \cdot k}}\)
然后有一个明显的结论:fibonacci的通项公式在模意义下仍然适用
于是我们可以将整个数列%上\(F_i\)就可以证明啦!

当然还有一个奇怪的结论:\(\gcd ({f_i},{f_j}) = {f_{\gcd (i,j)}}\)
据说这货证明和上面的类似?

所以说这个题目线筛一下约数个数和约数的平方和就好辣!
另外,我的线筛姿势比较奇怪,不要学我啊!

相关拓展

$Fibonacci$数列还有很多妙妙的性质,以下列举一点:

  1. $gcd(f_n,f_m)=f_{gcd(n,m)}$
  2. ${f_n}^2+{f_{n-1}}^2 = f{2n+2}$
  3. $f_n \cdot (f_{n-1}+f_{n+1}) = f{2n+1}$
  4. 如果$f_k$能被$x$整除,则$f_{ki}$都可以被$x$整除

Code

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

const int N = 10000000+9;
const int MX = 10000000;
const int MOD = 1000000007;

int f1[N],f2[N],n,A,B,C,W,vout1,vout2;
int tot,pri[N],top[N],REV[50];
short vis[N],MN[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;
	while (t) {
		if (t & 1) ret = (LL)ret * w % MOD;
		w = (LL)w * w % MOD; t>>= 1;
	}
	return ret;
}


inline void prework(){
	REV[0] = 1;
	for (int i=1;i<=30;i++) {
		REV[i] = pow(i,MOD-2);
	}
	
	f1[1] = 1; f2[1] = 1;
	for (int i=2;i<=MX;i++) {
		if (!vis[i]) {
			pri[++tot] = i;
			f1[i] = 2; 
			f2[i] = (1 + (LL)i * i) % MOD;
			MN[i] = 1; top[i] = (LL)i * i % MOD;
		}
		for (int j=1,to,sqr;j<=tot&&i*pri[j]<=MX;j++) {
			vis[to = i*pri[j]] = 1;
			sqr = (LL)pri[j] * pri[j] % MOD;
			if (i % pri[j]) {
				f1[to] = f1[i] * 2 % MOD;
				f2[to] = f2[i] * (1LL + sqr) % MOD;
				MN[to] = 1; 
				top[to] = (LL)f2[i] * sqr % MOD;
			} else {
				MN[to] = MN[i] + 1; 
				top[to] = (LL)top[i] * sqr % MOD;
				f1[to] = (f1[i] + (LL)f1[i]*REV[MN[i]+1]) % MOD;
				f2[to] = (f2[i] + top[to]) % MOD;
				break;
			}	
		}
	}
	for (int i=3;i<=MX;i++) {
		if (i % 2) {
			f1[i] += 1;
			f2[i] += 4;
		}
	}
}

int main(){
	freopen("fibonacci.in","r",stdin);
	freopen("fibonacci.out","w",stdout);
	n = read(); W = read(); 
	A = read(); B = read(); C = read();
	prework();
	
	for (int i=1;i<=n;i++,W=((LL)W*A+B)%C+1) {
		vout1 += f1[W]; vout2 += f2[W];
		vout1 %= MOD; vout2 %= MOD;
	}
	printf("%d %d\n",vout1,vout2);
	return 0;
}