Bzoj3992:[SDOI2015]序列统计

Posted Cyhlnj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Bzoj3992:[SDOI2015]序列统计相关的知识,希望对你有一定的参考价值。

题面

Bzoj

Sol

pts 1

大暴力很简单,\\(f[i][j]\\)表示到第\\(i\\)个位置,前面积的模为\\(j\\)的方案
然后可以获得\\(10\\)分的好成绩

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int Zsy(1004535809);
const int _(8010);

IL ll Input(){
    RG ll x = 0, z = 1; RG char c = getchar();
    for(; c < \'0\' || c > \'9\'; c = getchar()) z = c == \'-\' ? -1 : 1;
    for(; c >= \'0\' && c <= \'9\'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x * z;
}

int n, m, x, S, a[_], f[2][_];

IL void Up(RG int &x, RG int y){
    x += y;
    if(x >= Zsy) x -= Zsy;
}

int main(RG int argc, RG char* argv[]){
    n = Input(), m = Input(), x = Input(), S = Input();
    for(RG int i = 1; i <= S; ++i) a[i] = Input() % m;
    f[0][1] = 1;
    for(RG int i = 0; i < n; ++i)
        for(RG int j = 0; j < m; ++j){
            RG int lst = i & 1;
            if(!f[lst][j]) continue;
            for(RG int k = 1; k <= S; ++k)
                Up(f[lst ^ 1][1LL * j * a[k] % m], f[lst][j]);
            f[lst][j] = 0;
        }
    printf("%d\\n", f[n & 1][x]);
    return 0;
}

pts 2

你会发现所有的转移都是一样的
然后你看到\\(n\\)的范围就想到了快速幂
那么把\\(f\\)设成一维,\\(f[i]\\)表示积的模为\\(i\\)的方案数
当前状态下,假设做到了第\\(k\\)个位置
那么此时的\\(f\\)数组自己和自己组合就可以转移到第\\(2k\\)个位置的\\(f\\)
那么就可以用快速幂一样的方式来优化时间
\\(60\\)

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int Zsy(1004535809);
const int _(8010);

IL ll Input(){
    RG ll x = 0, z = 1; RG char c = getchar();
    for(; c < \'0\' || c > \'9\'; c = getchar()) z = c == \'-\' ? -1 : 1;
    for(; c >= \'0\' && c <= \'9\'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x * z;
}

int n, m, x, S, f[_], g[_], h[_];

IL void Up(RG int &x, RG int y){
    x += y;
    if(x >= Zsy) x -= Zsy;
}

int main(RG int argc, RG char* argv[]){
    n = Input(), m = Input(), x = Input(), S = Input();
    for(RG int i = 1; i <= S; ++i) ++f[Input() % m];
    h[1] = 1;
    for(RG int i = n; i; i >>= 1){
        if(i & 1){
            for(RG int j = 0; j < m; ++j)
                for(RG int k = 0; k < m; ++k)
                    Up(g[1LL * j * k % m], 1LL * f[j] * h[k] % Zsy);
            for(RG int j = 0; j < m; ++j) h[j] = g[j], g[j] = 0;
        }
        for(RG int j = 0; j < m; ++j)
            for(RG int k = 0; k < m; ++k)
                Up(g[1LL * j * k % m], 1LL * f[j] * f[k] % Zsy);
        for(RG int j = 0; j < m; ++j) f[j] = g[j], g[j] = 0;
    }
    printf("%d\\n", h[x]);
    return 0;
}

pts 3

此时的复杂度为\\(O(m^2log\\ n)\\)
那个\\(log\\)显然去不掉
只能优化那个\\(m^2\\)
注意到每次都是\\(i, j\\)转移到\\(i*j\\)
如果它是\\(i, j\\)转移到\\(i+j\\)就好了
怎么转化?
你知道可以用指数运算转化
又注意到\\(x>=1\\)\\(m为质数\\)
还是不会

这个时候就可以去orz 题解辣
原根!!(大雾)
原根能干什么?
注意到原根的性质:
\\(g\\)为质数\\(p\\)的原根
那么\\(g\\)\\(0\\)\\(p-2\\)的幂在模\\(p\\)的意义下一定不重不漏对应着\\(1\\)\\(p-1\\)这些数
而奇质数\\((>2)\\)一定有原根
并且\\(x>0\\),那么把积取模后为\\(0\\)的丢掉就好了
那么我们把转移时的\\(i, j\\)中的\\(i, j\\)映射成原根的幂的指数\\(i\', j\'\\)
那么转移的\\(i*j\\)就变成了指数运算\\(i\'+j\'\\)(注意这里都是模\\(m\\)意义下的)
然后就可以愉快的\\(NTT\\)
还要注意\\(NTT\\)完后得到的数组是比原来长的
要把它累加到下标对\\(m-1\\)取模后的地方
求原根

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int Zsy(1004535809);
const int Phi(1004535808);
const int G(3);
const int _(20010);

IL ll Input(){
    RG ll x = 0, z = 1; RG char c = getchar();
    for(; c < \'0\' || c > \'9\'; c = getchar()) z = c == \'-\' ? -1 : 1;
    for(; c >= \'0\' && c <= \'9\'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x * z;
}

int n, m, x, S, f[_], g[_];
int A[_], B[_], N = 1, l, r[_];
int mul[_], mp[_];

IL int Pow(RG ll x, RG ll y, RG int zsy){
	RG ll ret = 1;
	for(; y; y >>= 1, x = x * x % zsy)
		if(y & 1) ret = ret * x % zsy;
	return ret;
}

IL int Pr_Rt(RG int P){
	RG int phi = P - 1;
	for(RG int i = 2; i * i <= phi; ++i)
		if(phi % i == 0){
			while(phi % i == 0) phi /= i;
			mul[++mul[0]] = i;
		}
	if(phi > 1) mul[++mul[0]] = phi;
	for(RG int i = 2; ; ++i){
		RG int flg = 0;
		for(RG int j = 1; j <= mul[0]; ++j)
			if(Pow(i, (P - 1) / mul[j], P) == 1){
				flg = 1;
				break;
			}
		if(!flg) return i;
	}
	return 233;
}

IL void Prepare(){
	for(RG int i = m + m - 2; N <= i; N <<= 1) ++l;
	for(RG int i = 0; i < N; ++i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
	RG int rt = Pr_Rt(m);
	for(RG int i = 0; i < m - 1; ++i) mp[Pow(rt, i, m)] = i;
}

IL void NTT(RG int *P, RG int opt){
	for(RG int i = 0; i < N; ++i) if(i < r[i]) swap(P[i], P[r[i]]);
	for(RG int i = 1; i < N; i <<= 1){
		RG int W = Pow(G, Phi / (i << 1), Zsy);
		if(opt == -1) W = Pow(W, Zsy - 2, Zsy);
		for(RG int j = 0, p = i << 1; j < N; j += p){
			RG int w = 1;
			for(RG int k = 0; k < i; ++k, w = 1LL * w * W % Zsy){
				RG int X = P[k + j], Y = 1LL * w * P[k + j + i] % Zsy;
				P[k + j] = (X + Y) % Zsy, P[k + j + i] = (X - Y + Zsy) % Zsy;
			}
		}
	}
	if(opt == -1){
		RG int inv = Pow(N, Zsy - 2, Zsy);
		for(RG int i = 0; i < N; ++i) P[i] = 1LL * P[i] * inv % Zsy;
	}
}

IL void Mul(RG int *a, RG int *b){
	for(RG int i = 0; i < N; ++i) A[i] = a[i], B[i] = b[i], a[i] = 0;
	NTT(A, 1); NTT(B, 1);
	for(RG int i = 0; i < N; ++i) A[i] = 1LL * A[i] * B[i] % Zsy;
	NTT(A, -1);
	for(RG int i = 0; i < N; ++i) (a[i % (m - 1)] += A[i]) %= Zsy;
}

int main(RG int argc, RG char* argv[]){
	n = Input(), m = Input(), x = Input(), S = Input();
	Prepare();
	for(RG int i = 1; i <= S; ++i){
		RG int a = Input() % m;
		if(a) ++f[mp[a]];
	}
	g[mp[1]] = 1;
	for(; n; n >>= 1, Mul(f, f)) if(n & 1) Mul(g, f);
	printf("%d\\n", g[mp[x]]);
    return 0;
}

以上是关于Bzoj3992:[SDOI2015]序列统计的主要内容,如果未能解决你的问题,请参考以下文章

[BZOJ3992][SDOI2015]序列统计

[BZOJ3992][SDOI2015]序列统计

BZOJ 3992: [SDOI2015]序列统计 NTT+快速幂

BZOJ 3992 [SDOI2015]序列统计

Bzoj3992:[SDOI2015]序列统计

[BZOJ3992][SDOI2015]序列统计(DP+原根+NTT)