EOJ Monthly 2018.2

A. 坑爹的售票机

题意

用\(1,5,10,25,50,100\)的纸币买\(n\)张单价为\(p\)的船票,且一次性最多买\(k\)张,求钱数恰好时最少需要多少张纸币。

Hard: \(n,k,p\leq 10^9\)

思路

Easy: dp

Hard: dp + 瞎搞

当钱数过大或者张数过多时,(由直觉)其中的大部分都是遵循一定的规律来取的,只有剩余的一小部分需要dp.

Code

Easy

#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
#define maxn 10010
using namespace std;
typedef long long LL;
int a[6] = {1,5,10,20,50,100}, dp[maxn], dp2[maxn];
int main() {
    int n, k, p;
    scanf("%d%d%d", &n, &k, &p);
    int lim = k*p;
    F2(i, 1, lim) {
        int minn = INT_MAX;
        F(j, 0, 6) {
            if (i-a[j]<0) break;
            minn = min(minn, dp[i-a[j]]);
        }
        dp[i] = minn+1;
    }
    F2(i, 1, n) {
        int minn = INT_MAX;
        F2(j, 1, k) {
            if (i-j<0) break;
            minn = min(minn, dp2[i-j]+dp[j*p]);
        }
        dp2[i] = minn;
    }
    printf("%d\n", dp2[n]);
    return 0;
}

Hard

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
using namespace std;
typedef long long LL;
int a[6] = {1,5,10,20,50,100}, dp[1010];
LL dp2[1010], val[20];
int main() {
    LL n, p; int k;
    scanf("%lld%d%lld", &n, &k, &p);
    F(i, 1, 1000) {
        int minn = INT_MAX;
        F(j, 0, 6) {
            if (i-a[j]<0) break;
            minn = min(minn, dp[i-a[j]]);
        }
        dp[i] = minn+1;
    }
    F2(i, 1, k) {
        LL x = i * p;
        val[i] = x/1000*10 + dp[x%1000];
    }
    LL ans = inf;
    F2(i, 1, 1000) {
        LL minn = inf;
        F2(j, 1, k) {
            if (i-j<0) break;
            minn = min(minn, dp2[i-j]+val[j]);
        }
        dp2[i] = minn;
        ans = min(ans, n/i*dp2[i]+dp2[n%i]);
    }
    printf("%lld\n", ans);
    return 0;
}

B. 无聊的游戏

题意

给定一棵树,两人轮流从树中选取一个度数不为 0 的结点 (度数为 0 则不与任何边相连) 将其与其相连的边删去,谁最终无法删去结点,则谁败。问是否先手必胜?

思路

状压dp.

对删去的点进行状压,记录是必胜态还是必败态,记忆化搜索进行转移。

后继全部是必胜态的状态为必败态,后继中有一个是必败态的状态为必胜态。

Code

#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
#define maxn 1100000
using namespace std;
typedef long long LL;
int n, dp[maxn], mp[22][22];
bool vis[maxn];
bool dfs(int state) {
    if (vis[state]) return dp[state];
    vis[state] = true;
    F(i, 0, n) {
        if (!(state&(1<<i))) {
            F(j, 0, n) {
                if (!(state&(1<<j)) && mp[i][j]) {
                    bool ret = dfs(state|(1<<i));
                    if (!ret) return dp[state] = 1;
                }
            }
        }
    }
    return 0;
}
int main() {
    scanf("%d", &n);
    F(i, 1, n) {
        int u, v;
        scanf("%d%d", &u, &v); --u, --v;
        mp[u][v] = mp[v][u] = true;
    }
    if (dfs(0)) puts("First");
    else puts("Second");
    return 0;
}

C. 贪吃的 xjj 和贪心的 oxx

题意

求 \(S=\{a_1,a_2,\ldots,a_n\}\) 这一多重集的一个非空子集 \(S'=\{a_{i_1}, a_{i_2}, \ldots, a_{i_k} \}\),使得
\[\left(Sum(S') \le Sum(S-S') \right) \land \left(\forall t \in S-S', Sum(S') \ge Sum(S-S') - t\right)\]
记多重集 \(Sum(A)\) 表示多重集 \(A\) 所有元素的和。特别地,\(Sum(\varnothing)=0\)。

题解

无论什么情况答案都为 Yes,方法为将原序列从大到小排序,贪心地放两堆,每次往少的一堆里放新的一盒,最终取少的一堆即可。

正确性显见:对于多的一堆,取走其中数量最少的一盒(即刚刚放入的一盒),则其总数必然少于少的一堆,否则这一盒不会被放入。

Code

#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
#define maxn 1000010
using namespace std;
typedef long long LL;
int a[maxn], id[maxn];
bool cmp(int x, int y) { return a[x]>a[y]; }
int main() {
    int n;
    LL x=0, y=0;
    vector<int> v1,v2;
    scanf("%d", &n);
    F(i, 0, n) scanf("%d", &a[i]), id[i] = i;
    sort(id, id+n, cmp);
    F(i, 0, n) {
        if (x<=y) {
            v1.push_back(id[i]+1);
            x += a[id[i]];
        }
        else {
            v2.push_back(id[i]+1);
            y += a[id[i]];
        }
    }
    puts("Yes");
    if (x <= y) {
        printf("%d\n", v1.size());
        sort(v1.begin(), v1.end());
        for (auto x : v1) printf("%d ", x); puts("");
    }
    else {
        printf("%d\n", v2.size());
        sort(v2.begin(), v2.end());
        for (auto x : v2) printf("%d ", x); puts("");
    }
    return 0;
}

D. 黑心的出租车

题意

给定一个森林,从\(1\)号点出发,遍历森林中所有的点最后回到\(1\)号点。

大巴可以在有边相连的两点间往返,而出租车可以在任意两点间往返。

要求:乘出租车次数最少的情况下,乘大巴次数也最少,求两个次数。

思路

如果只有一棵树,显然,次数为\(0,2(n-1)\)

如果有多棵树,那么:

  1. 出租车次数即联通块个数;
  2. 每棵树内的遍历可以到叶子就结束(换乘出租车到另一棵树),而不用回到最初的点。也即可以省略一段回头路。要大把次数最少,则要省略的路径最长,即要找一条最长的路,一端作为根,一端作为叶子。显然,这一条最长的路即为 直径

Code

#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
#define maxn 100010
using namespace std;
typedef long long LL;
struct Edge { int to, ne; }edge[maxn<<1];
int ne[maxn], dep1[maxn], dep2[maxn], fa[maxn], tot, vis[maxn], sz[maxn];
vector<int> v[maxn];
void add(int u, int v) {
    edge[tot] = {v, ne[u]};
    ne[u] = tot++;
}
void dfs(int u, int fa, int d, int* dep) {
    dep[u] = d;
    for (int i = ne[u]; ~i; i = edge[i].ne) {
        int v = edge[i].to;
        if (v == fa) continue;
        dfs(v, u, d+1, dep);
    }
}
inline int find(int x) { return fa[x]==x ? x : fa[x]=find(fa[x]); }
inline void unionn(int u, int v) {
    u = find(u), v = find(v);
    if (sz[u] < sz[v]) swap(u, v);
    fa[v] = u, sz[u] += sz[v];
}
int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    memset(ne, -1, sizeof ne);
    F2(i, 1, n) fa[i] = i, sz[i] = 1;
    F(i, 0, k) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
        unionn(u, v);
    }
    int cnt=0;
    F2(i, 1, n) {
        if (!vis[find(i)]) {
            vis[find(i)] = ++cnt;
        }
        v[vis[find(i)]].push_back(i);
    }
    if (cnt==1) printf("%d %d\n", 0, k<<1);
    else {
        int ans = 0;
        F2(i, 1, cnt) {
            int u = v[i][0];
            dfs(u, -1, 0, dep1);
            for (auto x : v[i]) if (dep1[x]>dep1[u]) u = x;
            dfs(u, -1, 0, dep2);
            for (auto x : v[i]) if (dep2[x]>dep2[u]) u = x;
            ans += (v[i].size()-1)*2-dep2[u];
        }
        printf("%d %d\n", cnt, ans);
    }
    return 0;
}

E. 出老千的 xjj

题目描述

玩了一天的 oxx 和 xjj 精力不减,晚上回到宾馆后决定玩一种卡牌游戏。

游戏规则很简单:一开始,每人手中有若干不同的牌。在每一回合中,两人轮流先手,先手可以打出 x 张任意相同的牌,随后两人轮流出牌,每次打出 x 张任意相同的牌,直至双方都不出牌,该回合结束(一方不出牌时另一方可以继续出牌直至不出牌)。下一回合中换为另一人先手,直至某一回合中一人打完手上所有牌。先打完所有牌的获胜。

xjj 出了把老千,使得自己拿到了 k 张相同的牌,她骄傲地向 oxx 炫耀:“我这牌稳赢了,让你先手。看看你那寒酸样,再给你额外几张万能牌,可以代替任何一张牌。”

机智的 oxx 立刻发现了自己胜利的希望,他想知道他至少拿到几张万能牌就可以赢得游戏。oxx 在第一回合中是先手。

题解

当牌总数小于等于 \(k\) 时,只需一张一张出即可,因此答案为 \(0\)。

当牌总数大于 \(k\) 时,需要每次出 \(x\) 张,且 \(x\) 不整除 \(k\),即加上万能牌后所有牌数量的\(gcd\) 要不为 \(k\) 的约数。

对于每次枚举的 \(p\),通过前缀和可以求出数量在 \((pi,p(i+1)]\) 之间堆数的个数 \(num\) 以及总数量和 \(sum\),那么需要 \(num\cdot p(i+1)-sum\) 张万能牌,最后求和即可。总复杂度 \(O(nlogn)\)。前缀和数组注意开到 \(2\cdot 10^6\)。

还可以进行进一步优化:只需要枚举所有素数即可,对于 \(k\) 的素因子 \(p\),则取 \(p^i\),最小的 \(i\) 使得 \(p^i\) 不能整除 \(k\)。

Code

#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
#define maxn 3000000
#define inf 0x3f3f3f3f3f3f3f3f
typedef long long LL;
int cnt[maxn+10], maxx, prime[maxn], tot;
LL sum[maxn+10];
bool check[maxn+10];
using namespace std;
void init() {
    F2(i, 2, maxx) {
        if (!check[i]) prime[tot++] = i;
        F(j, 0, tot) {
            if (i*prime[j]>maxx) break;
            check[i*prime[j]] = true;
            if (i%prime[j]==0) break;
        }
    }
}
LL update(LL p, LL k) {
    LL ret = p*p;
    while (!(k%ret)) ret *= p;
    return ret;
}
int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    F(i, 0, n) {
        int x;
        scanf("%d", &x);
        ++cnt[x];
        sum[x] += x;
        maxx = max(maxx, x);
    }
    F2(i, 1, maxn) cnt[i] += cnt[i-1], sum[i] += sum[i-1];
    if (sum[maxn] <= k) { puts("0"); return 0; }
    init();
    LL ans = inf;
    F(i, 0, tot) {
        LL p = prime[i];
        if (!(k%p)) p = update(p, k);
        LL temp = 0;
        int lim = (maxx+p-1) / p;
        if (lim==1) temp = 1LL * n * p - sum[maxx];
        else {
            F(j, 0, lim) {
                temp += 1LL * (cnt[p*(j+1)]-cnt[p*j]) * p*(j+1) - (sum[p*(j+1)]-sum[p*j]);
            }
        }
        ans = min(ans, temp);
    }
    printf("%lld\n", ans);
    return 0;
}

F. 回家咯

思路

列方程解方程

Code

#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
using namespace std;
typedef long long LL;
int main() {
    int x1,x2,x3;
    scanf("%d%d%d", &x1, &x2, &x3);
    double c3=1.0*(x3-x1)/2,
        c1 = x2-c3,
        c2 = x1-c1;
    if (c2<=0 || c1<=0 || c3<=0) puts("Wrong");
    else printf("%.2f\n", c1);
    return 0;
}

原文地址:https://www.cnblogs.com/kkkkahlua/p/8460453.html

时间: 2024-10-06 14:59:47

EOJ Monthly 2018.2的相关文章

EOJ Monthly 2018.1 F 最小OR路径

题目链接 Description 给定一个有 \(n\) 个点和 \(m\) 条边的无向图,其中每一条边 \(e_i\) 都有一个权值记为 \(w_i\) . 对于给出的两个点 \(a\) 和 \(b\) ,求一条 \(a\) 到 \(b\) 的路径,使得路径上的边权的 \(OR\)(位或)和最小,输出这个值.(也就是说,如果将路径看做边的集合 \(\{e_1,e_2,-,e_k\}\),那么这条路径的代价为 \(w_1\ OR\ w_2\ OR\ -\ OR\ w_k\),现在求一条路径使得其

【EOJ Monthly 2018.2 (Good bye 2017)】

23333333333333333 由于情人节要回家,所以就先只放代码了. 此题是与我胖虎过不去. [E. 出老千的 xjj] #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=3000000; #define ll long long int

[EOJ Monthly 2018.10][C. 痛苦的 01 矩阵]

题目链接:C. 痛苦的 01 矩阵 题目大意:原题说的很清楚了,不需要简化_(:з」∠)_ 题解:设\(r_i\)为第\(i\)行中0的个数,\(c_j\)为第\(j\)列中0的个数,\(f_{i,j}\)代表对应格子是否为0,则有\(cost(i,j)=r_i+c_j-f_{i,j}\),\((cost(i,j))^2=r_i^2+c_j^2+f_{i,j}+2r_ic_j-2f_{i,j}(r_i+c_j)\) $$\sum_{i=1}^n \sum_{j=1}^n \left( cost(

EOJ Monthly 2018.12 F. 日落轨迹

题解: 对于任何一个串的前x字符内的本质不同子串 我们可以直接在SAM树上得到 然后我们考虑循环串的性质 (设循环节长度为l ) 则大于2*l的位置为等差数列 即每增加一个字符则增加l个本质不同的子串 所以对于2*l我们在后缀树上处理处理 对于x>2*l我们 通过等差求得 #include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <

EOJ Monthly 2019.2

题解 A 回收卫星 #pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optimize(4) #include<bits/stdc++.h> using namespace std; #define y1 y11 #define fi first #define se second #define pi acos(-1.0) #define LL long long //#define mp make_pair #defin

EOJ Monthly 2019.2 E 中位数 (二分+中位数+dag上dp)

题意: 一张由 n 个点,m 条边构成的有向无环图.每个点有点权 Ai.QQ 小方想知道所有起点为 1 ,终点为 n 的路径中最大的中位数是多少. 一条路径的中位数指的是:一条路径有 n 个点,将这 n 个点的权值从小到大排序后,排在位置 ⌊n2⌋+1 上的权值. 思路(官方题解): 考虑二分答案,我们需要验证路径最大的中位数是否 ≥mid . 我们把所有的点权做 −1/1 变换,即 ≥mid 的点权变为 1 ,否则变为 −1 . 根据题面路径中位数的定义,我们可以发现,如果这条路径的中位数 ≥

EOJ Monthly 2019.2 (based on February Selection) D.进制转换

题目链接: https://acm.ecnu.edu.cn/contest/140/problem/D/ 题目: 思路: 我们知道一个数在某一个进制k下末尾零的个数x就是这个数整除kx,这题要求刚好末尾有m个0,还需要除去高位为0的情况,因此这题答案就是r / kx-(l-1)/kx-(r/kx+1-(l-1)/kx+1). 代码实现如下: 1 #include <set> 2 #include <map> 3 #include <deque> 4 #include &

EOJ Monthly 2019.2 E. 中位数 (二分+dfs)

题目传送门 题意: 在一个n个点,m条边的有向无环图中,求出所有从1到n 的路径的中位数的最大值 一条路径的中位数指的是:一条路径有 n 个点, 将这 n 个点的权值从小到大排序后,排在位置 ⌊n2⌋+1 上的权值. 思路: 看到权值为1~1e9,可以想到用二分答案,然后我们在验证的时候 可以将小于mid的边权设为-1,大于为1这样遍历一遍序列加起来的值 刚好为0 代码: #include<bits/stdc++.h> using namespace std; typedef long lon

EOJ Monthly 2019.3A

A. 钝角三角形 单点时限: 3.0 sec 内存限制: 512 MB QQ 小方以前不会判断钝角三角形,现在他会了,所以他急切的想教会你. 如果三角形的三边长分别为 a, b, c (a≤b≤c),那么当满足 a2+b2<c2 且 a+b>c 的时候,这个三角形就是一个由三边长为 a, b, c 构成的钝角三角形. 单单讲给你听肯定是不够的,为了表现自己,QQ 小方现在要考考你. 现在 QQ 小方会给你一个包含 3n 个整数的集合,分别是 {2,3,4,?3n,3n+1} ,他想让你将这个集