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."
原文地址:https://www.cnblogs.com/zxy160/p/7215135.html