AtCoder Beginner Contest 155 - D - Pairs ,-E - Payment 题解

D - Pairs

We have NN integers A1,A2,...,ANA1,A2,...,AN.

There are N(N−1)2N(N−1)2 ways to choose two of them and form a pair. If we compute the product of each of those pairs and sort the results in ascending order, what will be the KK-th number in that list?

题意:

给定一个含有n个整数的数组a,将其所有点对\((i,j),i \neq j\),两两相乘\(a[i]*a[j]\) 得到\(\frac{N(N-1)}{2}\)个数,问将其升序排序后第k个数是多少?

思路:

\(a[i]\) 分为3类:大于0,等于0,小于0。分别加入vector容器 v1,v0,v2中后排序容器。

然后再\([-1e18,1e18]\) 中二分答案ans,

然后以时间复杂度为\(O(n*log_2(n))\) 的方法去计算有多少对数相乘小于ans,方法为:

如果ans小于0:

​ 只需要遍历v1中的每一个元素y,然后二分找到v2中与y相乘小于ans的下标,然后更新计数。

如果ans等于0:

​ 小于ans的一定是负数,那么只有正数与负数相乘才会得到负数,所以计数=\(v1.size()*v2.size()\)

如果ans大于0:

负数和零一定小于ans,所以计数先加上\(v1.size()*v2.size()+v0.size()*(v1.size()+v2.size())\)

然后再找正数中小于ans的乘积对:

​ 遍历v1中的每一个元素y,然后二分找到v1中与y相乘小于ans的下标,更新计数。

​ 遍历v2中的每一个元素y,然后二分找到v2中与y相乘小于ans的下标,更新计数。

对于vector容器的二分我们可以使用lower_bound()和upper_bound() 两个函数。

二分过程中可能会用到\(ceil(\frac{x}{y})= \left \lceil \frac{x}{y} \right \rceil=\frac{x+y-1}{y}\),可以避免精度丢失引起的误差。

具体可以看代码:

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <iomanip>
#define ALL(x) (x).begin(), (x).end()
#define sz(a) int(a.size())
#define rep(i,x,n) for(int i=x;i<n;i++)
#define repd(i,x,n) for(int i=x;i<=n;i++)
#define pii pair<int,int>
#define pll pair<long long ,long long>
#define gbtb ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define MS0(X) memset((X), 0, sizeof((X)))
#define MSC0(X) memset((X), '\0', sizeof((X)))
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define eps 1e-6
#define chu(x) cout<<"["<<#x<<" "<<(x)<<"]"<<endl
#define du3(a,b,c) scanf("%d %d %d",&(a),&(b),&(c))
#define du2(a,b) scanf("%d %d",&(a),&(b))
#define du1(a) scanf("%d",&(a));
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
ll lcm(ll a, ll b) {return a / gcd(a, b) * b;}
ll powmod(ll a, ll b, ll MOD) { if (a == 0ll) {return 0ll;} a %= MOD; ll ans = 1; while (b) {if (b & 1) {ans = ans * a % MOD;} a = a * a % MOD; b >>= 1;} return ans;}
void Pv(const vector<int> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%d", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("\n");}}}
void Pvl(const vector<ll> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%lld", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("\n");}}}
inline long long readll() {long long tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
inline int readint() {int tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
const int maxn = 1000010;
const int inf = 0x3f3f3f3f;
/*** TEMPLATE CODE * * STARTS HERE ***/
ll k, n;
ll a[maxn];
std::vector<ll> v1, v2;
ll len1, len2;
std::vector<ll> v;
ll cnt = 0ll;
bool check(ll x)
{
    ll res = 0ll;
    if (x > 0)
    {
        for (auto y : v1)
        {
            res += lower_bound(ALL(v), (x + y - 1) / y) - v.begin();
            v.push_back(y);
        }
        v.clear();
        for (auto y : v2)
        {
            res += lower_bound(ALL(v), (x + y - 1) / y) - v.begin();
            v.push_back(y);
        }
        v.clear();
        res += cnt * (cnt - 1) / 2;
        res += 1ll * sz(v1) * sz(v2);
        res += cnt * (sz(v1) + sz(v2));
    } else if (x < 0)
    {
        x = -x;
        for (auto y : v1)
        {
            res += sz(v2) - (upper_bound(ALL(v2), x / y) - v2.begin());
        }
    } else
    {
        res += 1ll * sz(v1) * sz(v2);
    }
    return res < k;
}
int main()
{
    //freopen("D:\\code\\text\\input.txt","r",stdin);
    //freopen("D:\\code\\text\\output.txt","w",stdout);
    n = readll();
    k = readll();
    repd(i, 1, n)
    {
        a[i] = readll();
        if (a[i] > 0)
        {
            v1.push_back(a[i]);
        } else if (a[i] < 0)
        {
            v2.push_back(-a[i]);
        } else
        {
            cnt++;
        }
    }
    sort(ALL(v1));
    sort(ALL(v2));
    len1 = sz(v1);
    len2 = sz(v2);
    ll l = -1e18;
    ll r = 1e18;
    ll mid;
    ll ans;
    while (l <= r)
    {
        mid = (l + r) >> 1;
        if (check(mid))
        {
            l = mid + 1;
            ans = mid;
        } else
        {
            r = mid - 1;
        }
    }
    printf("%lld\n", ans );
    return 0;
}

E - Payment

In the Kingdom of AtCoder, only banknotes are used as currency. There are 10100+110100+1 kinds of banknotes, with the values of 1,10,102,103,…,10(10100)1,10,102,103,…,10(10100). You have come shopping at a mall and are now buying a takoyaki machine with a value of NN. (Takoyaki is the name of a Japanese snack.)

To make the payment, you will choose some amount of money which is at least NN and give it to the clerk. Then, the clerk gives you back the change, which is the amount of money you give minus NN.

What will be the minimum possible number of total banknotes used by you and the clerk, when both choose the combination of banknotes to minimize this count?

Assume that you have sufficient numbers of banknotes, and so does the clerk.

题意:

在一个国家纸币只有\(10^{100}+1\)种面额,分别是\(1, 10, 10^2, 10^3, \dots, 10^{(10^{100})}\)

你需要去支付n元,可能是你付出了一个更大的钱数,对方再找回你差值的钱。

问整个交易过程最少需要用到多少个纸币?

思路:

我们从低位到高位看,第i位x,是选择方法1:付x个\(10^i\)元的纸币

还是选择方法2:付一个\(10^{i+1}\)元的纸币在让对方找\(10-x\)\(10^i\) 纸币呢?

我们可以看出对于每一位i,只有2个选择情况,即上述2种。

那么我们不妨定义动态规划的状态\(dp[i][0]\)代表从低数第i位选择了方法1,\(dp[i][1]\)代表从低数第i位选择了方法2.

定义初始状态:

dp[n][0] = (s[n] - '0');
dp[n][1] = 10 - (s[n] - '0');

转移方程:

dp[i][0] = min(dp[i + 1][0] + (s[i] - '0'), dp[i + 1][1] + (s[i] - '0') + 1);
dp[i][1] = min(dp[i + 1][0] + 10 - (s[i] - '0'), dp[i + 1][1] + 10 - (s[i] - '0' + 1)); 

代码:

char s[maxn];
ll dp[maxn][2];
int main()
{
    //freopen("D:\\code\\text\\input.txt","r",stdin);
    //freopen("D:\\code\\text\\output.txt","w",stdout);
    s[0] = '0';
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    dp[n][0] = (s[n] - '0');
    dp[n][1] = 10 - (s[n] - '0');
    for (int i = n - 1; i >= 0; --i)
    {
        dp[i][0] = min(dp[i + 1][0] + (s[i] - '0'), dp[i + 1][1] + (s[i] - '0') + 1);
        dp[i][1] = min(dp[i + 1][0] + 10 - (s[i] - '0'), dp[i + 1][1] + 10 - (s[i] - '0' + 1));
    }
    printf("%lld\n", min(dp[0][0], dp[0][1]));
    return 0;
}