zoukankan      html  css  js  c++  java
  • 2017广东工业大学程序设计竞赛决赛(官方题解)

    题目链接:http://gdutcode.sinaapp.com/contest.php?cid=1056
    Problem A: 两只老虎
    正常的+有耳朵的 = a/2
    正常的+有尾巴的 = b
    正常的+有耳朵的+有尾巴的 = c/4
    正常的 = a/2+b-c/4

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstdlib>
    using namespace std;
    
    const int NUM = 1000;
    
    void solve()
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        int ans = a/2+b-c/4;
        printf("%d
    ", ans);
    }
    
    int main()
    {
        int T;
        scanf("%d", &T);
        while(T--)
        {
            solve();
        }
        return 0;
    }
    

    Problem B: 占点游戏
    令 d = abs(x1-x2)+abs(y1-y2)
    首先判断(n+1)/2 >= d,先手可不可以从一个点走到另一个点 :
    如果不可以,则先手可以多得 n&1 分(因为劣势者可以选择逃离)
    如果可以,考虑 d 的奇偶性:
    如果 d 为奇数(先手可以先踩到后手覆盖过的点):
    如果 n 为奇数,先手可以多得 2 分,否则平。
    否则(d 为偶数):
    如果 n 为奇数,先手可以多得 1 分,否则后手可以多得 1 分。

    #include <cstdio>
    #include <cmath>
    
    using namespace std;
    
    int main()
    {
        int T;
        scanf("%d", &T);
        while(T--)
        {
            int x1, y1, x2, y2, n;
            scanf("%d%d%d%d%d", &n, &x1, &y1, &x2, &y2);
            int tot = abs(x1-x2) + abs(y1-y2);
            int ans = -1;
            if((n+1)/2 >= tot)
            {
                if(tot&1)
                {
                    if(n&1) ans = 2;
                }
                else ans = 1;
            }
            else if(n&1) ans = 1;
            printf("%d
    ", ans);
        }
        return 0;
    }
    

    Problem C: 爬楼梯
    先分段考虑:
    对于一段 x 阶的楼梯,方案数 f(x) = f(x-1)+f(x-2)+f(x-3) (其中 x >= 3),f(0)=1,f(1)=1, f(2) = 2。
    那么对于爬 n 层楼,只需要算出每一段的方案数,然后使用乘法原理乘起来即可。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    
    using namespace std;
    
    const int mod = 10007;
    const int N = 51;
    int f[N];
    
    
    void solve()
    {
        f[0] = 1;
        for(int i = 1; i < N; ++ i)
        {
            f[i] = f[i-1];
            if(i > 1) f[i] += f[i-2];
            if(i > 2) f[i] += f[i-3];
            f[i] %= mod;
        }
        int n;
        scanf("%d", &n);
        int ans = 1;
        for(int i = 1; i < n; ++ i)
        {
            int x;
            scanf("%d", &x);
            ans = ans*f[x]%mod;
        }
        printf("%d
    ", ans);
    }
    
    int main()
    {
        int T;
        scanf("%d", &T);
        while(T--)
        {
            solve();
        }
        return 0;
    }
    

    Problem D: 只有通过毁灭才能揭示真理
    可以知道每 30 秒可以可以爆发一次 c 伤害,每 1 秒造成 b 伤害,那么答案=a*b+a/30*c。

    #include <bits/stdc++.h>
    
    class Koz {
    public:
        int disintegration(int A, int B, int C) {
            return A * B + A / 10 / 3 * C;
        }
    };
    
    int main()
    {
        Koz koz;
        int T;
        scanf("%d", &T);
        while( T-- ) {
            int A, B, C;
            std::cin >> A >> B >> C;
            std::cout << koz.disintegration(A, B, C) << std::endl;
        }
        return 0;
    }
    

    Problem E: 倒水(Water)
    对于 n 瓶一升的水,把他们合并后,最少需要的瓶子数为 n 的二进制中 1 的个数。假
    设 n 的二进制中 1 的个数大于 k,那么我们要找到 1 个大于 n 的数,且二进制中 1 的个数等
    于 k 的最小的数 m,那么答案为 m-n。
    假设 n 二进制中,最右边的 1 在第 i 位(这里的第几位是从右往左数的,最右边为第 0
    位),那么假设你加上一个小于 2^i 的数,结果二进制中 1 的个数只会增加,如果加上一个
    2^i,则结果二进制中 1 的个数必定不会增加。所以只需要一直加上一个 2^i(这里的 i 表示
    的是当前水的总体积的二进制中最右边的 1 所在的位置)直到结果中 1 的个数等于 k 即可。

    #include <iostream>
    
    using namespace std;
    
    int cal(int n)
    {
        int ans = 0;
        while(n)
        {
            ans ++;
            n &= n-1;
        }
        return ans;
    }
    
    int lowbit(int n)
    {
        return n&-n;
    }
    
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            int n, k;
            scanf("%d%d", &n, &k);
            int ans = 0;
            while(cal(n) > k)
            {
                ans += lowbit(n);
                n += lowbit(n);
            }
            printf("%d
    ", ans);
        }
        return 0;
    }
    

    Problem F: tmk 找三角
    假设现在有 n 条线段,假设 n 条边从小到达排序,如果这 n 条边中没有三条可以构成
    三角形,那么这 n 条边必须满足关系:A[i] >= A[i-2]+A[i-1],这里的 A[i]表示第 i 条边的大小。
    假设 A[i]尽量取最小 A[i]=A[i-2]+A[i-1],且 A[1]=A[2]=1,是不是就是一个斐波那契,也就
    是对于一个 n 条边的集合,如果不存在三条边能构成一个三角形,那么最长的边至少为 f[n],
    表示斐波那契第 n 项。而题目中 A[i]<1e9,也就是只要 n>50,就必定存在三条边可以构成一
    个三角形,所以我们只需要暴力加入两点路径上的边(如果大于 50,直接 Yes),然后对这
    些边进行排序,枚举第 i 条边为最长边,贪心判断 A[i]是否小于 A[i-1]+A[i-2]即可。

    #include <cstdio>
    #include <set>
    #include <cstring>
    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    const int N = 1e5+10;
    int n;
    int tot, head[N], to[N<<1], nex[N<<1], len[N<<1]; 
    int f[N], dis[N], dep[N];
    
    void init()
    {
        tot = 0;
        for(int i = 0; i <= n; ++ i)
        {
            head[i] = -1;
        }
    }
    
    void addEdge(int x, int y, int l)
    {
        to[tot] = y;
        len[tot] = l;
        nex[tot] = head[x];
        head[x] = tot++;
    }
    
    void dfs(int u, int d)
    {
        dep[u] = d;
        for(int i = head[u]; ~i; i = nex[i])
        {
            int v = to[i];
            if(v == f[u]) continue;
            dis[v] = len[i];
            f[v] = u;
            dfs(v, d+1);
        }
    }
    
    bool solve(int x, int y)
    {
        vector<int> vec;
        while(vec.size() < 50 && x != y)
        {
            if(dep[x] < dep[y])
            {
                vec.push_back(dis[y]);
                y = f[y];
            }
            else
            {
                vec.push_back(dis[x]);
                x = f[x];
            }
        }
        if(vec.size()>=50) return true;
        sort(vec.begin(), vec.end());
        for(int i = 0; i + 2 < vec.size(); ++ i)
        {
            if(vec[i] + vec[i+1] > vec[i+2]) return true;
        }
        return false;
    }
    
    int main()
    {
        int T;
        scanf("%d", &T);
        while(T--)
        {
            scanf("%d", &n);
            init();
            for(int i = 1; i < n; ++ i)
            {
                int x, y, l;
                scanf("%d%d%d", &x, &y, &l);
                addEdge(x, y, l);
                addEdge(y, x, l);
            }
            dfs(1, 0);
            int m;
            scanf("%d", &m);
            for(int i = 0; i < m; ++ i)
            {
                int x, y;
                scanf("%d%d", &x, &y);
                puts(solve(x, y) ? "Yes" : "No");
            }
        }
        return 0;
    } 
    

    Problem G: 等凹数字
    dp[i][len][pre][up][down][ispa]代表当前第 i 位,长度为 len,上一位是什么,前面是否递
    增过,前面是否递减过,当前是否符合回文串的性质,然后记忆化搜索

    #include <stdio.h>
    #include <string.h>
    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <queue>
    #include <time.h>
    #include <set>
    #include <map>
    #include <string>
    #include <math.h>
    #include <stdlib.h>
    using namespace std;
    long long dp[20][20][10][2][2][2];
    int num[20];
    int s[20];
    long long rec(int i,int pre,int up,int down,int flag,int q,int len,int ispa)
    {
        if(i<0)return up&&down&&ispa;
        if(~dp[i][len][pre][up][down][ispa]&&!flag&&!q)return dp[i][len][pre][up][down][ispa];
        long long res=0;
        int o=s[i];
        for(int j=0;j<10;j++)
        {
            num[i]=j;
            if(j>o&&flag)break;
            if(q)res+=rec(i-1,j,0,0,j<o?0:flag,q&&j==0,len-(q&&j==0),ispa);
            else if(j==pre)
            {
                if(ispa&&i<len/2)
                res+=rec(i-1,j,up,down,j<o?0:flag,q&&j==0,len,j==num[len-i-1]);
                else res+=rec(i-1,j,up,down,j<o?0:flag,q&&j==0,len,ispa);
            }
            else if(j>pre)
            {
                if(!down)continue;
                if(ispa&&i<len/2)
                res+=rec(i-1,j,1,down,j<o?0:flag,q&&j==0,len,j==num[len-i-1]);
                else res+=rec(i-1,j,1,down,j<o?0:flag,q&&j==0,len,ispa);
            }
            else if(j<pre)
            {
                if(up)continue;
                if(ispa&&i<len/2)
                res+=rec(i-1,j,up,1,j<o?0:flag,q&&j==0,len,j==num[len-i-1]);
                else res+=rec(i-1,j,up,1,j<o?0:flag,q&&j==0,len,ispa);
            }
        }
        if(!flag&&!q)dp[i][len][pre][up][down][ispa]=res;
        return res;
    }
    long long cal(long long x)
    {
        int len=0;
        while(x)
        {
            s[len++]=x%10;
            x/=10;
        }
        return rec(len-1,0,0,0,1,1,len,1);
    }
    int main()
    {
        memset(dp,-1,sizeof(dp));
        long long l,r;
        int t;
        scanf("%d",&t);
        while(t--){
        scanf("%lld%lld",&l,&r);
        printf("%lld
    ",cal(r)-cal(l-1));
        }
        return 0;
    }
    

    Problem H: tmk 买礼物
    首先,先对 a[i]从小到大排序,假设对于前 i 个硬币,我们可以组合成 0~y:
    ①如果 a[i+1]>y+1,那么从 i+1~n 中任意取硬币,构成的和都>y+1,所以必定构造不出
    y+1,于是答案等于 y。
    ②如果 a[i+1]<=y+1,那么前 i+1 位可以组合成 0~y+a[i+1]。
    所以只需要对硬币从小到大排序,然后从第一个硬币枚举到最后一个硬币,或者中途有
    某个数够不出来即可得到答案。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    const int maxn = 1e5 + 5;
    
    int a[maxn];
    
    int main()
    {
        int T;
        scanf("%d", &T);
        while(T--)
        {
            int n;
            scanf("%d", &n);
            for(int i = 0; i < n; ++i) scanf("%d", &a[i]);
            sort(a, a + n);
            ll ans = 0;
            for(int i = 0; i < n && ans + 1 >= a[i]; ++i) ans += a[i];
            printf("%lld
    ", ans);
        }
        return 0;
    }
    
    "No regrets."
  • 相关阅读:
    前端基础 — BOM和DOM
    JavaScript概述
    前端知识 — HTML内容、CSS基础
    数据库MySQL
    jquery ajax() 方法使用
    移动端自适应的问题及解决
    jquery appendTo 和 append 的用法
    css 截取文字
    电脑模拟手机工具
    jquery 旋转插件jqueryrotate
  • 原文地址:https://www.cnblogs.com/zxy160/p/7215135.html
Copyright © 2011-2022 走看看