您的位置:首页 > 其它

【解题报告】Codeforces Round #401 (Div. 2)

2017-02-25 17:55 267 查看
题目链接

A. Shell Game(Codeforces 777A)

思路

为了更好地找到解题关键,先将小球随杯子运动的轨迹画在纸上。具体地,假设初始状态球在杯子 1 中。在纸上画一个数组 d ,其中 d[i] 表示当杯子被移动 i 次时,小球的位置。不难发现, d[i] 随 i 的增大呈现出周期性,其周期为 6 。对任意初始状态都可以画出这种数组。

于是我们用二维数组 d[i][j] 表示初始状态下小球在杯子 i 中,当杯子被移动了 j 次时小球的位置。对任意输入 n ,枚举i使得 d[i][n]=x ,那么 i 就是答案。

代码

#include <bits/stdc++.h>
using namespace std;

int n, x;
int d[3][6] = { {0, 1, 2, 2, 1, 0},
{1, 0, 0, 1, 2, 2},
{2, 2, 1, 0, 0, 1} };

int main() {
//  freopen("data.txt", "r", stdin);
cin >> n >> x;
n = n % 6;
for(int i = 0; i < 3; i++) {
if(d[i]
== x) {
cout << i << endl;
break;
}
}
return 0;
}


B. Game of Credit Cards(Codeforces 777B)

思路

首先这个数据规模暴力肯定是不行了。那么潜在的,可能有贪心策略也可能是动态规划。那么先考察有没有贪心策略。因为两个字符串的序对本题是没有影响的(因为M已经知道S的出牌顺序)。因此考虑对两个字符串进行排序。我们分两种策略来考虑:

被 S 攻击的最少次数。假设我们是 M ,那么我们的目标是让对方的牌尽可能不发挥作用。我们从当前 S 的点数最大的 S[i] 这儿考虑。为了让 S[i] 哑火,我们可以拿出最大的 M[j] 来压制它。这样为什么是最好的呢?因为如果 M[j] 都压不住它,那么也没有牌能够压住它了。况且如果此时不用 M[j] ,有什么理由以后再用它呢?有人可能会反驳:能不能放弃压制 S[i] ,把 M[j] 留到后面用呢?假如这样做,那么最好的情况是结果不变,最差的情况是结果变大了(也就是次优解)。因此我们按照点数从大到小枚举 S[i] ,用当前最大的牌来压制对方即可。

攻击 S 的最多次数。假设我们是 M ,那我们的目标是让我们的牌尽可能发挥作用。那么我们从 M 的点数最大的 M[j] 这儿考虑。为了让 M[j] 发挥最大效果,我们选择比 M[j] 小的(或相等的)最大的 S[i] 下手。因为对于任意满足 S[k]≤S[i] 的 k ,都存在 S[k]≤M[j] ,那么将 M[j] 用在 S[i] 上最利于我方的后来的牌的发挥。我们同样可以从大到小枚举 S[j] ,用当前最大的牌来压制对方。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1010;
char S[maxn], M[maxn];
int n, Max, Min;

int main() {
//  freopen("data.txt", "r", stdin);
scanf("%d%s%s", &n, S, M);
sort(S, S + n);
sort(M, M + n);
int j = n - 1;
for(int i = n - 1; i >= 0; i--) {
if(S[i] > M[j]) {
Min++;
}
else {
j--;
}
}
j = n - 1;
for(int i = n - 1; i >= 0; i--) {
if(S[i] < M[j]) {
Max++;
j--;
}
}
printf("%d\n%d\n", Min, Max);
return 0;
}


C. Alyona and Spreadsheet(Codeforces 777C)

思路

对于这题,不少人都能够立即想到暴力的解法:对于每列 j ,检查行区间 [l,r] 中的每行,看看这些行是不是在 j 列上单调不减。这是不错的想法,但是时间复杂度太大了,达到了 O(nm+n+m) 。

事实上,这种要对矩阵进行某种统计,暴力解复杂度太大的题,多半是要维护某种神奇的量,使得我们只用枚举行或枚举列就能完成这种统计。话说 CF 出这种题已经不是一次两次了。

那么这题怎么样呢?我们可以事先统计 d[j][i] ,表示以 i 行 j 列的元素 aij 为终点的最长上升子串(注意是子串而不是子序列)的长度为多少。此时原问题就转化成

对于第 r 行,是否存在一个 j ,使得 r−d[j][r]+1≤l 。

不等式左边的式子表示以 r 为区间右端点 d[j][r] 为区间长度的区间左端点的编号(也就是本题的行号)是多少。将式子变形后问题转化成

是否存在一个 j ,使得 d[j][r]≥r−l+1 。

显然这个问题就是在问

max{d[j][r]}≥r−l+1 是否成立。

那么对于每个 r ,维护 d[j][r] 的最大值即可。这既是我前面所说的神奇的量

最后,实现上需要注意一个细节。因为题目只说 n×m≤105 ,所以理论上说 n,m 都有可能达到 105 。在存矩阵的时候如果用二维数组的话会超内存限制。所以得采用类似图论中邻接表的数据结构才能存下。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;
vector <int> a[maxn], d[maxn];
int n, m, k, l, r, num, Max[maxn];

int main() {
//  freopen("data.txt", "r", stdin);
scanf("%d%d", &n, &m);
// 输入并存储矩阵
for(int i = 1; i <= n; i++) {
a[i].push_back(0);
for(int j = 1; j <= m; j++) {
scanf("%d", &num);
a[i].push_back(num);
}
}
for(int j = 1; j <= m; j++) {
d[j].push_back(0);
d[j].push_back(1);
}
// 对每列计算以某个元素结尾的最长不下降子串
for(int j = 1; j <= m; j++) {
for(int i = 2; i <= n; i++) {
if(a[i][j] >= a[i-1][j]) {
d[j].push_back(d[j][i-1] + 1);
}
else {
d[j].push_back(1);
}
}
}
// 统计每行的列最长不下降子串的最大值
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
Max[i] = max(Max[i], d[j][i]);
}
}
// 回答查询
scanf("%d", &k);
while(k--) {
scanf("%d%d", &l, &r);
puts(r - Max[r] + 1 <= l ? "Yes" : "No");
}
return 0;
}


D. Cloud of Hashtags(Codeforces 777D)

思路

这题从数据规模和“字典序”这个这么强的条件来看,可能存在贪心策略。首先从先往后考虑,结果是没什么结果。那么从后往前考虑呢?首先,对于一个字符串,我们删除它的后缀只会让它的字典序变小而不是变大。那么,对于倒数第一个串 s[n] 和倒数第二个串 s[n−1] ,如果字典序 s[n]<s[n−1] ,那么我们对 s[n] 做任何事情都于事无补,但可以删除 s[n−1] 的后缀。

从这个灵感不难得出贪心算法:我们从第一位开始逐位比较两个字符串 s[i] 和 s[i−1] 。

如果出现某位j使得 s[i][j]>s[i−1][j] ,那么一切顺利。

如果出现某位j使得 s[i][j]<s[i−1][j] ,那么从 j 开始的 s[i−1] 的后缀要全部删除掉。

如果一直是 s[i][j]==s[i−1][j] 直到某个串被遍历完,那么只需要 s[i] 的长度大于或等于 s[i−1] 的长度即可。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 5e5 + 10;
string s[maxn];
bool equ;
int n, p1, p2;

int main() {
//  freopen("data.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> s[i];
}
for(int i = n - 1; i >= 1; i--) {
equ = true;
p1 = p2 = 0;
while(true) {
if(s[i+1][p1] < s[i][p2]) {
if(true == equ) {
s[i] = s[i].substr(0, p2);
}
break;
}
if(s[i+1][p1] > s[i][p2]) {
equ = false;
}
if(++p2 >= s[i].size()) {
break;
}
if(++p1 >= s[i+1].size()) {
if(true == equ) {
s[i] = s[i].substr(0, p2);
}
break;
}
}
}
for(int i = 1; i <= n; i++) {
cout << s[i] << endl;
}
return 0;
}


E. Hanoi Factory(Codeforces 777E)

思路

这题显然不是贪心能够解决的(很难找到某个顺序进行某种贪心策略)。考虑动态规划。我们需要有一个“序”来让动规满足“无后效性”。根据题目的特点先按照外径从大到小对 ring 排序,当外径相等时按照内径从大到小排序(相当于排在前面的一定放在下面)。这时候就可以设计动态规划算法了。

令状态 i 表示当前考虑到 ringi 这个物品,且 ringi 是放在塔顶的物品, d[i] 表示状态 i 下的最优解。若某个状态 j 可以转移到状态 i ,那么物品 ringi 的外径一定要严格小于 ringj 的外径,且物品 ringi 的外径一定要严格大于 ringj 的内径,用方程来描述状态转移过程中最优解的变化有( a,b,h 数组分别存放内径,外径和高度)

d[i]=max{d[j],j<i,b[i]>a[j]}

这看上去会是一个 O(n2) 的算法。让我们来尝试优化它(所以一定要熟悉动态规划及其优化技巧,这样才能自信认为这样的优化会奏效)。根据 j<i 这个信息。我们可以开一个数组 data[] ,假设我们处理到状态 i 了, data[k] 表示在状态 i 之前出现过的内径为 k 的物品在塔的最顶端时的最优解。那么我们就可以通过区间最大值查询 RMQ(1,b[i]−1) ,来得知 max{d[j],j<i,b[i]>a[j]} (相当于不需要知道 j ,而直接知道了 d[j] ),从而更新 d[i] ,然后通过 d[i] 来更新 data[a[i]] 。那么这有什么用呢?将问题逐步变形至此有什么好处呢?答案是,动态的 RMQ 以及修改元素是可以用线段树在 O(logn) 的时间内完成的(需要对内径离散化)。只要将 data 数组用线段树维护起来就好了。这样,本题的复杂度达到了 O(nlogn) ,这是可以接受的。

代码

#include <bits/stdc++.h>
using namespace std;

#define lch (k << 1)
#define rch (k << 1 | 1)
#define mid ((l + r) >> 1)

typedef long long ll;
const int maxn = 2e5 + 10;
map <int, int> mp;
int n, a, b, h, m, foo[maxn], bar[maxn];
ll d, tmp, ans;

// 排序用的ring结构体
struct ring {
int a, b, h;
ring() {}
ring(int a, int b, int h):a(a), b(b), h(h) {}
bool operator < (const ring& o) const {
if(b == o.b) {
return a > o.a;
}
return b > o.b;
}
}rings[maxn];

// 线段树
template <class T>
struct Tree {
T data[maxn<<2];
T operate(T x, T y) {
return max(x, y);
}
void pushUp(int k) {
data[k] = operate(data[lch], data[rch]);
}
// 建树
void build(int k, int l, int r) {
if(l == r) {
data[k] = 0;
return;
}
build(lch, l, mid);
build(rch, mid + 1, r);
pushUp(k);
}
// 修改
void update(int a, T v, int k, int l, int r) {
if(l == r) {
data[k] = v;
return;
}
if(a <= mid) {
update(a, v, lch, l, mid);
}
else {
update(a, v, rch, mid + 1, r);
}
pushUp(k);
}
// 查询
T query(int a, int b, int k, int l, int r) {
if(a <= l && r <= b) {
return data[k];
}
ll res = 0;
if(a <= mid) {
res = operate(res, query(a, b, lch, l, mid));
}
if(b > mid) {
res = operate(res, query(a, b, rch, mid + 1, r));
}
return res;
}
};

Tree <ll> o;

// 离散化
int dec(int a[], int b[], int n, map <int, int>& mp) {
copy(a + 1, a + n + 1, b + 1);
sort(b + 1, b + n + 1);
int m = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++) {
mp[a[i]] = lower_bound(b + 1, b + m + 1, a[i]) - b;
}
return m;
}

int main() {
//  freopen("data.txt", "r", stdin);
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d%d%d", &a, &b, &h);
rings[i] = ring(a, b, h);
foo[i] = a;
foo[n + i] = b;
}
// 离散化
m = dec(foo, bar, 2 * n, mp);
o.build(1, 1, m);
sort(rings + 1, rings + n + 1);
for(int i = 1; i <= n; i++) {
a = rings[i].a;
b = rings[i].b;
h = rings[i].h;
// 查询d[j]
if(mp[b] >= 2) {
d = o.query(1, mp[b] - 1, 1, 1, m) + h;
}
else {
d = h;
}
// tmp相当于d[i]
tmp = o.query(mp[a], mp[a], 1, 1, m);
// 将d[i]插入线段树
if(d > tmp) {
o.update(mp[a], d, 1, 1, m);
}
// 用d[i]更新答案
ans = max(ans, d);
}
printf("%I64d\n", ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息