[SDOI2013]随机数生成器 题解
题目地址:ė …
May all the beauty be blessed.
题目地址:洛谷:【P3321】[SDOI2015]序列统计 – 洛谷、BZOJ:Problem 3992. — [SDOI2015]序列统计
小C有一个集合S,里面的元素都是小于M的非负整数。他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S。小C用这个生成器生成了许多这样的数列。但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中所有数的乘积mod M的值等于x的不同的数列的有多少个。小C认为,两个数列{Ai}和{Bi}不同,当且仅当至少存在一个整数i,满足Ai≠Bi。另外,小C认为这个问题的答案可能很大,因此他只需要你帮助他求出答案mod 1004535809的值就可以了。
输入格式:
一行,四个整数,N、M、x、|S|,其中|S|为集合S中元素个数。第二行,|S|个整数,表示集合S中的所有元素。
输出格式:
一行,一个整数,表示你求出的种类数mod 1004535809的值。
输入样例#1:
4 3 1 2 1 2
输出样例#1:
8
参考资料:bzoj 3992: [SDOI2015]序列统计 (NTT+快速幂+DP) – CSDN博客
本题需要的数学姿势:数学笔记:数论(欧拉函数、阶、原根) | KSkun’s Blog
首先,计数自然是一个动态规划,我们考虑设计状态dp[i][j]为选了i个数且结果mod M的值是j,转移如下
dp[i][j] = \sum_{k \in S} dp[i-1][j'] \ (j'k \bmod M = j)
直观上感觉直接做是一个O(n|S|)的,好像只能得10分。
接下来我们要考虑一个问题,模数很特殊,并不是常见的1000000007,而是1004535809,它的原根为3,是常用的NTT模数,难道出题人在指引我们使用NTT?
想用NTT做,首先得搞出卷积,卷积下标是加减的事情没办法像上面那样乘吧。那么考虑利用原根把乘法转换为加法,dp[i][j]表示选到第i个数结果mod M的值为g^j,改变以后的转移方程就成这样了
dp[i][j] = \sum_{g^k \bmod M \in S} dp[i-1][j'] \ ((j' + k) \bmod (M - 1) = j)
或者这样?用f[i]表示g^i是否在集合中。
dp[i][j] = \sum_{k=1}^{M-1} dp[i-1][j-k] \cdot f[k]
咦,后面那部分现在可以卷积了,而且你会发现每一层的转移中f数组都是不变的,也就是说这是个线性变换,参照矩阵快速幂的操作,我们可以做多项式的快速幂,即dp[n] = dp[0] \times f^n。答案便是 dp[n][i] \ (g^i=x) 。
于是我们的思路就很明确了:求原根、算出f数组、多项式快速幂。
没完呢!这里有一个坑点,集合内的元素可以有0,0显然对答案没贡献,所以你要跳过它处理。
// Code by KSkun, 2018/4
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF
: *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
inline LL fpow(LL n, LL k, LL p) {
n %= p;
LL res = 1;
while(k) {
if(k & 1) res = res * n % p;
n = n * n % p;
k >>= 1;
}
return res;
}
const int MAXN = 1 << 15, G = 3, MO = 1004535809;
int n, m, x, s, t;
LL f[MAXN], g[MAXN];
bool vis[MAXN];
LL rev[MAXN], dft1[MAXN], dft2[MAXN], dftr[MAXN];
inline void calrev(int n, int len) {
for(int i = 0; i < n; i++) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
}
}
inline void dft(int n, LL *arr, int f) {
for(int i = 0; i < n; i++) {
if(i < rev[i]) std::swap(arr[i], arr[rev[i]]);
}
for(int i = 1; i < n; i <<= 1) {
LL gn = fpow(G, (MO - 1) / (i << 1), MO);
if(f == -1) gn = fpow(gn, MO - 2, MO);
for(int j = 0; j < n; j += i << 1) {
LL g = 1;
for(int k = 0; k < i; k++) {
LL x = arr[j + k], y = g * arr[j + k + i] % MO;
arr[j + k] = (x + y) % MO;
arr[j + k + i] = ((x - y) % MO + MO) % MO;
g = g * gn % MO;
}
}
}
if(f == -1) {
LL invn = fpow(n, MO - 2, MO);
for(int i = 0; i < n; i++) {
arr[i] = arr[i] * invn % MO;
}
}
}
inline void ntt(LL *a, LL *b) {
memset(dft1, 0, sizeof(dft1));
memset(dft2, 0, sizeof(dft2));
memset(dftr, 0, sizeof(dftr));
int n, len = 0;
for(n = 1; n <= (m - 1) << 1; n <<= 1) len++;
for(int i = 0; i < n; i++) {
dft1[i] = a[i];
dft2[i] = b[i];
}
calrev(n, len);
dft(n, dft1, 1); dft(n, dft2, 1);
for(int i = 0; i < n; i++) {
dftr[i] = dft1[i] * dft2[i] % MO;
}
dft(n, dftr, -1);
for(int i = 0; i < m - 1; i++) {
a[i] = (dftr[i] + dftr[i + m - 1]) % MO;
}
}
inline LL calg(LL n) {
int fact[8005], tot = 0, x = n - 1;
for(int i = 2; i * i <= x; i++) {
if(x % i == 0) {
fact[tot++] = (n - 1) / i;
while(x % i == 0) x /= i;
}
}
if(x > 1) fact[tot++] = (n - 1) / x;
bool success;
for(int i = 2;; i++) {
success = true;
for(int j = 0; j < tot; j++) {
if(fpow(i, fact[j], n) == 1) {
success = false;
break;
}
}
if(success) return i;
}
}
int main() {
n = readint(); m = readint(); x = readint(); s = readint();
for(int i = 1; i <= s; i++) {
t = readint();
vis[t] = 1;
}
int xpow, G = calg(m), gn = 1;
for(int i = 0; i < m - 1; i++) {
if(vis[gn]) f[i] = 1;
if(gn == x) xpow = i;
gn = gn * G % m;
}
g[0] = 1;
for(int i = n; i; i >>= 1) {
if(i & 1) ntt(g, f);
ntt(f, f);
}
printf("%lld", g[xpow]);
return 0;
}
题目地址:Codeforces:Problem – 364D – Codeforces、洛谷:【CF364D】Ghd – 洛谷
John Doe offered his sister Jane Doe find the gcd of some set of numbers a.
Gcd is a positive integer g, such that all number from the set are evenly divisible by g and there isn’t such g’ (g’ > g), that all numbers of the set are evenly divisible by g’.
Unfortunately Jane couldn’t cope with the task and John offered her to find the ghd of the same subset of numbers.
Ghd is a positive integer g, such that at least half of numbers from the set are evenly divisible by g and there isn’t such g’ (g’ > g) that at least half of the numbers from the set are evenly divisible by g’.
Jane coped with the task for two hours. Please try it, too.
有一个数列,求最大的数,使得该数是数列中一半以上的数的因数。
输入格式:
The first line contains an integer n (1 ≤ n ≤ 10^6) showing how many numbers are in set a. The second line contains space-separated integers a1, a2, …, an (1 ≤ ai ≤ 10^12). Please note, that given set can contain equal numbers.
Please, do not write the %lld specifier to read or write 64-bit integers in С++. It is preferred to use the %I64d specifier.
输出格式:
Print a single integer g — the Ghd of set a.
输入样例#1:
6 6 2 3 4 5 6
输出样例#1:
3
输入样例#2:
5 5 5 6 10 15
输出样例#2:
5
参考资料:CFR 364 D. Ghd ( Random, Math ) – 0w1
随机化的题目,直接求显然不好办,n的数据范围看着就吓人,直观感觉像O(n \log n)。
我们知道这个数字一定是这个数列中某个数的因数,因此我们需要选择一个数求它与其他数的最大公因数。这些公因数中的一个就是答案。求一遍公因数是O(n \log n)的。
对于求出来的公因数,我们去从大到小找一个会成为超过一半数的因数的数字。具体做法是,选择一个因数,去找比它大的因数,如果它能整除大因数,说明大因数对应的数字也可以被这个数整除,应当把加到这个数的计数上。这个过程直观看上去是O(n^2)的,但是实际上我们不会对比当前最优解还小的因数计算,并且当计数超过n的一半的时候就可以停止计数并计入答案,在这样的处理下我们可以让这个计数的时间变得可接受。
答案肯定是这些数字中超过一半数的因数,因此每一次随机找到正确解的概率超过0.5。我们考虑重复找解的过程多次,这里我使用10次(超过10次似乎会TLE 18)。
// Code by KSkun, 2018/3
#include <cstdio>
#include <ctime>
#include <cstdlib>
#include <map>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF
: *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 1000005;
inline LL gcd(LL a, LL b) {
LL t;
while(b) {
t = a % b;
a = b;
b = t;
}
return a;
}
int n;
LL a[MAXN];
int main() {
srand(time(NULL));
n = readint();
for(int i = 1; i <= n; i++) {
a[i] = readint();
}
LL ans = 1;
for(int rep = 1; rep <= 10; rep++) { // bad rand
int rnd = (rand() * RAND_MAX + rand()) % n + 1;
std::map<LL, int> fact;
for(int i = 1; i <= n; i++) {
LL t = gcd(a[i], a[rnd]);
if(!fact.count(t)) fact[t] = 1;
else fact[t]++;
}
std::map<LL, int>::iterator it = fact.end();
do {
it--;
if((*it).first <= ans) continue;
int cnt = 0;
for(std::map<LL, int>::iterator it1 = it;
it1 != fact.end() && cnt << 1 < n; it1++) {
if(!((*it1).first % (*it).first)) {
cnt += (*it1).second;
}
}
if(cnt << 1 >= n) ans = (*it).first;
} while(it != fact.begin());
}
printf("%I64d", ans);
return 0;
}