• POJ 基础数学


    数学

    组合数学

    POJ3252,poj1850,poj1019,poj1942

    数论

    poj2635, poj3292,poj1845,poj2115

    计算方法(二分)

    poj3273,poj3258,poj1905,poj3122

      

       

       

              

                  

                       

                                     

    组合数学

    poj 3252

    题意:如果一个数是round number,则它的二进制表示中,0的个数大于等于1的个数。现在给一段数据范围[start, end]。求这一段数中round number的个数.(1 <= start < end <= 2,000,000,000)

    思路:暴力肯定会挂掉。因为是二进制表示,所以可以把结果“凑”出来。

    比如数据a用二进制表示有5位,那么1到4位中的round number是可以求出来的。结果为getNum(end) - getNum(start - 1);

    一、如求4位二进制表示的数中round number的个数。第一位肯定是1。后边还有三位,不是0就是1。设后三位中有x 个0则共有的情况数为 C(3, x)。因为要满足(0的个数) >= (1的个数)。所以这里x可以取2,3;

    二、还剩下五位二进制的情况。比如数据 11010.我们可以从左向右第2位开始。如果遇到1。则将其当成0,然后后边几位任意取,求符合条件的情况数。也就是说11010改成10xxx。实现方法是统计前边有多少个0和1.然后可以知道后边不确定的部分还至少需要多少个0才可以满足条件。

     

    ps:2,000,000,000二进制共有31位。本菜开了一个20的数组,然后wa了一下午!-_-! 本菜一下午的心血,代码无任何参考!

    渣代码

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>

    using namespace std;

    const int N = 50;

    int aug[N][N];
    int bit[N];
    int s, e;

    int C(int n, int m) { //dp求组合数,c[n][m] = c[n-1][m] + c[n-1][m-1];
    if(n == 0 || m == 0 || n == 1 || m == n) return 1;
    if(aug[n][m]) return aug[n][m];

    aug[n - 1][m] = C(n - 1, m);
    aug[n - 1][m - 1] = C(n - 1, m - 1);

    return aug[n - 1][m] + aug[n - 1][m - 1];
    }

    int solve(int n) {
    int ans, bt, t;
    int i, j, x;

    memset(bit, 0, sizeof(bit));
    ans = 1; bt = 0; t = n;

    while(t) {bit[++bt] = (t&1); t >>= 1;} //bt表示n共有多少位,bit[]记录n的二进制表示
    for(i = 2; i < bt; ++i) { //统计[1,bt)位中所有的情况数
    t = i - 1;
    if(t&1) {
    for(j = t/2 + 1; j <= t; ++j)
    ans += C(t, j);
    } else {
    for(j = (t + 1)/2 + 1; j <= t; ++j) {
    ans += C(t, j);
    }
    }
    }

    int a = 1, b = 0; //a表示当前取到的数中1的个数,b表示当前取到的数中0的个数
    for(i = bt - 1; i > 0; --i) { //统计二进制有bt位时的情况数
    if(bit[i]) {
    t = i - 1; //t表示后边可以自由组合的位数
    x = t + a - b - 1; //设x为后边至少需要的0的个数,x + b + 1 = t - x + a,x = (t + a - b - 1)/2
    if(x < 0) x = 0; //x可能为负,影响最后结果。本菜在这里wa了一次!

    if(x&1) x = (x + 1)/2; //奇偶性不同会有区别,自己在纸上画画
    else x /= 2;

    for(j = x; j <= t; ++j) {
    ans += C(t, j);
    }
    ++a;
    } else ++b;
    }
    if(b >= a) ans++; //判断n是不是round number

    return ans;
    }

    int main() {
    freopen("data.in", "r", stdin);

    memset(aug, 0, sizeof(aug));

    while(~scanf("%d%d", &s, &e)) {
    printf("%d\n", solve(e) - solve(s - 1));
    }
    return 0;
    }


    poj 1850 (31/03)

        昨天晚上回去神神叨叨的想了一路,回宿舍写了半天没写出来。今天上午上课时才想起来怎么写。

    思路: 先取给出序列的长度n。小于n的长度的序列排列方式共有sum(C(26, i)) (1 <= i < n)。等于n长度的序列。确定两个相邻的字符中间可以有取到多少个字符。然后从剩下可取的字符中选字符补上。

    比如序列: adgi,长度n = 4. ans += C(26, 1) + C(26, 2) + C(26, 3).

    a 和d中间可以放bc。如果放b,后边两位还有C(24, 2)中选择,如果是c,后边还有C(23, 2)种选择。

    dg,gi同理。注意,第一个字符a处理有些特殊,因为不用考虑前边有什么,所以直接从1到s[0] - 'a' 就行。

    渣代码:

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>

    using namespace std;

    const int N = 100;

    int aug[N][N];
    char s[N];

    int C(int n, int m) {
    if(n == 0 || m == 0 || n == 1 || n == m) return 1;
    if(aug[n][m]) return aug[n][m];

    aug[n - 1][m] = C(n - 1, m);
    aug[n - 1][m - 1] = C(n - 1, m - 1);

    return aug[n - 1][m] + aug[n - 1][m - 1];
    }

    int main() {
    //freopen("data.in", "r", stdin);

    int n, i, j, ans;
    int x, y, flag;
    while(~scanf("%s", s)) {
    n = strlen(s);
    ans = flag = 0;

    for(i = 1; i < n && !flag; ++i) {
    if(s[i] < s[i - 1]) flag = 1;
    }
    if(flag) {puts("0"); continue;}
    for(i = 1; i < n; ++i) {
    ans += C(26, i);
    }
    for(i = 0; i < n; ++i) {
    i ? x = s[i - 1] - 'a' + 1 : x = 0;
    y = s[i] - 'a' + 1;
    for(j = x + 1; j < y; ++j) {
    ans += C(26 - j, n - i - 1);
    }
    }
    printf("%d\n", ans + 1);
    }
    return 0;
    }


    POJ 1019

    感觉数学题搞起来不是那么轻松,虽然这是道水题。。。

    思路:先把1到X共有几位数存起来,用n连续减掉为整的数X所占的位数。 余下的数从1开始凑,直到大于n。然后去掉多出n的部分然后取最后一位(% 10)。

    ps:貌似说的我都有点看不懂。。。看代码吧

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>

    using namespace std;

    const int N = 40000;

    int s[N];

    int bit(int x) {
    int k = 0;
    while(x) {
    x /= 10;
    ++k;
    }
    return k;
    }

    void init() {
    int i;
    s[0] = 0;
    for(i = 1; i < N; ++i) {
    s[i] = s[i - 1] + bit(i);
    }
    }

    int main() {
    //freopen("data.in", "r", stdin);

    int n, t, i, l, ans;
    init();
    scanf("%d", &t);
    while(t--) {
    scanf("%d", &n);
    i = 0;
    while(n >= s[i]) { //去掉整的位
    n -= s[i];
    i++;
    }
    if(!n) {printf("%d\n", (i - 1)%10); continue;}
    l = 0;
    for(i = 1; l < n; ++i) { //凑出第n位的数i是多少。
    l += bit(i);
    }
    --i;
    ans = i/((int)pow(double(10), l - n)); //去掉多余的位
    printf("%d\n", ans % 10);
    }
    return 0;
    }


    POJ 1942

    《组合数学》(Richard A.Brualdi 机械工业出版社) 8.5 格路径和Schröder 数

    注意求C(n + m, min(n, m))。否则会tle

      

    数论                               

     POJ 2635

           题意:给一个数key,它是两个素数的乘积,然后求较小的那个素数是否小于L,如果是,输出。

     思路: 10^6以内的素数打表,把key换成1000进制,枚举素数进行大整数取模。 如果出现满足条件的则输出。

    第一次打表打错了,我又搓了!

    View Code
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <string>
    #include <cmath>

    using namespace std;

    const int N = 1000;
    const int MAX = 1000010;

    int num[N];
    int prim[MAX];
    bool f[MAX];
    int tol, num_s;
    char s[N];

    void init() {
    int i, j, k;
    tol = 0;
    memset(f, true, sizeof(f));
    k = 1;
    for(i = 2; i < MAX; ++i) {
    if(i*i >= MAX) break;
    for(j = i*i; j < MAX; j += i) {
    f[j] = false;
    }
    }
    for(i = 2; i < MAX; ++i) {
    if(f[i]) prim[tol++] = i;
    }
    }

    int mod(int x) {
    int i, res = 0;
    for(i = num_s - 1; i >= 0; --i) {
    res = (res*1000 + num[i]) % x;
    }
    return res;
    }

    void deal() {
    int i, k, len;
    len = strlen(s);
    memset(num, 0, sizeof(num));
    num_s = 0;
    for(i = len - 1; i >= 0; i -= 3) {
    for(k = max(0, i - 2); k <= i; ++k) {
    num[num_s] = num[num_s]*10 + s[k] - '0';
    }
    ++num_s;
    }
    }

    int main() {
    //freopen("data.in", "r", stdin);

    int l, i;
    bool flag;
    init();
    while(~scanf("%s%d", s, &l)) {
    if(!l) break;
    deal();
    flag = false;
    for(i = 0; i < tol && prim[i] < l && !flag; ++i) {
    if(mod(prim[i]) == 0) {
    printf("BAD %d\n", prim[i]);
    flag = true;
    }
    }
    if(!flag) puts("GOOD");
    }
    return 0;
    }


    POJ 3292

    主要是打表,既然打表就打彻底,否则TLE。。。H-primes的表,然后退出H-simi-primes的表。最后O(1)输出。

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>

    using namespace std;

    const int N = 1000002;

    int prime[N];
    bool vis[N];
    int tol;

    void init() {
    memset(vis, true, sizeof(vis));
    int i, j;
    for(i = 5; i < N; i += 4) {
    if(i*i > N) break;
    if(!vis[i]) continue;
    for(j = i*i; j < N; j += i) {
    vis[j] = false;
    }
    }
    tol = 0;
    for(i = 1; i < N; i += 4) {
    if(vis[i]) prime[tol++] = i;
    }
    int t;
    memset(vis, false, sizeof(vis));

    for(i = 1; i < tol; ++i) {
    if(prime[i]*prime[i] > N) break;

    for(j = i; j < tol; ++j) {
    t = prime[i] * prime[j];
    if(t > N) break;
    vis[t] = true;
    }
    }
    memset(prime, 0, sizeof(prime));
    for(i = 1; i < N; ++i) {
    prime[i] = prime[i - 1];
    if(vis[i]) prime[i]++;
    }
    }

    int main() {
    //freopen("data.in", "r", stdin);

    init();
    int n;
    while(scanf("%d", &n), n) {
    printf("%d %d\n", n, prime[n]);
    }
    return 0;
    }


    POJ 1845

    忘算了一种情况,wa了一晚上!-_-!

    对A分解质因子,A = a1^x1 + a2^x2 + ... + an^xn

    则A的所有因子之和为 sum(A) = {1+ a1 + a1^2 + .. a1^x1}*{1 + a2 + a2^2 + ... + a1^x2}* ... * {1 + an + an^2 + an^3 + ... + an^xn}   //怎么证明的,不知道!找了半天反例没找出来

    A^B即为 A^B = a1^(x1*B) + a2^(x2*B) + ... an ^ (xn*B);

    求一次1+ a1 + a1^2 + .. a1^x1可以用二分的思想 + 快速幂取模

    如果x1为奇数 则 (a1^(x1/2) + 1)* (1 + a1 + ... + a1^(x1/2));

    如果x1为偶数 则 (a1^(x1 - 1/2) + 1)* (1 + a1 + ... + a1^(x1 - 1/2)) + a1^x1;

    View Code
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #define mod(x) x%9901

    using namespace std;

    typedef long long ll;

    const int MAXN = 8000;

    int prime[MAXN], tol;
    bool vis[MAXN];

    void init() {
    int i, j;
    tol = 0;
    memset(vis, false, sizeof(vis));
    for(i = 2; i < MAXN; ++i) {
    for(j = i*i; j < MAXN; j += i) {
    vis[j] = true;
    }
    }
    for(i = 2; i < MAXN; ++i) {
    if(!vis[i]) prime[tol++] = i;
    }
    }

    ll exp_mod(ll a, ll b) {
    ll res = 1;
    a = mod(a);
    while(b) {
    if(b&1) {
    res = mod(res * a);
    }
    a = mod(a * a);
    b >>= 1;
    }
    return res;
    }

    ll sum(ll a, ll b) {
    if(b == 0) return 1;
    if(b&1) return mod((exp_mod(a, b/2 + 1) + 1) * sum(a, b/2));
    return mod(mod((exp_mod(a, (b-1)/2 + 1) + 1) * sum(a, (b-1)/2)) + mod(exp_mod(a, b)));
    }

    int main() {
    //freopen("data.in", "r", stdin);

    int i, a, b;
    ll ans, x;
    init();
    while(~scanf("%d%d", &a, &b)) {
    ans = 1;
    for(i = 0; i < tol && prime[i] <= a; ++i) {
    x = 0;
    while(a % prime[i] == 0) {
    x ++;
    a /= prime[i];
    }
    if(!x) continue;
    ans = mod(ans * sum(prime[i], x*b));
    }
    if(a > 1) ans = mod(ans * sum(a, b));
    printf("%lld\n", ans);
    }
    return 0;
    }



      POJ 2115

         题目的意思是求 a + c*x = b(mod n) , 可以转换成c*x = (b - a) (mod n)。经典的模线性方程

    cx + ny = d; d为c,n的最大公约数。

    如果b%d != 0 输出 FOREVER

    因为d = cx + ny;

       d%n = cx%n;

    又因为 b | d

      b%n = d*(b/d)%n = cx(b/d)%n;

    所以方程b % n = cx0%n的一个解为x0 = x*(b/d)%n(题目要求余数)。

    因为n | d,所以所求的余数每d个一次循环,为了保证x0不为负数。ans = (x0 + n/d)%(n/d);

    ps:《算法导论》上有各种证明,这题要用long long, 并且long long(1) << k;

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>

    using namespace std;

    typedef long long ll;

    ll exp_gcd(ll a, ll b, ll& x, ll& y) {
    if(b == 0) {
    x = 1; y = 0;
    return a;
    }
    ll p = exp_gcd(b, a%b, x, y);
    ll tmp = x;
    x = y; y = tmp - (a/b)*y;
    return p;
    }

    void mod_equ(ll a, ll b, ll n) {
    ll x, y;
    ll d = exp_gcd(a, n, x, y);
    if(b%d != 0) {puts("FOREVER"); return ;}
    x = x*(b/d)%n;
    n = n/d;
    if(n < 0) n = -n;
    printf("%lld\n", (x%n + n)%n);
    }

    int main() {
    //freopen("data.in", "r", stdin);

    ll a, b, c;
    int k;
    while(~scanf("%lld%lld%lld%d", &a, &b, &c, &k)) {
    if(!a && !b && !c && !k) break;
    mod_equ(c, b - a, ll(1) << k);
    }
    return 0;
    }

    计算方法(二分)


    POJ 3273

    刚想写dp,一看数据范围,直接无语了。看到discuss里有人说用二分,不得不佩服二分的强大。Orz

    以sum(day[i])为上界R,max(day[i])为下届L,得到一个mid。算出mid为限制时共分成了多少块(m),如果if( m < M ) R = mid - 1; else L = mid + 1; 详见代码                           

    View Code
    // 110+ ms 1Y ^^

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <cmath>

    using namespace std;

    const int inf = ~0u>>2;
    const int MAXN = 100007;

    int a[MAXN], N, M;

    int num(int mid) {
    int s = 0, m = 0, i;
    for(i = 0; i < N; ++i) {
    if(s + a[i] <= mid) {
    s += a[i];
    } else {
    s = a[i]; m ++;
    }
    }
    return m;
    }

    int main() {
    //freopen("data.in", "r", stdin);

    int md, sum, i;
    while(~scanf("%d%d", &N, &M)) {
    sum = 0; md = - inf;
    for(i = 0; i < N; ++i) {
    scanf("%d", &a[i]);
    md = max(md, a[i]);
    sum += a[i];
    }
    int l = md, r = sum, mid;
    while(l < r) {
    mid = (l + r) >> 1;
    if(num(mid) < M) r = mid - 1;
    else l = mid + 1;
    }
    printf("%d\n", l);
    }
    return 0;
    }


    POJ 1905

    公式很容易推出来,可是我把公式写复杂了。最后把精度都搞砸了。。。T_T

    设要求的高度为x。弧的半径为r

    (r - x)^2 + (l/2)^2 = r^2;

    r = (x^2 + (l/2)^2)/(2*x);

    r-x,跟r 所夹的角度为o = asin((l/2)/r);

    以x求出的弧长为L0 = 2*o*r; L0与L‘比较,如果小于则增大x,如果大于则减小x。(二分枚举x的值)

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>

    using namespace std;

    const double eps = 1e-8;
    double len, L;

    int cmp(double x) {
    if(x > eps) return 1;
    else if(x < -eps) return -1;
    return 0;
    }

    double cal(double x) {
    double r = ((len/2)*(len/2) + x*x)/(2*x);
    double O = asin(len/(2*r));
    double L0 = 2*O*r;

    if(cmp(L0 - L) >= 0) return 1;
    else return 0;
    }

    int main() {
    //freopen("data.in", "r", stdin);

    double n, c;
    while(~scanf("%lf%lf%lf", &len, &n, &c)) {
    if(cmp(len) < 0 && cmp(n) < 0 && cmp(c) < 0) break;
    L = (1 + n * c) * len;
    double l = 0, r = len, mid, t;
    while(cmp(r - l) > 0) {
    mid = (l + r)/2;
    t = cal(mid);
    if(t == 1) r = mid;
    else l = mid;
    }
    printf("%.3lf\n", l);
    }
    return 0;
    }


    POJ 3122

    题意:已知有N个pie,要分成F + 1份,每份的体积相同(每份必须是完整的,不能是几个小块凑起来的)。现在求每份pie可取的最大体积。

    思路:求出平均体积avg,所求的体积v就肯定在(0, avg] 区间内,二分枚举 v的值,判断以v体积分的话可以分到的快数n,让n与F + 1比较。调整L,R的值。

    ps: eps = 1e-8会tle,1e-6就好。

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>

    using namespace std;

    const int MAXN = 10007;
    const double pi = acos(-1.0);
    const double eps = 1e-6;

    double v[MAXN];

    int cmp(double x) {
    if(x > eps) return 1;
    else if(x < -eps) return -1;
    else return 0;
    }

    int main() {
    //freopen("data.in", "r", stdin);

    int N, F, n, i, T;
    double mid, l, r, t, rad;
    scanf("%d", &T);
    while(T--) {
    scanf("%d%d", &N, &F);
    l = 0; r = 0; ++F;
    for(i = 0; i < N; ++i) {
    scanf("%lf", &rad);
    v[i] = pi*rad*rad;
    r += v[i];

    }
    r = r/F;
    while(cmp(r - l) > 0) {
    mid = (l + r) / 2;
    for(n = 0, i = 0; i < N; ++i) {
    t = v[i];
    while(cmp(t - mid) >= 0) {++n; t -= mid;}
    }
    if(n < F) r = mid;
    else l = mid;
    }
    printf("%.4f\n", l);
    }
    return 0;
    }



                                                        

                                                    

                                                    

                                                          

  • 相关阅读:
    并查集分析+总结
    poj 3083 Children of the Candy Corn(bfs+dfs 数组模拟方向)
    poj 1094 Sorting It All Out (拓扑排序)
    poj 2632 Crashing Robots(模拟)
    poj 1068 Parencodings (模拟)
    poj 1273 Drainage Ditches ( 最大流Edmonds_karp算法)
    poj 3278 Catch That Cow (BFS)
    Codeforces Round #109 (Div. 2) 总结
    poj 2299 UltraQuickSort(归并排序)
    poj 1035 Spell checker(字符串)
  • 原文地址:https://www.cnblogs.com/vongang/p/2422112.html
Copyright © 2020-2023  润新知