Tutorials for 2014 SWJTU ACM Summer Training Team-PK Contest #1

A题:分段筛法+二分搜索。首先预处理出区间[1, 10^8]中的所有素数,然后对于查询区间[a, b],只需要用区间[1, b]中的素数个数减去区间[1, a - 1]中的素数个数,显然可以二分得到。在筛选素数的过程中,并不是直接筛,而是采用分段筛法,即将区间[1, 10^8]划分为50个不相交的等长区间,分别筛选即可。

参考代码:

//#define SPJ
#ifdef SPJ
#include <bits/stdc++.h>
#else
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#endif
#define dbg(x) cout << #x << " = " << x << endl
#define dbg2(x,y) cout << #x << " = " << x << ", " << #y << " = " << y << endl
#define dbg3(x,y,z) cout << #x << " = " << x << ", " << #y << " = " << y << ", " << #z << " = " << z << endl
#define out(x) cout << (x) << endl
#define out2(x,y) cout << (x) << " " << (y) << endl
  
int prime[6000000]; int tot_prime = 0;

bool vst[2000000 + 10];

void Get_prime_1(int M)
{/*
    memset(vst, 0, sizeof(vst));
    int i, j, k, Sq = 1 + (int)sqrt((M << 1) + 1.0);
    for(i = 3; i <= Sq; i += 2) {
        if( vst[i >> 1] ) continue;
        for(j = i * i, k = (i << 1); j <= ((M << 1) | 1); j += k)
            vst[j >> 1] = 1;
    }
    for(i = 1; i <= M; i ++)
        if( !vst[i] )
            prime[tot_prime ++] = (i << 1) | 1;*/
    
    int i, j;
    memset(vst, 0, sizeof(vst));
    for(i = 3; i <= M; i += 2) {
        if( vst[i] ) continue;
        prime[tot_prime ++] = i;
        for(j = i << 1; j <= M; j += i)
            vst[j] = 1;
    }
}

void Get_prime_2(int l, int r)
{/*
    memset(vst, 0, sizeof(vst));
    int i, j, k;
    for(i = 1; prime[i] * prime[i] <= ((r << 1) | 1); i ++) {
        j = ((l << 1) | 1) / prime[i]; if( j % 2 == 0 ) j ++;
        j = j * prime[i];
        for(k = prime[i] << 1; j <= ((r << 1) | 1); j += k)
            vst[(j >> 1) - l] = 1;
    }
    for(i = l; i <= r; i ++) {
        if( !vst[i - l] && tot_prime < 5900000 )
            prime[tot_prime ++] = ((i << 1) | 1);
    }*/
    
    memset(vst, 0, sizeof(vst));
    int i, j;
    for(i = 0; prime[i] * prime[i] <= r; i ++) {
        j = l / prime[i] * prime[i];
        if( j < l ) j += prime[i];
        for(; j <= r; j += prime[i]) vst[j - l] = 1;
    }
    for(i = l; i <= r; i ++) {
        if( !vst[i - l] ) {
            prime[tot_prime ++] = i;
        }
    }
}

void init()
{
    tot_prime = 0; prime[tot_prime ++] = 2;
    Get_prime_1(2000000);
    //printf("1: tot_prime = %d\n", tot_prime);
    for(int i = 2; i <= 50; i ++) {
        Get_prime_2(2000000 * (i - 1) + 1, 2000000 * i);
        //printf("tot_prime = %d\n", tot_prime);
    }
    //dbg(tot_prime);
}

int main()
{   
    //freopen("data.in", "r", stdin);
    //freopen("data.out", "w", stdout);
    init(); int N, a, b, l, r, m, pos, pos_2; 
    while( scanf("%d %d", &a, &b) == 2 ) {
        N = b;
        l = 0, r = tot_prime - 1, pos = -1;
        while( l <= r ) {
            m = (l + r) >> 1;
            if( prime[m] <= N ) pos = m, l = m + 1;
            else r = m - 1;
        }
        
        N = a - 1;
        l = 0, r = tot_prime - 1, pos_2 = -1;
        while( l <= r ) {
            m = (l + r) >> 1;
            if( prime[m] <= N ) pos_2 = m, l = m + 1;
            else r = m - 1;
        }
        printf("%d\n", (pos + 1) - (pos_2 + 1));
    }
    return 0;
}

B题:线段树+动态规划。容易推导出动态规划递推表达式,即DP[i] = max{DP[j] + A[i]}, (A[j] < A[i], 1 <= j < i)。但是这样做复杂度相当高。此时,我们需要优化,我们可以先对数组的值进行非递减排序,相同的值按照索引的大小排序(这一点很有必要)。然后,建立以索引为区间的线段树,进行动态更新和查询。

参考代码:

//#define SPJ
#ifdef SPJ
#include <bits/stdc++.h>
#else
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#endif
#define dbg(x) cout << #x << " = " << x << endl
#define dbg2(x,y) cout << #x << " = " << x << ", " << #y << " = " << y << endl
#define dbg3(x,y,z) cout << #x << " = " << x << ", " << #y << " = " << y << ", " << #z << " = " << z << endl
#define out(x) cout << (x) << endl
#define out2(x,y) cout << (x) << " " << (y) << endl
  
const int maxN = 100000 + 2;

typedef long long i64d;

struct Seq
{
    int idx; i64d val;
    inline void in(int &_idx) { scanf("%I64d", &val); idx = _idx; }
    inline bool operator<(const Seq &s) const {
        if( val != s.val ) return val < s.val;
        return idx > s.idx;
    }
};
Seq A[maxN]; int nA;

struct node
{
    int l, r; i64d val;
};
node tree[maxN * 5];

void build(int l, int r, int idx)
{
    tree[idx].l = l; tree[idx].r = r;
    tree[idx].val = 0LL;

    if( l == r ) return ;

    int Mid = (l + r) >> 0x1;
    build(l, Mid, idx << 0x1); build(1 + Mid, r, (idx << 0x1) + 1);
}

void update(int idx, int &ip, i64d &val)
{
    if( tree[idx].l == tree[idx].r ) {
        tree[idx].val = val; return ;
    }

    int Mid = (tree[idx].l + tree[idx].r) >> 0x1;
    if( ip <= Mid ) update(idx << 0x1, ip, val);
    else update(1 + (idx << 0x1), ip, val);

    tree[idx].val = max(tree[idx << 0x1].val, tree[(idx << 0x1) + 1].val);
}

i64d query(int l, int r, int idx)
{
    if( l == tree[idx].l && tree[idx].r == r )
        return tree[idx].val;

    int Mid = (tree[idx].l + tree[idx].r) >> 0x1;

    if( r <= Mid ) return query(l, r, idx << 0x1);
    else if( Mid < l ) return query(l, r, (idx << 0x1) + 1);
    else return max(query(l, Mid, idx << 0x1), query(1 + Mid, r, (idx << 0x1) + 1));
}

i64d dp[maxN];

int main()
{
    //freopen("data.in", "r", stdin);
    //freopen("data.out", "w", stdout);
    while( scanf("%d", &nA) == 1 ) {
        for(int i = 0; i < nA; ++i) A[i].in(i);
        sort(A, A + nA);
        build(0, nA - 1, 1);
        i64d tmp;
        for(int i = 0; i < nA; ++i) {
            tmp = query(0, A[i].idx, 1);
            //printf("tmp = %lld, idx = %d\n", tmp, A[i].idx);
            dp[i] = A[i].val + tmp;
            update(1, A[i].idx, dp[i]);
        }

        i64d ans = dp[0];
        for(int i = 1; i < nA; ++i) if( dp[i] > ans ) ans = dp[i];

        printf("%I64d\n", ans);
    }
    return 0;
}

C题:推导题。这题有同学预先对字符串排序,失策没有卡死。实际上只需要统计每个字符的频率,然后从a到z获取频率为奇数的字符,并把得到的字符串中的最后一个字符去掉,就是答案(如果是空串就输出0)。

//#define SPJ
#ifdef SPJ
#include <bits/stdc++.h>
#else
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#endif
#define dbg(x) cout << #x << " = " << x << endl
#define dbg2(x,y) cout << #x << " = " << x << ", " << #y << " = " << y << endl
#define dbg3(x,y,z) cout << #x << " = " << x << ", " << #y << " = " << y << ", " << #z << " = " << z << endl
#define out(x) cout << (x) << endl
#define out2(x,y) cout << (x) << " " << (y) << endl

const int maxN = 1000000 + 10;

char str[maxN];
int cnt[26];

int main()
{
    //freopen("data.in", "r", stdin);
    //freopen("data.out", "w", stdout);
    int i, j;
    while( scanf("%s", str) == 1 ) {
        memset(cnt, 0, sizeof(cnt));
        for(i = 0; str[i]; i ++)
            cnt[ str[i] - 'a' ] ++;
        for(i = 25; i >= 0; i --)
            if( cnt[i] & 1 ) {
                cnt[i] ++;
                break;
            }
        j = 0;
        for(i = 0; i < 26; i ++)
            if( cnt[i] & 1 ) {
                j = 1;
                putchar('a' + i);
            }
        if( j == 0 ) putchar('0');
        putchar('\n');
    }
    return 0;
}

D题:推导题。有内存限制,意味着我们不能用数组存储,如果发现输入数据是64比特,n的规模又大,意味着我们往往需要使用getchar读入数据。接下来有两种思想,我实现了第2个,不知道有没有人用第1个,或者第1个会被卡掉?

第1种思想:容易在读入数据的过程中,得到3个数,即min(最小的数),max(最大的数),sum(和),显然答案等于 sum - (max - min + 1) * (max + min) / 2,小数组高精度处理即可。

第2种思想:同样的,我们可以在输入的过程中得到3个数,即min(最小的数),max(最大的数),XOR(所有数的异或值),显然答案等于 XOR ^ [min ^ (min + 1) ^ ... ^ max],这里 ^ 表示异或运算。此时问题变成了需要求区间[min, max]所有的数的异或值,这是OJ上的一道陈题(链接),这里简单说下一种求解区间[a, b]所有的数的异或值的解法:

假设x是偶数,那么x ^ (x+1) 就等于1,也即x和(x+1)只有最低位不同。好了,我们会发现区间[a, b]是由大量的这样的x和x+1构成(除了边界情况)。

所以我们只需要从a和b的奇偶性入手即可,这里只考虑其中一种情况,即a是奇数,b是偶数,那么我们总可以把区间[a, b]划分为:

a, (a+1, a+2), (a+3, a+4), ..., (b-2, b - 1), b

显然中间有括号的两个数的异或值为1,假设这样的配对的数有y个,那么答案就是a ^ (y & 1) ^ b,不是吗?(其他情况可以参考着来。)

参考代码:

//#define SPJ
#ifdef SPJ
#include <bits/stdc++.h>
#else
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#endif
#define dbg(x) cout << #x << " = " << x << endl
#define dbg2(x,y) cout << #x << " = " << x << ", " << #y << " = " << y << endl
#define dbg3(x,y,z) cout << #x << " = " << x << ", " << #y << " = " << y << ", " << #z << " = " << z << endl
#define out(x) cout << (x) << endl
#define out2(x,y) cout << (x) << " " << (y) << endl

unsigned long long gao(unsigned long long l, unsigned long long r)
{
    if( l == r )
        return l;
    unsigned long long res = 0;
    if( l & 1LL ) {
        res ^= l;
        l ++;
    }
    if( !(r & 1LL) ) {
        res ^= r;
        r --;
    }
    if( l > r )
        return res;
    unsigned long long last = ((r - l + 1LL) >> 1) & 1LL;
    return (res ^ last);
}

int main()
{
    //freopen("data.in", "r", stdin);
    //freopen("data.out", "w", stdout);
    int n, i;
    unsigned long long mi, ma, res, ans;
    unsigned long long val;
    char ch;
    while( scanf("%d", &n) == 1 ) {
        //scanf("%llu", &val);
        
        while( (ch = getchar()) == ' ' || ch == '\n' ) ;
        val = ch - '0';
        while( (ch = getchar()) >= '0' && ch <= '9' )
            val = val * 10 + (ch - '0');
        ans = mi = ma = val;
        for(i = 1; i < n; i ++) {
            //scanf("%llu", &val);
            
            while( (ch = getchar()) == ' ' || ch == '\n' ) ;
            val = ch - '0';
            while( (ch = getchar()) >= '0' && ch <= '9' )
                val = val * 10 + (ch - '0');
            if( mi > val )
                mi = val;
            if( ma < val )
                ma = val;
            ans ^= val;
        }
        res = gao(mi, ma);
        //dbg3(mi, ma, res);
        printf("%llu\n", (res ^ ans));
    }
    return 0;
}

E题:卡平方题。首先数据量大,请用getchar;其次再去计算每个数出现的次数,此时你可能会用平方(即 k * 100 *100)的复杂度去统计数据,如果输入数据有10^5组小数据,就很危险,可以先预处理,对付10^5组小数据的复杂度消耗是10^8数量级。(下一次可能有一道变型的题,请关注此题。)

参考代码:

//#define SPJ
#ifdef SPJ
#include <bits/stdc++.h>
#else
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#endif
#define dbg(x) cout << #x << " = " << x << endl
#define dbg2(x,y) cout << #x << " = " << x << ", " << #y << " = " << y << endl
#define dbg3(x,y,z) cout << #x << " = " << x << ", " << #y << " = " << y << ", " << #z << " = " << z << endl
#define out(x) cout << (x) << endl
#define out2(x,y) cout << (x) << " " << (y) << endl

const int maxN = 100 + 2;

vector<int> adj[maxN];
int sz[maxN];

int gcd(int a, int b)
{
    if( b == 0 )
        return a;
    return gcd(b, a % b);
}

void init()
{
    for(int i = 100; i >= 1; i --) {
        for(int j = i; j >= 1; j --)
            if( gcd(i, j) == 1 ) {
                adj[i].push_back(j);
            }
        sz[i] = (int)adj[i].size();
    }
}

int n, val, i, j, k;
long long times[maxN];
long long res, tmp;
char ch;

int main()
{
    //freopen("data.in", "r", stdin);
    //freopen("data.out", "w", stdout);
    init();
    while( scanf("%d", &n) == 1 ) {
        memset(times, 0, sizeof(times));
        k = 0;
        for(i = 0; i < n; i ++) {
            //scanf("%d", &val);
            while( (ch = getchar()) == ' ' || ch == '\n' ) ;
            val = ch - '0';
            while( (ch = getchar()) >= '0' && ch <= '9' )
                val = val * 10 + (ch - '0');
            times[val] ++;
            if( k < val ) k = val;
        }
        res = times[1] * times[1];
        for(i = k; i > 1; i --) {
            if( !times[i] ) continue;
            tmp = 0LL; times[i] <<= 1;
            for(j = 0; j < sz[i]; j ++)
                tmp += times[ adj[i][j] ];
            res += times[i] * tmp;
        }
        printf("%lld\n", res);
    }
    return 0;
}

(备注:所有比赛中都是解出0题最后的实习成绩并不一定就是不及格。还有其他成绩,比如100道题要求、出勤分等等,完成了集训的定量基本要求,肯定及格,不必太担心。)

你可能感兴趣的:(Trainning)