2016中国大学生程序设计竞赛CCPC-长春赛区

B. Fraction(传送门)

题意

给你两个数组,进行如下运算:
这里写图片描述

解题思路

直接递归模拟一下就可以了

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#define FIN freopen("input.txt", "r", stdin)
#define FOUT freopen("output.txt", "w", stdout)
using namespace std;
typedef pair<int, int> PII;
const int MAXN = 15;
int n;
int A[MAXN], B[MAXN];

int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}



PII DFS(int x) {
    if(x >= n + 1) {
        return PII(0, 1);
    }
    PII v = DFS(x + 1);
    int up = v.first;
    int down = v.second;
    up += A[x] * down;
    int nup = down * B[x];
    int ndown = up;
    int gc = gcd(nup, ndown);
    nup /= gc;
    ndown /= gc;
    return PII(nup, ndown);
}
int main() {
    //FIN;
    //FOUT;
    int _;
    int cas = 1;
    scanf("%d", &_);
    while(_ --) {
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &A[i]);
        }
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &B[i]);
        }
        PII p = DFS(1);
        printf("Case #%d: %d %d\n", cas ++, p.first, p.second);
    }
    return 0;
}

D.Triangle(传送门)

题意

给你 1..n 长的棍子,让你去掉这 n 个中的x个棍子,让这些棍子无法组成一个三角形,求出最少需要去掉多少根

解题思想

直接二分+DFS暴力,通过题意可以知道,去掉任意x根不能组成三角形,去掉任意 x+1 根一定不能构成三角形,所以这个关系是积性的,所以可以二分,如果我们选了 x 根棍子,然后我们就要针对剩余的nx个进行判断是否可以组成三角形,这里直接DFS暴力枚举就可以了

代码

#include <cstdio>
#include <cstring>
#include <algorithm>


#define FIN freopen("input.txt", "r", stdin)
#define FOUT freopen("output.txt", "w", stdout)
using namespace std;
typedef pair<int, int> PII;
const int MAXN = 20 + 5;
int n;

bool issuc;
int path[MAXN];
int kpath[MAXN];
void DFS(int u, int num, int sz) {
    if(sz == num) {
        memset(kpath, 0, sizeof(kpath));
        for(int i = 0; i < num; i ++) kpath[path[i]] ++;
        for(int i = 1; i <= n; i ++) {
            if(kpath[i]) continue;
            for(int j = i + 1; j <= n; j ++) {
                if(kpath[j]) continue;
                for(int k = j + 1; k <= n; k ++) {
                    if(kpath[k]) continue;
                    if(i + j > k && i + k > j && k + j > i){
                         return;
                    }
                }
            }
        }
        issuc = true;
        return;
    }
    for(int i = u + 1; i <= n; i ++) {
        path[num] = i;
        DFS(i, num + 1, sz);
    }
}

bool C(int m) {
    issuc = false;
    DFS(1, 0, m);
    return issuc;
}

int main() {
    //FIN;
    //FOUT;
    int _;
    int cas = 1;
    scanf("%d", &_);
    while(_ --) {
        scanf("%d", &n);
        int lb = -1, ub = n;
        while(ub - lb > 1) {
            int mid = (ub + lb) >> 1;
            if(C(mid)) {
                ub = mid;
            } else {
                lb = mid;
            }
        }
        printf("Case #%d: %d\n",cas ++,  ub);
    }
    return 0;
}

F.Harmonic Value Description(传送门)

题意

给你 1..n 的序列,求它的排列针对下列公式属于第 K 小的序列

i=0n1gcd(pi,pi+1)

解题思路

通过分析我们发现,最小的一定是 n1i=0gcd(pi,pi+1)=n1 ,因为相邻两个数的 gcd(i,i+1)=1 ,同时相邻的两个奇数他们的 gcd(oddi,oddi+1)=1 ,而相邻的两个偶数 gcd(eveni,eveni+1)=2 ,所以我们针对第 1 小的序列直接输出,对于第2小的则先输出两个相邻的偶数,然后输出这个偶数空出来的两个奇数然后再输出其他,第 k 小也是这么处理,

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int MAXN = 1e6 + 5;

int n, k;
bool vis[MAXN];
int main() {
    int _;
    int cas = 1;
    scanf("%d", &_);
    while(_ --) {
        scanf("%d%d", &n, &k);
        printf("Case #%d:",cas ++);
        memset(vis, false, sizeof(vis));
        for(int i = 1;i <= k;i ++){
            printf(" %d", i * 2);
            vis[i * 2] = true;
        }
        for(int i = 1;i <= k;i ++){
            printf(" %d", i * 2 - 1);
            vis[i * 2 - 1] = true;
        }
        for(int i = 1;i <= n;i ++){
            if(vis[i]) continue;
            printf(" %d", i);
        }
        printf("\n");
    }
    return 0;
}

H.Sequence I(传送门)

题意

给你两个序列a b ,让你在序列a中找到子序列等于 b 的个数有几个,当然子序列有限制,即相邻两个数在a之间的位置差等于 p

解题思路

存暴力不解释

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int MAXN = 1e6 + 5;
int n, m, p;
int A[MAXN], B[MAXN];
int main() {
    int _;
    int cas = 1;
    scanf("%d", &_);
    while(_ --) {
        scanf("%d%d%d", &n, &m, &p);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &A[i]);
        }
        for(int i = 1; i <= m; i++) {
            scanf("%d", &B[i]);
        }
        int ans = 0;
        for(int i = 1;i <= (n - (m - 1) * p);i ++){
            bool flag = true;
            for(int j = 0;j < m;j ++){
                if(A[i + j * p] != B[j + 1]){
                    flag = false;
                    break;
                }
            }
            if(flag) ans ++;
        }
        printf("Case #%d: %d\n",cas ++, ans);
    }
    return 0;
}

I.Sequence II(传送门)

题意

n个数 q 次查询,每次查询(l,r)表示查询区间 [l,r] 内所有不同数字第一次出现的下标,输出这些下标中位数,其中每一个 (l,r) 受上一步答案的影响

解题思路

由于每一次受上一步影响所以无法离线,必须在线,所以想到主席树,然后求一次区间不同数,从后往前建立线段树以确保最新更新的为当前最左边的下标,接着求一下区间第 K 小就可以了

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>

#define FIN freopen("input.txt", "r", stdin)

using namespace std;
struct CT {
    static const int MAXN = 4e5 + 5;
    static const int TIME = 20;
    int tot;
    int T[MAXN * TIME], LT[MAXN * TIME], RT[MAXN * TIME], SUM[MAXN * TIME];
    void T_init() {
        tot = 0;
    }
    void build(int &o, int l, int r) {
        o = ++ tot;
        SUM[o] = 0;
        if(l == r) return;
        int mid = (l + r) >> 1;
        build(LT[o], l, mid);
        build(RT[o], mid + 1, r);
    }

    void update(int &o, int l, int r, int last, int p, int v) {
        o = ++ tot;
        LT[o] = LT[last];
        RT[o] = RT[last];
        SUM[o] = SUM[last] + v;
        if(l == r) return;
        int mid = (l + r) >> 1;
        if(p <= mid) update(LT[o], l, mid, LT[last], p, v);
        else update(RT[o], mid + 1, r, RT[last], p, v);
    }

    int query_sum(int nl, int pos, int l, int r) {
        if(l == r) return SUM[nl];
        int mid = (l + r) >> 1;
        if(pos <= mid) return query_sum(LT[nl], pos, l, mid);
        else return query_sum(RT[nl], pos, mid + 1, r) + SUM[LT[nl]];
    }

    int query_pos(int lt, int k, int l, int r) {
        if(l == r) return l;
        int mid = (l + r) >> 1;
        if(k <= SUM[LT[lt]]) return query_pos(LT[lt], k, l, mid);
        else return query_pos(RT[lt], k - SUM[LT[lt]], mid + 1, r);
    }

} ST;

int n, m;
int A[CT::MAXN], B[CT::MAXN];
map<int,int>MP;

int main() {
    //FIN;
    int _;
    int cas = 1;
    scanf("%d", &_);
    while(_ --) {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &A[i]);
        }
        MP.clear();
        ST.T_init();
        ST.build(ST.T[n + 1], 1, n);
        for(int i = n; i >= 1; i --) {
            if(!MP[A[i]]) {
                ST.update(ST.T[i], 1, n, ST.T[i + 1], i, 1);
            } else {
                ST.update(ST.T[i], 1, n, ST.T[i + 1], MP[A[i]], -1);
                ST.update(ST.T[i], 1, n, ST.T[i], i, 1);
            }
            MP[A[i]] = i;
        }
        int a, b;
        int ans = 0;
        printf("Case #%d:", cas ++);
        while(m --) {
            scanf("%d%d", &a, &b);
            int na = (a + ans) % n + 1, nb = (b + ans) % n + 1;
            a = min(na, nb);
            b = max(na, nb);
            int tmp = ST.query_sum(ST.T[a], b, 1, n);//求区间不同数个数
            tmp = tmp + 1 >> 1;
            ans = ST.query_pos(ST.T[a], tmp, 1, n);//求区间第K小
            printf(" %d", ans);
        }
        printf("\n");
    }
    return 0;
}

J.Ugly Problem(传送门)

题意

给定一个长度最多为1000的数字,让你拆分为不超过 50 个的回文数字

解题思路

  1. 总体思路

    每次都拆分出最大的回文数字即可

  2. 个人思路

    对于求解最大回文字符串步骤
    1 对于当前数的位长为奇数,那么从中间拆开分为左边一半右边一半,分情况讨论:左边大于等于右边和左边小于右边
    2对于当前数的位长为偶数,也是和奇数一样处理就可以了

当时几乎写了一个大数,好尴尬Orz

代码

#include <cstdio>
#include <vector>
#include <string>
#include <iostream>
#include <cstring>
#include <algorithm>
//#include <windows.h>

#define FIN freopen("input.txt", "r", stdin)
#define FOUT freopen("output.txt", "w", stdout)

using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;


struct StringNum {
    static const int MAXN = 1e3 + 5;
    static const int INF = 1e5;

    char S[MAXN];
    int len;
    StringNum() {
        len = 0;
        memset(S, 0, sizeof(S));
        S[len ++] = '0';
        S[len] = '\0';
    }
    StringNum(const StringNum  &sn) {
        *this = sn;
    }
    StringNum(bool sflag, int slen) {
        for(int i = 0; i < slen; i ++) {
            S[i] = '0';
        }
        S[slen] = '1';
        S[len = slen + 1] = '\0';
    }
    StringNum(int p) {
        int slen = 0;
        while(p) {
            S[slen ++] = p % 10 + '0';
            p /= 10;
        }
        if(slen == 0) S[slen ++] = '0';
        S[len = slen] = '\0';
    }
    StringNum(char buf[]) {
        int slen = strlen(buf);
        for(int i = 0; i < slen; i ++) {
            S[i] = buf[i];
        }
        S[len = slen] = '\0';
    }
    void read() {
        cin >> S;
        len = strlen(S);
        *this = this -> reverse();
    }

    StringNum substr(int l, int slen) {
        StringNum sn;
        for(int i = l; i < l + slen && i < len; i ++) {
            sn.S[i - l] = S[i];
        }
        sn.len = slen;
        sn.S[slen] = '\0';
        if(sn.len == 0){
            sn.S[sn.len ++] = '0';
            sn.S[sn.len] = '\0';
        }
        return sn;
    }

    StringNum reverse() {
        StringNum sn = *this;
        for(int i = 0; i < len / 2; i ++) {
            swap(sn.S[i], sn.S[len - 1 - i]);
        }
        return sn;
    }


    StringNum operator = (const StringNum &p) {
        for(int i = 0; i < p.len; i ++) {
            S[i] = p.S[i];
        }
        S[len = p.len] = '\0';
    }

    StringNum operator - (const StringNum &p) {
        StringNum tmp = *this;
        int jw = 0;
        for(int i = 0; i < len; i ++) {
            char pz = '0';
            if(i < p.len) pz = p.S[i];
            if(tmp.S[i] + jw < pz) {
                tmp.S[i] = tmp.S[i] + 10;
                tmp.S[i] = tmp.S[i] - pz + '0' + jw;
                jw = -1;
            } else {
                tmp.S[i] = tmp.S[i] - pz + '0' + jw;
                jw = 0;
            }
        }
        while(tmp.S[tmp.len - 1] == '0' && tmp.len > 0) tmp.S[-- tmp.len] = '\0';
        if(tmp.len <= 0){
            tmp.S[tmp.len ++] = '0';
            tmp.S[tmp.len] = '\0';
        }
        return tmp;
    }


    bool operator > (const StringNum &p) const {
        if(len > p.len) return true;
        if(len < p.len) return false;
        for(int i = len - 1; i >= 0; i --) {
            if(S[i] > p.S[i]) return true;
        }
        return false;
    }
    bool operator > (const int &intp) const {
        StringNum p(intp);
        return *this > p;
    }

    bool operator < (const StringNum &p) const {
        if(len > p.len) return false;
        if(len < p.len) return true;
        for(int i = len - 1; i >= 0; i --) {
            if(S[i] < p.S[i]) return true;
        }
        return false;
    }
    bool operator < (const int &intp) const {
        StringNum p(intp);
        return *this < p;
    }

};


char tmpS[StringNum::MAXN];
vector<string>ve;


StringNum o_L_l_R(StringNum sna) {//处理奇数左边小于右边
    if(sna.len == 1) return sna;
    StringNum a = sna.substr(sna.len / 2 + 1, sna.len / 2);
    a = a.reverse();
    StringNum b = sna.substr(0, sna.len / 2);
    int l = 0;
    for(int i = 0; i < a.len; i ++) {
        tmpS[l ++] = a.S[i];
    }
    tmpS[l ++] = sna.S[sna.len / 2];
    for(int i = a.len - 1; i >= 0; i --) {
        tmpS[l ++] = a.S[i];
    }
    tmpS[l] = '\0';
    a = StringNum(tmpS);
    //printf("[%s]\n", a.S);
    return a;
}

StringNum e_L_l_R(StringNum sna) {//处理偶数左边小于右边
    if(sna.len == 1) return sna;
    StringNum a = sna.substr(sna.len / 2, sna.len / 2);
    a = a.reverse();
    int l = 0;
    for(int i = 0; i < a.len; i ++) {
        tmpS[l ++] = a.S[i];
    }
    for(int i = a.len - 1; i >= 0; i --) {
        tmpS[l ++] = a.S[i];
    }
    tmpS[l] = '\0';
    a = StringNum(tmpS);
    return a;
}

StringNum o_L_g_R(StringNum sna) {//处理奇数左边大于右边
    StringNum tmpb = sna - sna.substr(0, sna.len / 2) - StringNum(1);
    if(tmpb.len & 1){
        tmpb = o_L_l_R(tmpb);
    }
    else{
        tmpb = e_L_l_R(tmpb);
    }
    //printf("[%s]\n", tmpb.S);
    return tmpb;
}

StringNum e_L_g_R(StringNum sna) {//处理偶数左边大于右边
    StringNum tmpb = sna - sna.substr(0, sna.len / 2) - StringNum(1);
    if(tmpb.len & 1){
        tmpb = o_L_l_R(tmpb);
    }
    else{
        tmpb = e_L_l_R(tmpb);
    }

    //printf("[%s]\n", tmpb.S);
    return tmpb;
}


int main() {
    //FIN;
    //FOUT;
    ios::sync_with_stdio(false);
    int _;
    int cas = 1;
    cin >> _;
    StringNum sna;
    while(_ --) {
        ve.clear();
        sna.read();
        while(sna > 0) {
            StringNum a = sna.substr(sna.len / 2 + (sna.len & 1), sna.len / 2);
            a = a.reverse();
            StringNum b = sna.substr(0, sna.len / 2);
            StringNum tmpt;
            if((sna.len & 1) && sna.len > 1) {
                if(!(a > b)) {
                    tmpt = o_L_l_R(sna);
                    sna = sna - tmpt;
                } else {
                    tmpt = o_L_g_R(sna);
                    sna = sna - tmpt;
                }
                ve.push_back(string(tmpt.S));
            } else if(!(sna.len & 1) && sna.len > 1) {
                if(!(a > b)) {
                    tmpt = e_L_l_R(sna);
                    sna = sna - tmpt;
                } else {
                    tmpt = e_L_g_R(sna);
                    sna = sna - tmpt;
                }
                ve.push_back(string(tmpt.S));
            } else {
                ve.push_back(string(sna.S));
                sna = sna - sna;
            }
            //Sleep(1000);
        }
        cout << "Case #" << cas ++ <<":"<<"\n";
        cout << ve.size() << endl;
        for(int i = 0; i < ve.size(); i ++) {
            cout << ve[i] << endl;
        }
    }
    return 0;
}

/*
以下是一些思路和数据
odd > 1:
l <= r
result:lzl
cur -= lzl
odd == 1:
result:cur

l > r
result:l(z-1)l
cur -= l(z-1)l

even:
l <= r
result:ll
cur -= ll
odd==1:
result:cur

l > r
result:l-1l-1
cur -= l-1l-1
*/

//1845481
//18455481
//1345321
//1845241
//1425241
//1000
//999+1
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值