[UOJ 48][UR #3]核聚变反应堆
先去考虑两个变量的sgcd怎么求……我们先求出他们的gcd,然后在除掉一个最小的质因子即可。
那么我们先把\(a_1\)分解质因数,然后所有数和它求一个gcd,然后去去掉一个尽可能小的质因子。注意到一个数\(p\)的质因子数量是\(O(\log p)\)级别的,所以每一个数找到要除的那个最小的质因子只需要\(O(\log a_1)\)的时间。
代码:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <vector>
#include <cmath>
const int maxn = 100005;
using ll = long long;
ll V[maxn]; int vcnt = 0;
void des(ll x) {
ll m = (ll)sqrt(x + 0.5);
for(ll i = 2; i <= m; i ++) {
if(x % i == 0) {
V[vcnt ++] = i;
while(x % i == 0) x /= i;
}
}
if(x > 1LL) V[vcnt ++] = x;
}
ll gcd(ll a, ll b) {
if(!b) return a;
else return gcd(b, a % b);
}
ll A[maxn];
int main() {
int n; scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%lld", &A[i]);
}
des(A[1]);
for(int i = 1; i <= n; i ++) {
if(i > 1) putchar(' ');
ll bs = gcd(A[i], A[1]);
if(bs == 1) {
printf("-1"); continue;
}
for(int j = 0; j < vcnt; j ++) {
if(A[i] % V[j] == 0) {
bs /= V[j];
break;
}
}
printf("%lld", bs);
}
puts("");
return 0;
}
[BZOJ 4816][SDOI2017]数字表格
当年在考场上一点反演都不会啊啊啊啊啊啊
这题虽然牵扯到了对积的反演,但其实也不是很难。先让我们大力推导一波(逃
考虑枚举柿子中的\(f[(i, j)]\)中的最大公约数(设为\(k\)),然后式子变成了(这里不妨偷个懒,钦定\(n\leq m\)):
\[\prod_{k = 1}^n f[k]^{\sum_{i = 1}^n \sum_{j = 1}^m\:[(i, j) = k]}\]
然后发现上面那个指数就是Problem b的式子,显然可化为\(\sum_{d = 1}^n \mu(d) \lfloor\frac{n}{kd}\rfloor \lfloor\frac{m}{kd}\rfloor\)。然后似乎化简没有余地了,但是根据直觉,我们可以钦定\(T = kd\),然后枚举\(T\)。
然后柿子变成了:
\[\prod_{T = 1}^n (\prod_{k\mid T} f[k]^{\mu(\tfrac{T}{k})})^{\lfloor\tfrac{n}{T}\rfloor \lfloor\tfrac{m}{T}\rfloor}\]
柿子中的\(\prod_{k\mid T} f[k]^{\mu(\tfrac{T}{k})}\)是一个类似狄利克雷卷积的东西(取完对数就是了),可以枚举约数预处理。并且这个东西很不错的一点就是上面的指数是莫比乌斯函数,可以取到的值只有那三种,不需要快速幂。
然后剩下的部分就和通常的反演题一般无二了……
代码(卡线过的蒟蒻……求轻喷……但我在LOJ上跑的还挺快的):
/**************************************************************
Problem: 4816
User: danihao123
Language: C++
Result: Accepted
Time:50840 ms
Memory:33048 kb
****************************************************************/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <utility>
#include <cctype>
#include <cassert>
const int N = 1000000;
const int maxn = N + 5;
typedef long long ll;
const ll ha = 1000000007LL;
bool p_flag = false;
ll pow_mod(ll a, ll b) {
if(!b) return 1LL;
ll ans = pow_mod(a, b / 2LL);
ans = (ans * ans) % ha;
if(1LL & b) ans = (ans * a) % ha;
#ifdef LOCAL
if(p_flag) printf("%lld^%lld : %lld\n", a, b, ans);
if(b == ha - 2LL) p_flag = false;
#endif
return ans;
}
ll inv(ll x) {
// if(x == 1LL) p_flag = true;
return pow_mod(x, ha - 2LL);
}
int miu[maxn];
void sieve() {
static int prm[maxn]; int cnt = 0;
static bool vis[maxn];
miu[1] = 1;
for(int i = 2; i <= N; i ++) {
if(!vis[i]) {
miu[i] = -1;
prm[cnt ++] = i;
}
for(int j = 0; j < cnt && prm[j] * i <= N; j ++) {
int v = prm[j] * i;
vis[v] = true;
if(i % prm[j] == 0) {
miu[v] = 0;
break;
} else {
miu[v] = miu[i] * -1;
}
}
}
}
ll f[maxn], inv_d[maxn], F[maxn];
void process() {
sieve();
f[1] = 1LL; inv_d[1] = 1LL;
for(int i = 2; i <= N; i ++) {
f[i] = (f[i - 1] + f[i - 2]) % ha;
inv_d[i] = inv(f[i]);
assert(f[i] != 0LL); assert(inv_d[i] != 0LL);
}
for(int i = 1; i <= N; i ++) F[i] = 1LL;
for(int i = 1; i <= N; i ++) {
for(int j = i; j <= N; j += i) {
int res = j / i;
if(miu[res] == 1) {
F[j] = (F[j] * f[i]) % ha;
#ifdef LOCAL
if(j <= 3) printf("f[%d](%lld) -> F[%d]\n", i, f[i], j);
#endif
continue;
}
if(miu[res] == -1) {
F[j] = (F[j] * inv_d[i]) % ha;
#ifdef LOCAL
if(j <= 3) printf("inv_f[%d](%lld) -> F[%d]\n", i, inv_d[i], j);
#endif
}
}
}
F[0] = 1LL;
bool flag = true;
for(int i = 1; i <= N; i ++) {
F[i] = (F[i] * F[i - 1]) % ha;
if(flag && F[i] == 0LL) {
printf("SF[%d] has been 0.\n", i);
flag = false;
}
}
}
ll calc(ll n, ll m) {
if(n > m) std::swap(n, m);
ll ans = 1LL;
for(ll i = 1; i <= n;) {
ll nx = std::min(n / (n / i), m / (m / i));
#ifdef LOCAL
// printf("nx of %lld : %lld\n", i, nx);
#endif
ll mulv = pow_mod((F[nx] * inv(F[i - 1])) % ha, (n / i) * (m / i));
ans = (ans * mulv) % ha;
i = nx + 1LL;
}
return ans;
}
int main() {
process();
int T; scanf("%d", &T);
while(T --) {
int n, m; scanf("%d%d", &n, &m);
printf("%lld\n", calc(n, m));
}
return 0;
}
[BZOJ 3992][SDOI2015]序列统计
终于调过去了……(然而不就是道NTT+生成函数水题吗至于调半天)
首先积非常的恶心,考虑转成和。这个事需要求指标的……求指标的话枚举原根的若干次幂即可(恰好$m$是素数一定有原根……),判断原根用比较大力的手段即可(我搞了一个$O(n\sqrt{n}logn)$的……求轻喷)。
然后这题还算比较简单吧……用生成函数表示原来的集合,然后$n$次幂就可以了。
注意那事个循环卷积……所以要开两倍然后每次乘完了再把右半部分搞过去。
代码:
/**************************************************************
Problem: 3992
User: danihao123
Language: C++
Result: Accepted
Time:6652 ms
Memory:1864 kb
****************************************************************/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <queue>
#include <utility>
#include <vector>
#include <cmath>
typedef long long ll;
const int maxn = 32005;
const ll ha = 1004535809LL;
const ll bs = 3LL;
ll n, m, gl; int sz;
ll pow_mod(ll a, ll b, ll p) {
if(!b) return 1LL;
ll ans = pow_mod(a, b >> 1, p);
ans = (ans * ans) % p;
if(b & 1LL) ans = (ans * a) % p;
return ans;
}
ll inv(ll x, ll p) {
return pow_mod(x, p - 2LL, p);
}
void break_up(ll x, std::vector<ll> &V) {
int bd = sqrt(x + 0.5);
for(ll i = 2; i <= bd; i ++) {
if(x % i == 0) {
V.push_back(i);
while(x % i == 0) x /= i;
}
if(x == 1LL) break;
}
if(x > 1LL) V.push_back(x);
}
ll get_phi() {
if(m == 2LL) return 1LL;
ll mi = m - 1LL;
std::vector<ll> V;
break_up(mi, V);
for(ll i = 2; i <= mi; i ++) {
bool ok = true;
for(int j = 0; j < V.size(); j ++) {
ll b = mi / V[j];
if(pow_mod(i, b, m) == 1LL) {
ok = false;
#ifdef LOCAL
printf("%lld not passed!\n", i);
#endif
break;
}
}
if(ok) {
return i;
}
}
}
int bi, len;
int flip(int x) {
int ans = 0;
for(int i = 0; i < bi; i ++) {
if((1 << i) & x) {
ans += (1 << (bi - i - 1));
}
}
return ans;
}
void ntt(ll *A, bool flag = false) {
for(int i = 0; i < len; i ++) {
int v = flip(i);
if(v < i) std::swap(A[i], A[v]);
}
for(int L = 1; L < len; L <<= 1) {
ll xi_n = pow_mod(3LL, (ha - 1LL) / (ll(L << 1)), ha);
if(flag) xi_n = inv(xi_n, ha);
for(int i = 0; i < len; i += (L << 1)) {
ll w = 1;
for(int j = i; j < i + L; j ++) {
ll p1 = A[j], p2 = A[j + L];
A[j] = (p1 + (p2 * w) % ha) % ha;
A[j + L] = (p1 - (p2 * w) % ha + ha) % ha;
w = (w * xi_n) % ha;
}
}
}
}
void poly_mul(ll *A, ll *B) {
static ll C[maxn]; memset(C, 0, sizeof(C));
#ifdef LOCAL
printf("A :");
for(int i = 0; i < len; i ++) printf(" %lld", A[i]);
puts("");
printf("B :");
for(int i = 0; i < len; i ++) printf(" %lld", B[i]);
puts("");
#endif
ntt(A); ntt(B);
for(int i = 0; i < len; i ++) C[i] = (A[i] * B[i]) % ha;
ntt(C, true);
ll inv_n = inv(len, ha);
for(int i = 0; i < len; i ++) {
C[i] = (C[i] * inv_n) % ha;
}
#ifdef LOCAL
printf("C (not processed) :");
for(int i = 0; i < len; i ++) printf(" %lld", C[i]);
puts("");
#endif
for(int i = 0; i < len; i ++) {
int v = i % (m - 1LL);
if(v != i) {
C[v] = (C[v] + C[i]) % ha;
C[i] = 0LL;
}
}
#ifdef LOCAL
printf("C :");
for(int i = 0; i < len; i ++) printf(" %lld", C[i]);
puts("");
#endif
std::copy(C, C + len, A);
}
void poly_pow(ll *A, ll b) {
static ll B[maxn];
static ll res[maxn];
std::copy(A, A + len, res);
std::fill(A, A + len, 0); A[0] = 1LL;
#ifdef LOCAL
printf("A :");
for(int i = 0; i < len; i ++) printf(" %lld", A[i]);
puts("");
printf("res : ");
for(int i = 0; i < len; i ++) printf("%lld ", res[i]);
puts("");
#endif
while(b) {
if(1LL & b) {
std::copy(res, res + len, B);
poly_mul(A, B);
std::fill(B, B + len, 0);
}
std::copy(res, res + len, B);
poly_mul(res, B);
std::fill(B, B + len, 0);
b >>= 1LL;
}
}
int main() {
static bool vis[maxn];
static ll A[maxn];
scanf("%lld%lld%lld%d", &n, &m, &gl, &sz);
ll phi = get_phi();
for(int i = 1; i <= sz; i ++) {
int v; scanf("%d", &v);
if(v == 0) continue;
vis[v] = true;
}
int logx = 0;
bi = 0; len = 1;
while(len <= (2 * m - 2)) {
bi ++; len <<= 1;
}
for(int i = 0; i < (m - 1); i ++) {
int v = pow_mod(phi, i, m);
if(vis[v]) {
A[i] ++;
#ifdef LOCAL
printf("log(%d) : %d\n", v, i);
#endif
}
if(v == gl) {
logx = i;
}
}
poly_pow(A, n);
printf("%lld\n", A[logx]);
return 0;
}
[坑]狄利克雷卷积和反演
开个坑,记录一些和反演以及狄利克雷卷积的东西。
首先积性函数、狄利克雷卷积等基本概念不写了,就讲讲性质吧。
有几个一定要记住的东西:
\[\mu \ast 1 = e\]
\[\varphi \ast 1 = id\]
\[\mu \ast id = \varphi\]
这几个在推式子的过程中都有很大的作用,务必要记住。
所谓莫比乌斯反演,其实就是:
\[F = f \ast 1\Leftrightarrow f = F \ast \mu\]
(谜之音:其实很多所谓“反演题”都没用到这俩性质啊……)
关于莫比乌斯函数本身,还有一个好康的性质:
\[(\mu\ast 1)(k) = \sum_{i = 0}^k (-1)^i C_k^i\]
[BZOJ 2186]沙拉公主的困惑
这个题啊……亦可赛艇!
答案是[tex]\varphi(m!)*n!/m![/tex]。原因很简单,把[tex][1,n!][/tex]分成长度为[tex]m![/tex]的若干段,除去第一段外每一段中与[tex]m![/tex]互质的数[tex]k[/tex]肯定满足[tex](k\bmod m!,m!)=1[/tex](否则,[tex]k[/tex]和[tex]m![/tex]就会有大于一的公因子了)。所以说每一段内与[tex]m![/tex]互质的数都有[tex]\varphi(m!)[/tex]个。
麻烦好像就在于求一个阶乘的欧拉函数。考虑一个新乘上的数能给答案带来的贡献——如果这个数是合数,它的所有因子在前面都有了,只能把他自己贡献出来;如果这个数是质数(假设为[tex]p[/tex]),出了贡献自己以外还会贡献一个[tex](1-1/p)[/tex],最后乘起来就是贡献了[tex]p-1[/tex]。筛一遍素数再递推一下就好辣~
并且……[tex]n-m[/tex]可能非常大,所以说除去[tex]m![/tex]那块要用逆元做。
(顺便说下阶乘也要递推)
代码:
/**************************************************************
Problem: 2186
User: danihao123
Language: C++
Result: Accepted
Time:9408 ms
Memory:166836 kb
****************************************************************/
#include <cstdio>
#include <cmath>
typedef unsigned long long ull;
const int maxn=10000000;
ull R;
bool vis[maxn+5];
inline void sievePrime(){
register int i,j,m=sqrt(maxn+0.5);
for(i=2;i<=m;i++)
if(!vis[i])
for(j=i*i;j<=maxn;j+=i)
vis[j]=true;
}
ull fac[maxn+5];
inline void sieveFac(){
register int i;
fac[0]=1%R;
for(i=1;i<=maxn;i++)
fac[i]=(fac[i-1]*(i%R))%R;
}
ull phifac[maxn+5];
inline void sievePhiFac(){
register int i;
phifac[1]=1%R;
for(i=2;i<=maxn;i++){
if(vis[i])
phifac[i]=(phifac[i-1]*(i%R))%R;
else
phifac[i]=(phifac[i-1]*((i%R-1%R+R)%R))%R;
}
}
void exgcd(ull a,ull b,ull& d,ull& x,ull& y){
if(!b){
d=a;
x=1;
y=0;
}else{
exgcd(b,a%b,d,y,x);
y-=x*(a/b);
}
}
ull inv(ull a){
ull d,x,y;
exgcd(a,R,d,x,y);
return (x+R)%R;
}
int main(){
int T;
int n,m;
scanf("%d%llu",&T,&R);
sievePrime();
sieveFac();
sievePhiFac();
while(T--){
scanf("%d%d",&n,&m);
printf("%llu\n",(phifac[m]*((fac[n]*inv(fac[m]))%R))%R);
}
return 0;
}
[BZOJ 2118]墨墨的等式
论如何把数论乱搞和图论乱搞出在一起……
这个题由于要求[tex]x\ge 0[/tex],所以不能gcd乱搞。我们可以先取[tex]\{a_n\}[/tex]的最小值[tex]p[/tex](忽略为0的情况,为啥取最小值待会再说),对方程两边模[tex]p[/tex]。然后对于任何能使某个同余方程成立的[tex]\{x_n\}[/tex],将其中所有[tex]x_i[/tex]同时加任意个[tex]p[/tex],同余方程都成立。
取模后,[tex]B\in Z_p[/tex],所以说只要对于[tex]Z_p[/tex]中的每个数找出最小的一组合法解即能推出其他解(所以说,剩余系越少效率越高,这也就要求取的[tex]a_i[/tex]要小)。不过这个最小的一组合法解怎么找?
我们先找出任意一个合法[tex]B[/tex](比如说0吧),然后尝试加上[tex]a_i[/tex],就可以推出其他[tex]B\in Z_p[/tex]的最小解。这个应用当然是需要最短路辣。
求出来的最短路,表示的是取最小解时的[tex]B[/tex]。这样的话就可以推出某个前缀区间中合法[tex]B[/tex]的个数(能加多少[tex]p[/tex],就有多少个,注意不要忽略加0个的情况),并且答案符合区间减法,最后做差分即可。
注意忽略[tex]a_i=0[/tex]的情况(相当于不存在)。
代码:
/**************************************************************
Problem: 2118
User: danihao123
Language: C++
Result: Accepted
Time:1952 ms
Memory:5508 kb
****************************************************************/
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#ifdef DEBUG
#include <cassert>
#endif
using namespace std;
typedef long long ll;
const int maxn=15;
const ll INF=0x7f7f7f7f7f7f7f7f;
ll A[maxn];
bool inQueue[500005];
ll d[500005];
int n;
ll minv;
inline void SPFA(){
register int i,u,to;
queue<int> Q;
memset(d,0x7f,sizeof(d));
d[0]=0;
Q.push(0);
inQueue[0]=true;
#ifdef DEBUG
assert(d[1]==INF);
#endif
while(!Q.empty()){
u=Q.front();
Q.pop();
inQueue[u]=false;
for(i=1;i<=n;i++){
to=(u+A[i])%minv;
if(d[u]<INF && d[u]+A[i]<d[to]){
d[to]=d[u]+A[i];
if(!inQueue[to]){
Q.push(to);
inQueue[to]=true;
}
}
}
}
}
inline ll Calc(ll x){
register ll ans=0;
register int i=0;
for(i=0;i<minv;i++)
if(d[i]<=x)
ans+=(x-d[i])/minv+1;
return ans;
}
int main(){
ll l,r;
register int i;
scanf("%d%lld%lld",&n,&l,&r);
minv=0x7fffffff;
for(i=1;i<=n;i++){
scanf("%d",&A[i]);
if(!A[i]){
i--;
n--;
continue;
}
minv=min(minv,A[i]);
}
SPFA();
printf("%lld\n",Calc(r)-Calc(l-1));
return 0;
}
[BZOJ 2190]仪仗队
这个题啊……有规律可循。
我们可以发现,关于答案[tex]a[/tex]有如下规律:
[tex]a+1=2\mul (\Sigma_{i=2}^{n-1}\varphi(i)+2)[/tex]
然后筛法求欧拉函数即可(我听说神犇们都用了杜教筛哎)
代码:
/**************************************************************
Problem: 2190
User: danihao123
Language: C++
Result: Accepted
Time:28 ms
Memory:976 kb
****************************************************************/
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=40005;
#define CUS_REP(i,a,b) for(i=(a);i<=(b);i++)
int phi[maxn];
int n;
void sieve(){
register int i,j;
phi[1]=1;
CUS_REP(i,2,n)
if(!phi[i])
for(j=i;j<=n;j+=i){
if(!phi[j])
phi[j]=j;
phi[j]=phi[j]/i*(i-1);
}
}
int main(){
register int i,ans=2;
scanf("%d",&n);
sieve();
/*
#ifdef DEBUG
CUS_REP(i,1,n)
printf("%d\n",phi[i]);
#endif
*/
CUS_REP(i,2,n-1)
ans+=phi[i];
printf("%d\n",ans*2-1);
return 0;
}
[BZOJ 1441]Min
这个问题看似无从下手。
我们可以先取[tex]n=2[/tex],然后你就发现你似乎要解[tex]A_{1}X_{1}+A_{2}X_{2}>0[/tex],而且还要求[tex]S[/tex]最小?你想到了什么?扩展欧几里得?对头!
由扩欧推广可得答案就是所有数的最大公约数。
代码:
/**************************************************************
Problem: 1441
User: danihao123
Language: C++
Result: Accepted
Time:0 ms
Memory:820 kb
****************************************************************/
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
int main(){
register int ans,i;
int n,temp;
scanf("%d",&n);
scanf("%d",&temp);
ans=abs(temp);
for(i=2;i<=n;i++){
scanf("%d",&temp);
ans=gcd(ans,abs(temp));
}
printf("%d\n",ans);
return 0;
}
[CF 371B]Fox Dividing Cheese
狐狸的三种操作的实质是除二,除三,除五。由此我们可以猜想在最优策略下,两块蛋糕最后的重量应该是[tex]gcd(a,b)[/tex]。然后试除即可。
(大胆猜想,不用证明)
代码:
#include <iostream>
#include <algorithm>
using namespace std;
int gcd(int a,int b){
if(!b)
return a;
else
return gcd(b,a%b);
}
static int P[3]={2,3,5};
inline int min_fj(int source,int direction){
register int i,ans=0;
source/=direction;
if(source==1)
return 0;
for(i=2;i>=0;i--){
while(source%P[i]==0){
source/=P[i];
ans++;
}
}
if(source==1)
return ans;
else
return -1;
}
int main(){
register int direction,t1,t2;
int a,b;
cin>>a>>b;
if(a==b){
cout<<0<<endl;
return 0;
}
direction=gcd(a,b);
t1=min_fj(a,direction);
if(t1==-1){
cout<<-1<<endl;
return 0;
}
t2=min_fj(b,direction);
if(t2==-1){
cout<<-1<<endl;
return 0;
}
cout<<t1+t2<<endl;
return 0;
}
[CodeVS 1012]最大公约数和最小公倍数问题
很经典的问题了吧……然而现在才A……
应注意[tex]P*Q=x*y[/tex],然而[tex]P[/tex]和[tex]Q[/tex]都可表示为[tex]x[/tex]的乘积,问题就好思考多了。答案就是[tex]y/x[/tex]的质因子的子集数(同一种质因子不能同时分配到P与Q中,否则gcd(P,Q)会不等于x)。
注意有可能无解!
代码:
#include <iostream>
using namespace std;
inline int fj(int x){
register int i,ans=0;
for(i=2;i<=x && x>1;i++)
if(!(x%i)){
ans++;
while(!(x%i))
x/=i;
}
return ans;
}
int main(){
int a,b;
register int ans;
cin>>a>>b;
if(b%a)
ans=0;
else
ans=a==1?1:1<<fj(b/a);
cout<<ans<<endl;
return 0;
}