UPC 个人训练赛2部分题题解

A 招待

本题是一个三进制下的模拟题。为什么判定这是三进制?是因为砝码的重量一定是3的幂次。

假如砝码的重量都是2的幂次,那么根据二进制的相关知识可以知道110=4+2+0=6,也就是6可以由一个2单位的砝码和一个4单位的砝码称量。换成三进制的的话,对于数字13,三进制表示是111,也就是1+3+9,重量为13的物体可以由1,3,9这三个重量的砝码称量。但是三进制表达式中含有2这个数字,题给的每种重量的砝码只有一个,那这要怎么办呢?

因为可以在天平的物体盘里添加砝码,所以只需要通过添加配重块把物品盘的重量配成三进制下只有0和1两个数字的情况即可。所以对于表达式里含有2这个数字的某个值,比如说,52的表达式就是1221,当你在物品盘里加上一个重量为3的砝码时,根据进位,表达式就会变成2001,正好是55,再添加一个重量为27的砝码,表达式变为10001,十进制是82,这样这个数字就可以由81和1直接表示了。

实现这个思路直接模拟即可。

/**Operation Overlord 1944.6.6 Daybreak**/
/**Last Remote**/
ll digits[50000],left1[50000],right2[50000];    
ll cnt1 = 0, cnt2 = 0;
ll quickpower(ll a, ll b, ll mod)
{
    ll ans = 1;
    a %= mod;
    while(b)
    {
        if ((b & 1) > 0)
            ans = (ans*a) % mod;
        b >>= 1;
        a = (a*a) % mod;
    }
    return ans;
}
int DETERMINATION()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    ll n;
    cin >> n;
    ll tmp = n;
    ll cnt = 0;
    while (n > 0)
    {
        digits[++cnt] = n % 3;
        n /= 3;
    }
    for (int i = 1; i <=2*cnt; i++)
    {
        if (digits[i] == 2)
        {
            ll next = i + 1;
            while (digits[next] + 1 > 2)
            {
                digits[next] = (digits[next]+1)%3;
                next++;
            }
            digits[next] += 1;
            right2[++cnt2] = i;
        }
        else if (digits[i] == 1)
            left1[++cnt1] = i;
    }
    sort(left1 + 1, left1 + 1 + cnt1);
    sort(right2 + 1, right2 + 1 + cnt2);
    for (int i = 1; i <= cnt1; i++)
    {
        if (i == cnt1)
        {
            cout << quickpower(3, left1[i] - 1, LLONG_MAX) << endl;
        }
        else
            cout << quickpower(3, left1[i] - 1, LLONG_MAX)<<" ";
    }
    cout << tmp << " ";
    for (int i = 1; i <= cnt2; i++)
    {
        if (i == cnt2)
        {
            cout << quickpower(3, right2[i] - 1, LLONG_MAX) << endl;
        }
        else
            cout << quickpower(3, right2[i] - 1, LLONG_MAX)<<" ";
    }
    return 0;
}

B 小说

很明显的分层图最短路,但是这个题的意思是要求K次免费后最短路上的路径最大值,所以需要修改一下模板,把最短路算法里边求距离的换成求路径中的最大值即可。

struct node
{
    ll next, to, value;
}nodes[1140000];
ll heads[1002000];
ll cnt = 0;
void construction(ll from, ll to, ll value)
{
    nodes[cnt] = node{ heads[from], to, value };
    heads[from] = cnt++;
}
bool vis[1002000];
ll dis[1002000];
void spfa(ll st, ll limit1, ll limit2)
{
    for (int i = 0; i <= limit1 * (limit2+3); i++)
    {
        vis[i] = false;
        dis[i] = INF;
    }
    dis[st] = 0;
    queueq;
    q.push(st);
    vis[st] = true;
    while (!q.empty())
    {
        ll current = q.front();
        q.pop();
        vis[current] = false;
        for (int i = heads[current]; i != -1; i = nodes[i].next)
        {
            ll t = nodes[i].to;
            ll nxt = max(nodes[i].value, dis[current]);
            if (dis[t] > nxt)
            {
                dis[t] = nxt;
                if (!vis[t])
                {
                    q.push(t);
                    vis[t] = true;
                }
            }
        }
    }
}
int DETERMINATION()
{
    //std::ios::sync_with_stdio(false);
    //std::cin.tie(0), std::cout.tie(0);
    ll n, m, k;
    lldin(n), lldin(m), lldin(k);
    reset(heads, -1);
    for (int i = 1; i <= m; i++)
    {
        ll tmp1, tmp2, tmp3;
        lldin(tmp1), lldin(tmp2), lldin(tmp3);
        for (int j = 0; j <= k; j++)
        {
            construction(j*n + tmp1, j*n + tmp2, tmp3);
            construction(j*n + tmp2, j*n + tmp1, tmp3);
        }
        for (int j = 1; j <= k; j++)
        {
            construction((j - 1)*n + tmp1, j*n + tmp2, 0);
            construction((j - 1)*n + tmp2, j*n + tmp1, 0);
        }
    }
    spfa(1, n, k);
    if (dis[k*n+n] == INF)
        cout << -1 << endl;
    else
        cout << dis[k*n+n] << endl;
    return 0;
}
 

D 小A进学校

本题涉及一个k进制转换与求末尾0的问题。

首先说一下关于求一个数的末尾0的个数,末尾0的个数意味着这个数是10的若干整数倍,而10拆分成因子就是2和5(根据算数基本定理分解后的表达式可以看出),就十进制而言,10以内的2的倍数比5的倍数多,所以这就需要讨论一下2与5在某个数中有多少个这样的因子,然后取最小值。

值得一说的是阶乘数求末尾0,因为阶乘是若干个自然数的乘积,所以需要一步一步的筛因子幂次。比如说对于10!,要求2的幂次,就需要先把10/2,得到5,再把5/2,得到2,再把2/2,得到1,总的数目就是8,这么做的意义是:第一步先把5-10这个范围内的2筛出来(10,8,6),再把2-5这个范围内的2筛出来(4),再筛0-2范围内的(2)

然而一个数转换成k进制之后怎么判断末尾0呢?我们知道进制转换必要的几步是:对一个数取余base,然后把这个数除以base。(比如说6转化为二进制,就是6%2=0,6/2=3,3%2=1,3/2=1,1%2=1,1/2=0---->110)而出现0的关键步骤就在于取余base的这一步,如果某个数是base的整数倍,换句话说,就是把原数唯一分解后的若干因子可以组成base,那就意味着必然会出现0。至于有多少个0,那就是需要判断它内部有多少个乘积为base的因子,比如说对于数字8转化为二进制,它内部有2的立方,可以支付三次对2的整除(进制转换取余后需要除base),三次之后就不存在2的因子,故8的二进制表达式有3个0.

所以说转化成K进制后的末尾0就是看原数的因子够完整支付(整除)K的对应因子几次,换句话说就是对应因子的幂次取最小值。

ll primes[1500000], power[1500000];
ll cnt = 0;
void factorization(ll x)
{
    ll limit = (ll)sqrt(x) + 1;
    for (int i = 2; i<=limit; i++)
    {
        if (x%i == 0)
        {
            primes[++cnt] = i;
            while (x%i == 0)
            {
                power[cnt]++;
                x /= i;
            }
        }
    }
    if (x > 1)
    {
        primes[++cnt] = x;
        power[cnt]++;
    }
}
ll split(ll x,ll b)
{
    ll ans = 0;
    while (x > 0)
    {
        ans += x / b;
        x /= b;
    }
    return ans;
}
int DETERMINATION()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    ll n, k;
    cin >> n >> k;
    factorization(k);
    ll ans = LLONG_MAX;
    for (int i = 1; i <= cnt; i++)
        ans = min(ans, split(n, primes[i]) / power[i]);
    cout << ans << endl;
    return 0;
}

 

你可能感兴趣的:(训练集)