[JLOI2015]城池攻占 题解
题目地址:ė …
May all the beauty be blessed.
比赛地址:Dashboard – Codeforces Round #462 (Div. 2) – Codeforces,也可以在底下的赛题列表点进题目。
#A 934A – A Compatible Pair | 暴力,枚举
#B 934B – A Prosperous Lot | 贪心
#C 933A – A Twisty Movement | 枚举
#D 933B – A Determined Cleanup | 数学
#E 933C – A Colourful Prospect | 计算几何
给两个数列a, b长分别为n, m,其中a数列要删去一个数,求一种删数的方案,使得所有a_i \cdot b_j中的最大值最小。只需要输出这个最小值。
数据范围:2 \leq n, m \leq 50
输入:
2 2 20 18 2 14
输出:
252
说明:
删去20,此时最大值为18*14=252。
输入:
5 3 -1 0 1 2 3 -1 0 1
输出:
2
说明:
删去3,最大值为2*1=2。
枚举删去的数,然后枚举求出此时最大值,维护全部情况的最大值的最小值即可。
复杂度:O(n^3)
// Code by KSkun, 2018/2
#include <cstdio>
#include <algorithm>
int n, m;
long long ans = 1e18 + 10;
long long a[55], b[55];
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for(int i = 1; i <= m; i++) {
scanf("%lld", &b[i]);
}
for(int k = 1; k <= n; k++) {
long long mx = -1e18 - 10;
for(int i = 1; i <= n; i++) {
if(i == k) continue;
for(int j = 1; j <= m; j++) {
mx = std::max(mx, a[i] * b[j]);
}
}
ans = std::min(ans, mx);
}
printf("%lld", ans);
return 0;
}
定义一个数的圈的个数为这个数中包含数码的圈的个数和,其中,数码的圈的个数依照下图计算。
例如,8
的圈的个数为2,而5
的圈的个数为0。
给定一个数k,求任意一个不大于10^{18}的非负整数使得其圈的个数为k。如果不存在,输出-1
。
数据范围:1 \leq k \leq 10^6
输入:
2
输出:
462
输入:
6
输出:
8080
我们知道8
有两个圈,而6
有一个圈。先贪心一下,全用8,如果还剩下1就用1个6,加一起长度超没超18。超了就无解,没超就输出。
复杂度:O(1)
// Code by KSkun, 2018/2
#include <cstdio>
int k, c2, c1;
int main() {
scanf("%d", &k);
c2 = k / 2;
c1 = k % 2;
if(c1 + c2 > 18) {
printf("-1");
return 0;
}
if(c1) putchar('6');
for(int i = 1; i <= c2; i++) putchar('8');
return 0;
}
给定一个只由1
和2
构成的数列,长为n,有一次机会翻转任意区间[l, r],求一种翻转方法,使得翻转后的序列的LIS最长。只需要输出最长的LIS长度。
数据范围:1 \leq n \leq 2000
输入:
4 1 2 1 2
输出:
4
说明:
翻转区间[2, 3]得到序列1 1 2 2
。
输入:
10 1 1 2 2 2 1 1 2 2 1
输出:
9
说明:翻转区间[3, 7]得到序列1 1 1 1 2 2 2 2 2 1
。
这个题需要一些思考。
首先传统的LIS求法是O(n \log n)的,但是12序列可以做到O(n)的。枚举LIS中1和2的分界点,即为该点前1的个数加该点后2的个数。可以分别预处理出来。
我们考虑枚举分界点,然后枚举翻转的区间。可以发现,如果分界点不在区间内部,翻转区间是没有用的。所以区间应该包含这个分界点。翻转后的答案应该是1~区间左端点1的个数+区间左端点~分界点2的个数+分界点~区间右端点1的个数+区间右端点~n 2的个数。左端点和右端点的计算是分开的,所以可以分开枚举分别求最大值,加起来就是一个完整的最大值。
复杂度:O(n^2)
// Code by KSkun, 2018/2
#include <cstdio>
#include <algorithm>
int n, a[2005], c1[2005], c2[2005], ans = 0;
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
c1[i] = c1[i - 1];
c2[i] = c2[i - 1];
if(a[i] == 1) {
c1[i]++;
} else {
c2[i]++;
}
}
for(int i = 0; i <= n; i++) {
ans = std::max(ans, c1[i] + c2[n] - c2[i]);
}
for(int i = 0; i <= n; i++) {
int mx1 = 0, mx2 = 0;
for(int l = 1; l < i; l++) {
mx1 = std::max(mx1, c1[l - 1] + c2[i - 1] - c2[l - 1]);
}
for(int r = i; r <= n; r++) {
mx2 = std::max(mx2, c1[r] - c1[i - 1] + c2[n] - c2[r]);
}
ans = std::max(ans, mx1 + mx2);
}
printf("%d", ans);
return 0;
}
给两个数p和k,要求求一个多项式f(x),使得f(x)满足f(x)=q(x) \cdot (x+k)+p,其中q(x)是另一个多项式,且f(x)各项的系数和常数项都不大于k。如果无解,输出-1
。
数据范围:1 \leq p \leq 10^{18}, 2 \leq k \leq 2000
输入:
46 2
输出:
7 0 1 0 0 1 1 1
说明:
f(x) = x^6 + x^5 + x^4 + x = (x^5 - x^4 + 3x^3 - 6x^2 + 12x - 23)(x + 2) + 46
输入:
2018 214
输出:
3 92 205 1
说明:
f(x) = x^2 + 205x + 92 = (x - 9)(x + 214) + 2018
本题需要用到余式定理。余式定理的内容是多项式f(x)除以多项式(x – a)的余式为f(a)。
余式定理的证明可以参考余式定理_百度百科。
这里需要f(x)除以(x + k)余p,就是说f(-k) = p。那么我们只需要将p化成一个-k进制数即可。这样看来,本题不存在无解的情况。
复杂度:O(\log p)
// Code by KSkun, 2018/2
#include <cstdio>
#include <vector>
long long p;
int k;
std::vector<int> vec;
int main() {
scanf("%lld%d", &p, &k);
while(p != 0) {
vec.push_back((p % k + k) % k);
p = (vec.back() - p) / k;
}
printf("%d\n", vec.size());
for(int i = 0; i < vec.size(); i++) {
printf("%d ", vec[i]);
}
return 0;
}
给n个圆,求n个圆将平面分成几个区域(内部不包含任何弧线)。
数据范围:1 \leq n \leq 3
输入:
3 0 0 1 2 0 1 4 0 1
输出:
4
说明:
输入:
3 0 0 2 3 0 2 6 0 2
输出:
6
说明:
输入:
3 0 0 2 2 0 2 1 1 2
输出:
8
说明:
需要使用欧拉定理(V-E+F=2)解决。暂未解决。
复杂度:
题目地址:Codeforces:Problem – 842D – Codeforces、vjudge:Vitya and Strange Lesson – CodeForces 842D – Virtual Judge
Today at the lesson Vitya learned a very interesting function — mex. Mex of a sequence of numbers is the minimum non-negative number that is not present in the sequence as element. For example, mex([4, 33, 0, 1, 1, 5]) = 2 and mex([1, 2, 3]) = 0.
Vitya quickly understood all tasks of the teacher, but can you do the same?
You are given an array consisting of n non-negative integers, and m queries. Each query is characterized by one number x and consists of the following consecutive steps:
Note that after each query the array changes.
给一个数列,每次查询给出一个值x,先对数列中每个元素异或上x,然后查询数列的mex值(即数列中不包含的最小非负整数)。
输入格式:
First line contains two integer numbers n and m (1 ≤ n, m ≤ 3 \cdot 10^5) — number of elements in array and number of queries.
Next line contains n integer numbers ai (0 ≤ ai ≤ 3 \cdot 10^5) — elements of then array.
Each of next m lines contains query — one integer number x (0 ≤ x ≤ 3 \cdot 10^5).
输出格式:
For each query print the answer on a separate line.
输入样例#1:
2 2 1 3 1 3
输出样例#1:
1 0
输入样例#2:
4 3 0 1 5 6 1 2 4
输出样例#2:
2 0 0
输入样例#3:
5 4 0 1 5 6 7 1 1 4 5
输出样例#3:
2 2 0 2
冷静分析一下这题,发现异或操作是对整个数列的,这个直接在外面维护标记就行了,因为异或满足结合律。找mex是一个难点,需要用01Trie处理。
先把数列元素去个重插入01Trie中,预先算好发现数据最大是大约19位二进制,那就存20位算了,插入的同时统计一下每个子树中有多少数。每次查询的时候先把查询的这一位异或上,即如果要异或的数为1,则1当0处理,0当1处理。要异或的数为0时,选择0子树肯定是比较小的,但是要看一下这个子树有没有满,满了只能向1走,否则向0走,当然如果子树为空直接返回即可。每次走的时候维护一个结果,如果向1走要在结果的这一位上设成1。这个结果就是询问的答案。
// Code by KSkun, 2018/2
#include <cstdio>
#include <algorithm>
int trie[6000005][2], tot = 1, has[6000005];
inline void insert(int n) {
int t = 1;
for(int i = 20; i > 0; i--) {
has[t]++;
if(n & (1 << (i - 1))) {
if(!trie[t][1]) trie[t][1] = ++tot;
t = trie[t][1];
} else {
if(!trie[t][0]) trie[t][0] = ++tot;
t = trie[t][0];
}
}
has[t]++;
}
int dlt = 0;
inline int query() {
int t = 1, res = 0;
for(int i = 20; i > 0; i--) {
if(dlt & (1 << (i - 1))) {
if(!trie[t][1]) {
return res;
} else if(has[trie[t][1]] < (1 << (i - 1))) {
t = trie[t][1];
} else {
t = trie[t][0];
res += (1 << (i - 1));
}
} else {
if(!trie[t][0]) {
return res;
} else if(has[trie[t][0]] < (1 << (i - 1))) {
t = trie[t][0];
} else {
t = trie[t][1];
res += (1 << (i - 1));
}
}
}
return res;
}
int n, m, a[300005], t;
int main() {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
std::sort(a, a + n);
n = std::unique(a, a + n) - a;
for(int i = 0; i < n; i++) {
insert(a[i]);
}
for(int i = 0; i < m; i++) {
scanf("%d", &t);
dlt ^= t;
printf("%d\n", query());
}
return 0;
}
比赛地址:Dashboard – Codeforces Round #461 (Div. 2) – Codeforces,也可以在底下的赛题列表点进题目。
由于这场比赛我并不打算也没有时间重新把代码写一遍,这里仅提供思路。
#A 922A – Cloning Toys | 思维
#B 922B – Magic Forest | 暴力、枚举
#C 922C – Cave Painting | 数论、暴力
#D 922D – Robot Vacuum Cleaner | 贪心
#E 922E – Birds | DP
#F 922F – Divisibility | 数论、构造
最开始你有1个原始玩具,有一个玩具复制机,有两种复制模式:①输入一个原始玩具(但不消耗)并额外输出一个原始玩具和一个复制玩具;②输入一个复制玩具(但不消耗)并额外输出两个复制玩具。现在想得到x个复制玩具和y个原始玩具,想知道是否可行。
数据范围:0 \leq x, y \leq 10^9
输入:
6 3
输出:
Yes
说明:
2次①,2次②。
输入:
4 2
输出:
No
输入:
1000 1001
输出:
Yes
易知,y-1就是①操作的次数,这个时候只要求出x-(y-1)就能知道需要②操作进行多少次,如果这个值是奇数,显然②操作是做不到的,这个时候需要输出No
,其他时候输出Yes
。
别忘了特殊情况!
首先,y=0绝对不可行。y=1的时候,x只能为0。以及必须满足x-(y-1)>0。
复杂度:O(1)
给定值n,求出满足以下条件的有序数对(a, b, c)数。
数据范围:1 \leq n \leq 2500
输入:
6
输出:
1
说明:
只有一组(3, 5, 6)
输入:
10
输出:
2
看到这个数据范围,就想可以枚举来做。
枚举i,j,求出i^j,然后判断答案合不合法,统计一下合法的数量。i可以从6开始枚举。
复杂度:O(n^2)
给定值n, k,判断是否符合条件:不存在有序实数对(i, j),满足以下条件
数据范围:1 \leq n, k \leq 10^{18}
输入:
4 4
输出:
No
说明:
4 \mod 1 = 4 \mod 4 = 0
输入:
5 3
输出:
Yes
让我们从1开始探索n应当满足的条件,可以发现下列式子
\begin{matrix} n \equiv 0 \ (\mod 1) \\ n \equiv 1 \ (\mod 2) \\ n \equiv 2 \ (\mod 3) \\ \cdots \\ n \equiv k - 1 \ (\mod k) \end{matrix}
这个条件一列出来,经过冷静的分析,机制的你发现了n = {\rm lcm}\{1, 2, \cdots, k\} - 1。然后再冷静分析一波,算一下k的上限,发现n取到上限的时候k应该很小(43),借用一些辅助工具甚至可以把这个表打出来。
比较可取的做法是对于比较大的k直接输出No
,k比较小的时候就用上面条件验证一下。
复杂度:取决于你设的k的上限
给你n个只包含s
和h
的串,一个串的价值是其中的满足i\<j且i位置为s
且j位置为h
的(i, j)对数。求一种排序方式,使得这些串排完序拼接成的长串的价值最大。只需要输出最大价值。
数据范围:1 \leq n \leq 10^5
输入:
4 ssh hs s hhhs
输出:
18
说明:
按照ssshhshhhs
的顺序获得最大价值。
输入:
2 h s
输出:
1
按照经验来说这种题往往都是贪心,而它确实是个贪心。依据:设s_i表示i串的s
字符数,h_i表示i串的h
字符数,保证\frac{s_i}{h_i}在排序中单调不增即可。
证明如下:
设想一个排序中有相邻的i, j两串,考察满足什么条件的时候交换它们的顺序答案会更优。我们发现它们内部对答案的贡献与它们的顺序无关,这个整体与排序中其他部分联合产生的贡献也与它们的顺序无关,这个时候只需要讨论它们之间产生的贡献。当s_i \cdot h_j > s_j \cdot h_i时排序不需要交换,整理就得到了s/h比较大的串应该在前面的结论。
复杂度:O(n \log n)(排序算法的复杂度)
有n棵树,每棵树上有鸟巢,第i棵树的鸟巢里有ci只鸟,获得第i棵树的鸟每只花费costi能量,同时使能量上限增加B,每棵树可以自由选择获得0~ci只鸟。最开始的时候能量和能量上限都是W,从一棵树走向下一棵树的时候恢复X能量,但是拥有的能量不能超过当前能量的上限。求最后最多能获得多少鸟。
输入第一行n, W, B, X,第二行ci,第三行costi。
数据范围:1 \leq n \leq 10^3, 0 \leq W, B, X \leq 10^9, \sum_{i=1}^n c_i \leq 10^4, 0 \leq cost_i \leq 10^9
输入:
2 12 0 4 3 4 4 2
输出:
6
说明:
第一棵树获得2只,第二棵获得4只。
输入:
4 1000 10 35 1 2 4 5 1000 500 250 200
输出:
5
说明:
从最后一棵树获得5只。
输入:
2 10 7 11 2 10 6 1
输出:
11
发现当前剩余能量这个显然不能放进DP的维度里,于是可以考虑把这个当做DP值,把当前获得的鸟数量作为DP的一个维度,于是设计出DP的状态:dp[i][j]表示到第i课树,当前获得了j只鸟的剩余能量。
然后就可以推一下方程了
dp[i][j] = \max \{\min\{dp[i-1][j-k]+X, W+(j-k)*B\} - k*cost_i\}
然后我们发现它应该是一个O(n)的转移,枚举k即可。
复杂度:O(n^2)
给定n,找到一个\{1, 2, \cdots, n\}的子集I,满足I中满足a, b都来自I,a\<b且a|b的有序实数对(a, b)的数量为k。
如果找不到,输出No
,如果找得到,输出一种I。
数据范围:2 \leq n \leq 3 \times 10^5, 1 \leq k \leq \min (10^9, \frac{n(n-1)}{2})
输入:
3 3
输出:
No
输入:
6 6
输出:
Yes 5 1 2 4 5 6
说明:
有序实数对是(1, 2)(1, 4)(1, 5)(1, 6)(2, 4)(2, 6)。
输入:
8 3
输出:
Yes 4 2 4 5 8
说明:
有序实数对是(2, 4)(2, 8)(4, 8)。
首先说判定。当k比集合\{1, 2, \cdots, n\}产生的答案还大的时候,就不存在。这个集合产生的答案就是每个数字除了它本身和1以外的因子数,这个可以O(n \log n)预处理出来再前缀和得到。
接下来构造。首先找到一个前缀和不大于k的最大位置pos,我们要选出答案为k的集合,换句话就是说要在\{1, 2, \cdots, pos\}删元素删到答案为k,考虑从pos/2~pos这一段开始删,这一段的因子数本来就比较大,而且删除这些数并不会对比其他数对答案的贡献产生影响。删完以后把剩下的输出就行。
复杂度:O(n \log n+2n)