poj专题 - 初期数学

第六个专题了,初期数学:


(1)、组合数学

1、加法原理和乘法原理以及排列组合

 

1、hdu 4497 GCD and LCM

题意:已知l,g其中g=gcd(x,y,z),l=lcm(x,y,z),问x,y,z有多少种组合使得关系成立。

分析:

最大公约数和最小公倍数有以下式子成立:

poj专题 - 初期数学_第1张图片

所以对g进行唯一定理的分解,对任意一个素因子,设其在g和l中的指数分别为a,b,则必有a <= b,且x,y,z中必有一个数指数为a,一个为b,至于另一个数其指数在a和b之间都行,根据排列组合很容易可以推导出计算一个素因子时的组合数为6*(l – g),然后对于所有素因子的情况根据乘法原理便可以求出总共的可能数。

#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
map dg, dl;
LL g, l;
void factor(map& mp, int x) {
    for (int i = 2; i * i <= x; i++) {
        while (x % i == 0) mp[i]++, x /= i;
    }
    if (x != 1) mp[x] = 1;
}
bool cal() {
    for (map::iterator i = dg.begin(); i != dg.end(); i++)
        if (i->second > dl[i->first]) return 0;
    return 1;
}
void init() {
    dg.clear(); dl.clear();
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        scanf("%I64d %I64d", &g, &l);
        init();
        factor(dg, g); factor(dl, l);
        LL ans = cal();
        for (map::iterator i = dl.begin(); ans && i != dl.end(); i++) ans *= i->second == dg[i->first] ? 1 : 6 * (i->second - dg[i->first]);
        printf("%I64d\n", ans);
    }
    return 0;
}

2、hdu5719 Arrange

题意:Ai是1~n的某种排列,Bi表示Ai中前i个数的最小值,Ci表示Ai中前i个数的最大值,给出B和C,求A序列有多少种可能。

分析:分析可知一下得到基本的几点:

1、第一,如果某个位置i最小值和最大值变化了,那么i位置就为变化的值,且不可能同时变化,如果某个位置最小值和最大值都没变化,那么该位置不确定。2、第二,b序列逐渐递减,c序列逐渐递增,对于某个不确定的位置, 对于任何i,必有bi <= bi-1, ci >= ci-1,所以,对于不确定的位置,其数所在的范围也在逐渐增大,也就是说后面位置要确定的数的范围比前面要确定的数的范围大,且对于某个位置i,i以前所有已确定位置的数必定在bi到ci之间,这样的话就是一个明显的乘法原理了,对于某个不确定的位置i,很容易知道其有多少种可能,直接全部乘起来即为答案。

#include 
using namespace std;

typedef long long LL;
const int N = 100010;
const int MOD = 998244353;
int b[N], c[N];

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        for (int i = 0; i < n; i++) scanf("%d", &b[i]);
        for (int i = 0; i < n; i++) scanf("%d", &c[i]);
        LL ans = 1;
        if (b[0] != c[0]) ans = 0;
        for (int i = 1; i < n && ans; i++) {
            if (b[i] <= b[i-1] && c[i] >= c[i-1]) {
                if (b[i] < b[i-1] && c[i] > c[i-1]) ans = 0;
                if (b[i] == b[i-1] && c[i] == c[i-1]) ans *= c[i] - b[i] + 1 - i, ans %= MOD;
            }
            else ans = 0;
        }
        printf("%I64d\n", ans);
    }
    return 0;
}

2、递推关系

1、poj 3252 Round Numbers

题意:计算一个区间内二进制位中0的个数比1的多的数的个数。
分析:题目归结为递推关系,我还以为是数位dp,但是强行用排列组合做完了这题。。。
花了整整一个上午才做完。。。
对于给定的两个数,比如说10010、1000100001,其位数分别为5和10.
首先把位数在6和9之间的种数计算出来,然后加上位数为5和10的个数即为答案。
位数在6和9之间的种数很容易知道,关键是5和10:
位数为5的我们需要计算比10010大的数,从10010的第二位开始,如果某位为0,则令其为1,后面的位可以随便取,求符合条件的组合数。
同理,位数为10的我们需要计算比1000100001小的数,从1000100001的第二位开始,如果某位为1,则令其为0,后面的位都可以随便取,求符合条件的组合数。
根据以上的原则所求取的数目并不是答案,因为这样做忽略了一个问题:位数相同时有重复的计算,比如说2到3之间的数计算出来就为2。这样要采取另外的原则:
比如说10010、11001,从正面考虑不容易计算,从反面出发,首先计算出比位数为5且10010小的数目,以及位数为5且比11001大的数目,在计算出位数为5的所有可能的种数,减去前面的两种就是10010到11001之间的个数了。
不过本题完全可以用数位dp实现,在此就不多说了。

#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int N = 32;
int c[N][N];
LL num[N][N];
void deal(LL s, int* s1, int* bs, int& len) {
    while (s) bs[len++] = s & 1, s >>= 1;
    s1[0] = 1;
    for (int i = len - 2, j = 1; i >= 0; i--, j++) s1[j] = s1[j-1] + (bs[i] ? 1 : 0);
};
int bs[N], bf[N], s1[N], f1[N];
int lens, lenf;
int main() {
    for (int i = 0; i < N; i++) num[i][0] = c[i][0] = c[i][i] = 1;
    for (int i = 1; i < N; i++) {
        for (int j = 1; j < i; j++) c[i][j] = c[i-1][j] + c[i-1][j-1];
    }
    for (int i = 1; i < N; i++) {
        for (int j = 1; j <= i; j++) num[i][j] = num[i][j-1] + c[i][j];
    }
    LL s, f;
    scanf("%I64d %I64d", &s, &f);
    deal(s, s1, bs, lens);
    deal(f, f1, bf, lenf);
    LL ans = 0;
    if (lens == lenf) {
        LL tf = 0, ts = 0, tmp;
        LL a = lens >> 1;
        for (int i = lens - 2, j = 0; i >= 0; i--, j++) {
            if (bs[i]) {
                int t = s1[j];
                if (a - t >= 0) ts += a - t >= i ? num[i][i] : num[i][a-t];
            }
        }
        for (int i = lenf - 2, j = 0; i >= 0; i--, j++) {
            if (!bf[i]) {
                int t = f1[j] + 1;
                if (a - t >= 0) tf += a - t >= i ? num[i][i] : num[i][a-t];
            }
        }
        lens--;
        tmp = lens & 1 ? num[lens][lens/2] : num[lens][lens/2-1];
        ans = tmp - ts - tf;
    }
    else {
        LL a = lens >> 1;
        if (s1[lens-1] <= a) ans++;
        for (int i = lens - 2, j = 0; i >= 0; i--, j++) {
            if (!bs[i]) {
                int t = s1[j] + 1;
                if (a - t >= 0) ans += a - t >= i ? num[i][i] : num[i][a-t];
            }
        }
        LL tmp = ans;
        a = lenf >> 1;
        if (f1[lenf-1] <= a) ans++;
        for (int i = lenf - 2, j = 0; i >= 0; i--, j++) {
            if (bf[i]) {
                int t = f1[j];
                if (a - t >= 0) ans += a - t >= i ? num[i][i] : num[i][a-t];
            }
        }
        for (int i = lens; i <= lenf - 2; i++) {
            ans += i & 1 ? num[i][i/2] : num[i][i/2-1];
        }
    }
    printf("%I64d\n", ans);
    return 0;
}

2、poj 1850 Code

题意:计算一个区间内二进制位中0的个数比1的多的数的个数。
分析:
按照字典序的顺序从小写字母a开始按顺序给出序列
a- 1
b- 2
...
z- 26
ab - 27
...
az - 51
bc - 52
...
vwxyz - 83681
...
输入一个字符串,求出其编号。
分析:刚开始做这题时一直在想如何递推,想了很多状态的定义方式然后去推到转移方程,每次都是在中间被绕晕了。。。。。最后冷静地理清思路就写出来了。。。
首先,要计算一个长为l的字符串的编号,首先计算比长度在1到l-1之间的字符串的个数,然后计算在该字符串之前且长为l的字符串的个数就行了。前面的就是简单的组合数。
一个原则就是对于某个以i开始的字符串,其能组成的且能编号的的字符串很容易递推知道,这样令dp[i][j]为长度为i且以j + ‘a’为首字母的字符串中能编号的字符串的个数,也就是符合字典序的字符串的个数,显然,dp[i][j] = dp[i-1][j+1] + … + dp[i-1][25]。这样,首先预处理dp数组,之后统计相加即可。
最后回想整个解题过程,之前被绕晕的原因就是因为总想着dp数组即为答案。。。其实这题中dp数组只是个中间过程而已。。。。

 

#include 
#include 
#include 
using namespace std;

const int N = 15;
char s[N];
int dp[N][28], len, c[N];

void predeal() {
    for (int i = 0; i < 26; i++) dp[1][i] = 1;
    for (int i = 2; i <= 10; i++) {
        for (int j = 0; j <= 26 - i; j++) {
            for (int x = j + 1; x < 26; x++) dp[i][j] += dp[i-1][x];
        }
    }
    c[0] = 1;
    for (int i = 1; i <= 10; i++) c[i] = c[i-1] * (26 - i + 1) / i;
}
int dfs(int pos, int lim, int x) {
    if (pos == len) return 1;
    int beg = lim ? x + 1 : 0, end = s[pos] - 'a';
    int ans = 0;
    for (int i = beg; i < end; i++) ans += dp[len-pos][i];
    ans += dfs(pos + 1, 1, end);
    return ans;
}
int solve() {
    int ans = 0;
    for (int i = 1; i < len; i++) ans += c[i];
    return ans + dfs(0, 0, 1);
}
int main() {
    predeal();
    scanf("%s", &s);
    len = strlen(s);
    bool flag = 1;
    for (int i = 1; i < len && flag; i++)
        if (s[i] <= s[i-1]) flag = 0;
    flag ? printf("%d\n", solve()) : puts("0");
    return 0;
}

3、poj1019 Number Sequence

题意:给定一个序列,由1到n的排列组成,n从1开始,求第i个位置上面的数。
分析:不知道和递推有什么关系,就是个简单的推公式题。令s[i]为到n的排列位置总共的位数,令num[i]为1到i的序列的位数。s[i] = s[i-1] + num[i],预处理两个数组,后面直接二分即可。

#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int N = 33000;
LL s[N];
int num[N];
int main() {
    num[1] = s[1] = 1;
    int i;
    for (i = 2; ; i++) {
        num[i] = num[i-1] + log10(i) + 1;
        s[i] = s[i-1] + num[i];
        if (s[i] > 2147483647) break;
    }
    int t;
    scanf("%d", &t);
    while (t--) {
        LL pos;
        scanf("%I64d", &pos);
        LL tmp = pos;
        int p = lower_bound(s + 1, s + i, pos) - s;
        int ans;
        if (s[p] == pos) ans = p % 10;
        else {
            pos -= s[--p];
            p = lower_bound(num + 1, num + i, pos) - num;
            if (num[p] == pos) ans = p % 10;
            else {
                pos -= num[p-1];
                char str[10];
                sprintf(str, "%d", p);
                ans = str[pos-1] - '0';
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

4、poj1942 Paths on a Grid

题意:给定一个网格,m行n列,求从左下角(0,0)开始走到(n,m)有多少种可能。
分析:刚开始无脑的我直接开数组递推,没看到m和n为32位整数的条件,于是顺理成章的re。
题目只需要算出最后的路线数目,直接利用组合数学就可以了,一共要走n+m步,选出n步向右走即可。。。且C(n + m, n) = C(n + m, m)。

 

#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
int main() {
    LL n, m;
    while (scanf("%I64d %I64d", &n, &m), n + m) {
        LL ans = 1, t = n + m;
        LL mi = min(n, m);
        for (LL i = 1; i <= mi; i++) ans = ans * (t - i + 1) / i;
        printf("%I64d\n", ans);
    }
    return 0;
}

(2)、数论

1、素数与整除问题

1、poj 3421 X-factor Chains

题意:给定数x,求一条最长的因子链,1 = X0, X1, X2, …, Xm = X,链中xi 分析:两种方法:
① 利用类似于素数筛法的过程预处理出所有数x的最长链长度和其数量,复杂度也只有O(2^20*20),900多ms。
② 要使得链尽量长,那么链中的元素前后两个数中大的比小的多一个素因子相乘即可,这样链肯定是最长的,那么数量怎么求呢?其实就是选取素因子相乘的排列问题,这样便转化为了多重集排列数的计算,直接套用离散数学书上的公式即可:
设多重集S={n1*a1,n2*a2,….nk*ak},且n=n1+n2+n3+…+nk,则s的排列数为n!/(n1!*n2!*…nk!),79ms。

类筛法预处理:

#include 
#include 
#include 
#include 
using namespace std;

typedef pair p;
const int N = 1048576;
p ans[N+5];

void predeal() {
    for (int i = 1; i <= N; i++) ans[i] = p(1, 1);
    int up = N >> 1;
    for (int i = 2; i <= up; i++) {
        for (int j = i << 1; j <= N; j += i) {
            if (ans[i].first + 1 > ans[j].first) ans[j] = p(ans[i].first + 1, ans[i].second);
            else if (ans[i].first + 1 == ans[j].first) ans[j].second += ans[i].second;
        }
    }
}
int main() {
    predeal();
    int x;
    while (~scanf("%d", &x)) printf("%d %d\n", ans[x].first, ans[x].second);
    return 0;
}

多重集:

#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int N = 25;
int num;
LL fac[N], tmp;
void prime_factor(int x) {
    int up = sqrt(x);
    for (int i = 2; x != -1 && i <= up; i++) {
        if (x % i == 0) {
            int cnt = 0;
            while (x % i == 0) x /= i, cnt++;
            tmp *= fac[cnt]; num += cnt;
        }
    }
    if (x != 1) num++;
}
int main() {
    fac[1] = 1;
    for (int i = 2; i <= 20; i++) fac[i] = fac[i-1] * i;
    int x;
    while (~scanf("%d", &x)) {
        num = 0; tmp = 1;
        prime_factor(x);
        printf("%d %I64d\n", num, fac[num] / tmp);
    }
    return 0;
}

2、poj 3292 Semi-prime H-numbers

题意:定义自然素数一样一个H-number是所有的模四余一的数。
如果一个H-number是H-primes 当且仅当它的因数只有1和它本身(除1外)。
一个H-number是H-semi-prime当且仅当它只由两个H-primes的乘积表示。
H-number剩下其他的数均为H-composite。
给你一个数h,问1到h有多少个H-semi-prime数。
分析:这个和自然素数、自然合数类似,直接筛h素数在处理下就可以了。

 

#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int N = 1000001;
int vis[N+10];
vector hp, hsp;

void seive() {
    for (int i = 5; i <= N; i += 4) {
        if (!vis[i]) {
            hp.push_back(i);
            for (LL j = (LL)i * i; j <= N; j += i) vis[j] = 1;
        }
    }
    memset(vis, 0, sizeof(vis));
    for (int i = 0; i < hp.size(); i++) {
        for (int j = i; j < hp.size(); j++) {
            LL t = (LL)hp[i] * hp[j];
            if (t > N) break;
            if (!vis[t] && (t-1) % 4 == 0) vis[t] = 1, hsp.push_back(t);
        }
    }
    sort(hsp.begin(), hsp.end());
}

int main() {
    seive();
    int n;
    while (~scanf("%d", &n), n) printf("%d %d\n", n, upper_bound(hsp.begin(), hsp.end(), n) - hsp.begin());
}

2、进制位

1、poj1152 An Easy Problem!

题意:给你一个N进制的整数R,题目保证R能被N-1整除,让你求符合条件的最小的N。
分析:大数取模,可以推导出只要每个数位之和能整出n-1就说明n符合条件。直接暴力就可以了。

 

#include 
#include 
#include 
#include 
using namespace std;

const int N = 32010;
char s[N];
int num(char c) {
    if (isdigit(c)) return c - '0' + 1;
    else if (isupper(c)) return c - 'A' + 11;
    else if (islower(c)) return c - 'a' + 37;
}
int main() {
    while (~scanf("%s", s)) {
        int ma = 0, sum = 0;
        for (int i = 0; s[i]; i++) {
            ma = max(ma, num(s[i]));
            sum += (num(s[i]) - 1);
        }
        int ans = 0;
        for ( ; ma <= 62 && !ans; ma++) {
            if (sum % (ma - 1) == 0) ans = ma;
        }
        ans ? printf("%d\n", ans) : puts("such number is impossible!");
    }
    return 0;
}

3、同余模算术

1、poj2635 The Embarrassed Cryptographer

题意:给定一个大数k和l,判断k是否有比l小的素因子。
分析:和上题一样,也是大数取模,不过这题卡的也是无语,直接每一位处理取模会超时,需要分块取模,也就是说每几位取一次模,取几位自己定,都行。

 

#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int N = 1000000;
char s[110];
vector p;
bool vis[N+5];
void seive() {
    int up = sqrt(N);
    for (int i = 2; i <= up; i++) {
        if (!vis[i]) {
            for (int j = i * i; j <= N; j += i) vis[j] = 1;
        }
    }
    for (int i = 2; i < N; i++)
        if (!vis[i]) p.push_back(i);
    p.push_back(N << 1);
}
int main() {
    seive();
    int l;
    while (scanf("%s %d", &s, &l), l) {
        int ans = 0;
        for (int i = 0; p[i] < l && !ans; i++) {
            LL a = 0;
            int len = strlen(s), j, t;
            for (j = 0; j + 4 <= len; j += 4) {
                t = 0;
                for (int x = j; x < j + 4; x++) t = t * 10 + s[x] - '0';
                a = (a * 10000 + t) % p[i];
            }
            t = 0;
            for (int x = j; x < len; x++) t = t * 10 + s[x] - '0';
            if (j == len - 3) a = (a * 1000 + t) % p[i];
            else if (j == len - 2) a = (a * 100 + t) % p[i];
            else if (j == len - 1) a = (a * 10 + t) % p[i];
            if (!a) ans = p[i];
        }
        ans ? printf("BAD %d\n", ans) : puts("GOOD");
    }
    return 0;
}

2、poj 2115 C Looooops

题意:对于C的for(i=A ; i!=B ;i +=C)循环语句,问在k位存储系统中循环几次才会结束。
若在有限次内结束,则输出循环次数。否则输出死循环。.
分析:不知道和同余模有什么关系,就是个拓展欧几里得的模板题。

 

#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
LL exgcd(LL a, LL b, LL& d, LL& x, LL& y) {
    if (b) exgcd(b, a % b, d, y, x), y -= x * (a / b);
    else d = a, x = 1, y = 0;
}
int main() {
    LL a, b, c, k;
    while (scanf("%I64d %I64d %I64d %I64d", &a, &b, &c, &k), k) {
        LL t = 1LL << k, gcd, x, y;
        exgcd(c, -t, gcd, x, y);
        if ((b - a) % gcd) puts("FOREVER");
        else {
            x *= (b - a) / gcd;
            t = -t / gcd;
            x %= t;
            if (x < 0) t < 0 ? x -= t : x += t;
            printf("%I64d\n", x);
        }
    }
    return 0;
}

3、poj1845 Sumdiv

题意:求a^b的因子和对9901取模。
分析:因子和的求法:
假设n = p1^e1 *p2^e2 * …*pn^en.那么因子和s =g(p1,e1) + g(p2,e2) + g(p3, e3) + ….。 g(p, e) = (p^(e+1) – 1) / (p – 1) = (1+p+p^2+p^3+...p^e)。
本题有两种方法:
①如果利用等比数列的和计算,那么在使用时需要特判(p – 1)是否会是9901的倍数,因为如果是倍数那么不能计算逆元,这时候可以利用(a / b) % mod = (a % (b * mod)) / b来取模,这个很容易证明。
②不用逆元、直接利用(a / b) % mod = (a % (b * mod)) / b求解,但是有一个问题是中间两个LL的数相乘会溢出,可以转换为加法二分求解。
③这种方法比较神奇,直接求取等比数列的和,递归求解。

规则如下:
用递归二分求等比数列1+pi+pi^2+pi^3+...+pi^n:
(1)若n为奇数,一共有偶数项,则:
1 +p + p^2 + p^3 +...+ p^n = (1+p^(n/2+1)) + p * (1+p^(n/2+1)) +...+ p^(n/2) *(1+p^(n/2+1))
= (1+ p + p^2 +...+ p^(n/2)) * (1 + p^(n/2+1))
(2)若n为偶数,一共有奇数项,则:
1 +p + p^2 + p^3 +...+ p^n = (1+p^(n/2+1)) + p * (1+p^(n/2+1)) +...+ p^(n/2-1) *(1+p^(n/2+1)) + p^(n/2) = (1 + p + p^2 +...+ p^(n/2-1)) * (1+p^(n/2+1)) + p^(n/2);

 求逆元、特判模数倍数:

#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const LL MOD = 9901;
const int N = 30;
int k;
LL pmod(LL n, LL a, LL mod) {
    LL ans = 1;
    while (n) {
        if (n & 1) ans = ans * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return ans;
}
LL d[N][2];
LL prime_factor(LL x) {
    LL up = sqrt(x + 0.5);
    for (LL i = 2; x != -1 && i <= up; i++) {
        if (x % i == 0) {
            int cnt = 0;
            while (x % i == 0) x /= i, cnt++;
            d[k][0] = i, d[k++][1] = cnt;
        }
    }
    if (x != 1) d[k][0] = x, d[k++][1] = 1;
}
LL solve(LL n, LL a) {
    if ((a - 1) % MOD == 0) {
        LL m = MOD * (a - 1);
        LL t = (pmod(n, a, m) - 1) % m;
        if (t < 0) t += m;
        return t / (a - 1) % MOD;
    }
    LL t = (pmod(n, a, MOD) - 1) % MOD;
    if (t < 0) t += MOD;
    return t * pmod(9899, a - 1, MOD) % MOD;
}
int main() {
    LL a, b;
    scanf("%I64d %I64d", &a, &b);
    if (!a) puts("0");
    else if (!b) puts("1");
    else {
        prime_factor(a);
        LL ans = 1;
        for (int i = 0; i < k; i++) ans = ans * solve(d[i][1] * b + 1, d[i][0]) % MOD;
        printf("%I64d\n", ans);
    }
    return 0;
}

递归直接求和:

#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const LL MOD = 9901;
const int N = 30;
int k;
LL pmod(LL a, LL n) {
    LL ans = 1;
    while (n) {
        if (n & 1) ans = ans * a % MOD;
        a = a * a % MOD;
        n >>= 1;
    }
    return ans;
}
LL d[N][2];
LL prime_factor(LL x) {
    LL up = sqrt(x + 0.5);
    for (LL i = 2; x != -1 && i <= up; i++) {
        if (x % i == 0) {
            int cnt = 0;
            while (x % i == 0) x /= i, cnt++;
            d[k][0] = i, d[k++][1] = cnt;
        }
    }
    if (x != 1) d[k][0] = x, d[k++][1] = 1;
}
LL solve(LL p, LL n) {
    if (!n) return 1;
    if (p == 1) return (1 + p) % MOD;
    LL t = n / 2, m = (1 + pmod(p, t + 1)) % MOD;
    if (n & 1) return m * solve(p, t) % MOD;
    return (m * solve(p, t - 1) + pmod(p, t)) % MOD;
}
int main() {
    LL a, b;
    scanf("%I64d %I64d", &a, &b);
    if (!a) puts("0");
    else if (!b) puts("1");
    else {
        k = 0;
        prime_factor(a);
        LL ans = 1;
        for (int i = 0; i < k; i++) ans = ans * solve(d[i][0], d[i][1] * b) % MOD;
        printf("%I64d\n", ans);
    }
    return 0;
}

直接转化取模、转换相乘:

#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const LL MOD = 9901;
const int N = 30;
int k;
LL mulmod(LL a, LL b, LL mod) {
    a %= mod, b %= mod;
    LL res = 0;
    while (b) {
        if (b & 1) {
            res += a;
            if (res >= mod) res -= mod;
        }
        a <<= 1; b >>= 1;
        if (a >= mod) a -= mod;
    }
    return res;
}
LL pmod(LL n, LL a, LL mod) {
    LL ans = 1;
    while (n) {
        if (n & 1) ans = mulmod(ans, a, mod);
        a = mulmod(a, a, mod);
        n >>= 1;
    }
    return ans;
}
LL d[N][2];
LL prime_factor(LL x) {
    LL up = sqrt(x + 0.5);
    for (LL i = 2; x != -1 && i <= up; i++) {
        if (x % i == 0) {
            int cnt = 0;
            while (x % i == 0) x /= i, cnt++;
            d[k][0] = i, d[k++][1] = cnt;
        }
    }
    if (x != 1) d[k][0] = x, d[k++][1] = 1;
}
LL solve(LL n, LL a) {
    LL m = MOD * (a - 1);
    LL t = (pmod(n, a, m) - 1) % m;
    if (t < 0) t += m;
    return t / (a - 1);
}
int main() {
    LL a, b;
    scanf("%I64d %I64d", &a, &b);
    if (!a) puts("0");
    else if (!b) puts("1");
    else {
        prime_factor(a);
        LL ans = 1;
        for (int i = 0; i < k; i++) ans = ans * solve(d[i][1] * b + 1, d[i][0]) % MOD;
        printf("%I64d\n", ans);
    }
    return 0;
}



 (3)、计算方法(二分)

1、  poj1905 Expanding Rods

题意:一根两端固定在两面墙上的杆 受热弯曲后变弯曲,求前后两个状态的杆的中点位置的距离。
分析:这题有毒!!!未知量太多无法用公式计算,我们枚举半径或者角度或者答案h,但是有单调性,我们可以二分枚举。这题动不动就wa,不知道错了多少次才A,换了各种二分判断方式,如果我们枚举角度的话,那么中间二分判断的时候计算量太大一般会wa…..所以判断的时候计算公式要尽量简单。。。。二分半径和h比二分角度错误的可能性小一些。。。刚开始没看清题目条件,L’最多到1.5L,在二分角度的时候上界取小了。。。
以前浮点数的比较一般都用EPS,这题比较时精度EPS到1e-12都错,真是无语。。直接比较反而不会错。。。。
至于公式如何推导就不多说了,用的公式不同,二分的方式也不同,比如说余弦定理、正弦定理、等比式、勾股定理、三角函数等等。

 

#include 
#include 
using namespace std;

int main() {
    double l, n, c;
    while (~scanf("%lf %lf %lf", &l, &n, &c)) {
        if (l < 0) break;
        double le = 0, ri = acos(-1.0), t = 1 + n * c;
        for (int i = 1; i <= 50; i++) {
            double m = (ri + le) / 2;
            t * sin(m)  <= m ? ri = m : le = m;
        }
        printf("%.3f\n", l/2 * tan(ri/2));
    }
    return 0;
}

2、  poj3258 River Hopscotch

题意:一条宽为L的河,有n个石头,然后河的左端为位置0点,右端为位置L点,给定n个石头每个石头的位置,现在要从左端跳至河的右端,只能从一个石头跳至另一个石头上面,然后我们可以去除河中最多m个石头,求去除石头之后一次跳跃的最短距离的最大值是多少?
分析:二分最大化最小值。判断的明显就是去掉的石头的数量,采取贪心的策略,对于当前假定的距离d,从河的左端开始,每次把离当前的石头的距离小于d的全部去掉,然后跳至刚距离刚好大于d的那个石头,这样一直判断下去直到跳至河的右端,采取这样的贪心策略所跳的次数一定最多,然后去掉的石头数量当然也是最少的,如果此时去掉的石头数量仍然大于m的话,证明这个距离d不可行,而且比d大的也绝对不可行,所以缩小d,否则的话增大d。一直二分直到找到最大的可能的d为止。
这题其实有点难以理解。。。。

#include 
#include 
#include 
using namespace std;

const int N = 50010;
int x[N];
int l, n, m;

bool cal(int d) {
    int cnt = 0, pre = 0;
    for (int i = 1; i <= n; i++) {
        if (x[i] - x[pre] < d) cnt++;
        else pre = i;
    }
    return cnt <= m;
}
int main() {
    scanf("%d %d %d", &l, &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", x + i);
    x[++n] = l;
    sort(x, x + n);
    int le = 0, ri = l + 1;
    while (ri - le > 1) {
        int d = (ri + le) / 2;
        cal(d) ? le = d : ri = d;
    }
    printf("%d\n", le);
    return 0;
}

3、  poj3273 Monthly Expense

题意:给N个数,划分为M个块(每个块中的数是连续的)。找到一个最好的划分方式,使得块中的最大值最小。
分析:二分最小化最大值,常见的优化目标。同样是贪心的判断方式,从第一个数开始,每次选择尽量多的数放在一块,但原则是块中数的和不大于枚举的数,如果最后块的数目大于m,那么肯定不可行,则向上二分枚举的数,否则向下二分找更小的。

#include 
#include 
#include 
using namespace std;

const int N = 100010;
int sum[N];
int m, n;
bool cal(int d) {
    int cnt = 1, pre = 1;
    for (int i = 1; i <= n; ) {
        if (sum[i] - sum[pre-1] > d) {
            pre = i;
            if (++cnt > m) return 0;
        }
        else i++;
    }
    return 1;
}
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        int num;
        scanf("%d", &num);
        sum[i] = sum[i-1] + num;
    }
    int r = sum[n], l = 0;
    while (r - l > 1) {
        int d = (r + l) / 2;
        cal(d) ? r = d : l = d;
    }
    printf("%d\n", r);
    return 0;
}

4、  poj3122 Pie

题意:作者要开一个生日party,他现在拥有n块高度都为1的圆柱形奶酪,已知每块奶酪的底面半径为r不等,作者邀请了f个朋友参加了他的party,他要把这些奶酪平均分给所有的朋友和他自己(f+1人),每个人分得奶酪的体积必须相等(这个值是确定的),形状就没有要求。现在要你求出所有人都能够得到的最大块奶酪的体积是多少?
分析:假定一个解并判断是否可行。直接二分体积。

 

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int N = 10010;
const double PI = acos(-1);
const double EPS = 1e-6;
double v[N];
int n, f;
bool cal(double m) {
    int cnt = 0;
    for (int i = 0; i < n; i++) cnt += (int)(v[i] / m);
    return cnt < f;
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        scanf("%d %d", &n, &f);
        f++;
        double r = 0, l = 0;
        for (int i = 0; i < n; i++) {
            double ri;
            scanf("%lf", &ri);
            v[i] = PI * ri * ri;
            r += v[i];
        }
        while (r - l >= EPS) {
            double m = (r + l) / 2;
            cal(m) ? r = m : l = m;
        }
        printf("%.4f\n", l);
    }
    return 0;
}


总结:本专题为初期数学部分,大概分为数论和二分。
以下是本专题设计到的一些内容:
1、 数论:
(1) 素数
数论中的一个最基本概念,但应用却很常见,一般有以下几个应用:
1、素数判定:有简单的sqrt(n)素性测试,也有复杂的大素数Miller_Rabin 算法判定,如果要用到某个范围内的大量素数,那么使用筛法,而且关键的一点是素数筛法的思想应用很多。
2、唯一分解定理表示:对于每个整数n,都可以唯一分解成素数的乘积。这个可以sqrt(n)实现。注意因子和、因子个数的求法。因子和一般会取模,如果采用等比数列的和求逆元实现,那么需要注意取模的数与分母是互质的。
求a(mod m)意义下的逆元,要求a与m互质,否则不存在乘法逆元,求逆元可以采用以下方法实现:
1、欧拉定理:
a ^ (ϕ(m)−1)为a的逆元,ϕ(m)为m的欧拉函数值。
2、 拓展欧几里得:
ax + m⋅y = 1,求得x即为a的逆元。
3、 2、根据费马小定理:若(a,p)互质,且p为质数:则 a ^ (p–1)= 1 (% p),所以x = a ^ (p- 2) (% p)。
4、 线性O(n)递推:
预处理1-n关于p的逆元:(n < p)
假设已经预处理了1-i-1的逆元,j的逆元设为F[j]
令p = x * i –y ( 0 < y < i)
X* i = y (mod p)
X* F[y] * i = y * F[y] = 1(mod p),
所以i的逆元是F[i] = X* F[y]
这样就可以O(n)的时间预处理了。

至于因子和的公式这是需要记住的:
n = p1^e1 * p2^e2 * …*pn^en.那么因子和s = g(p1,e1) +g(p2,e2) + g(p3, e3) + ….。 g(p, e) = (p^(e+1) – 1) / (p – 1) = (1+p+p^2+p^3+...p^e)。
因子个数知道所有素因子的指数之后可以通过乘法原理得出。

(2) 同余模运算
给定一个正整数p,任意一个整数n,一定存在等式n = kp + r; 其中k、r是整数,且满足0 <= r < p,
模运算:a % p(a mod p)的公式:
模p加法:(a + b) % p = (a%p + b%p) % p
模p减法:(a - b) % p = (a%p - b%p) % p
模p乘法:(a * b) % p = ((a % p)*(b % p))% p
幂模p : (a^b) % p = ((a % p)^b) % p
模p除法:(a / b) %p = (a % (b * p)) / b % p。(要看a % (b * p)是否能整除b)。
(a / b) % p = (a* b^-1) % p,b^-1这里指b的逆元。
模运算的一般应用在大整数取模还有各种因为数太大而对结果取模的计算中,尝尝结合快速幂。模运算还常常与循环节有关。

(3) 欧几里得
1、 最大公约数gcd(a,b) = gcd(b, a % b)。gcd(a, 0) = a。
2、 拓展欧几里得:设ab的最大公约数为g,拓展欧几里得用来求ax + by = g的一组解(x, y),对于方程ax + by = c,如果c % g == 0,那么一组解是(x * c/g, y * c / g),否则为整数解,如果一组解表示为(x0, y0),那么任意解可表示为(x0 + k * b / g, y0 – k * a / g)。
3、 拓展欧几里得应用一般有一下几点:求解不定方程、求解逆元、求解模线性方程。

(4) 计数基础
乘法原理、加法原理、排列组合是最基本的计数技巧,组合中的杨辉三角很重要,可以O(n^2)求出1到
n中所有的组合数。如果只需要第n行的组合数,可以简单地通过C(n, k) = C(n, k-1) * (n – k+ 1 ) / k,注意计算时保证先算乘法再算除法,否则可能会因为(n – k+ 1 ) 不能被k整除导致结果错误。
同样地,还有多重集的排列、组合,环形排列等等。
多重集排列:设多重集S={n1*a1,n2*a2,….nk*ak},且n=n1+n2+n3+…+nk,则s的排列数为n!/(n1!*n2!*…nk!)。
设多重集s = (inf * a1, inf * a2, …..inf * an),inf表示这个元素可以无限取。
则s的r组合数是C(k + r – 1, r)。
N元集合的环形r排列是n! / (r *(n – r) !)。

(5) 递推
递推是实现dp的方式。关键在于状态的定义以及找到阶段之间的联系,也就是状态转移方程,状态的定义靠经验和灵感。不过递推和组合联系紧密。

2、二分
关于二分就不多说了,之前做过总结,单调性问题的神技,实现时注意上下界。

你可能感兴趣的:(数学,poj分类专题训练)