2018CCPC吉林赛区
Contest Info
[Practice Link](https://cn.vjudge.net/contest/311655)
Solved | A | B | C | D | E | F | G | H | I | J | K | L |
---|---|---|---|---|---|---|---|---|---|---|---|---|
7/12 | Ø | Ø | Ø | Ø | - | Ø | - | Ø | Ø | - | - | - |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
A.The Fool
签到题。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
printf("Case %d: ", kase);
int n; scanf("%d", &n);
ll res = 0;
for (int i = 1, j; i <= n; i = j + 1) {
j = n / (n / i);
res += (j - i + 1) % 2 * (n / i) % 2;
res %= 2;
}
puts(res ? "odd" : "even");
}
return 0;
}
B.The World
题意:
给出十二小时制的时间,以及两个时区,要求转换时间。
思路:
- 12:00 AM 是凌晨十二点
- 12:00 PM 是中午十二点
- \((h - 1) % 12 + 1\),如果\(h = 0\)的时候,输出的是\(0\),显然应该输出\(12\)
代码:
#include <bits/stdc++.h>
using namespace std;
struct node {
int h, m;
char p[5];
node() {}
void scan() {
scanf("%d:%d %s", &h, &m, p);
if (h == 12 && p[0] == 'A') {
h = 0;
}
if (p[0] == 'P' && h != 12) h += 12;
}
void print() {
if (h < 0) {
printf("Yesterday ");
h += 24;
} else if (h >= 0 && h < 24) {
printf("Today ");
} else {
printf("Tomorrow ");
h -= 24;
}
printf("%d:%02d ", (h + 11) % 12 + 1, m);
string s = "AM";
if (h >= 12) {
s = "PM";
}
cout << s << "\n";
}
}A;
char C[20], D[20];
int mp[220];
int main() {
mp['B'] = 8;
mp['W'] = -5;
mp['L'] = 0;
mp['M'] = 3;
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
printf("Case %d: ", kase);
A.scan();
scanf("%s%s", C, D);
int gap = 0;
gap -= mp[C[0]];
gap += mp[D[0]];
A.h += gap;
A.print();
}
return 0;
}
C.Justice
题意:
有\(n\)个物品,每个物品的重量为\(\frac{1}{2^{k_i}}\),问能够将物品分成两组,使得每一组的重量总和都大于等于\(\frac{1}{2}\)
思路:
假如可以,那么一定存在一种方案能够拼成两个\(\frac{1}{2}\),因为向上合并的过程不可能直接跳过\(\frac{1}{2}\)。
那么只要暴力合并就可以了,直到出现两个\(\frac{1}{2}\),那么输出方案即可。
否则就是\(NO\)
其实本来觉得\(k\)很大的时候,这个\(\frac{1}{2^{k_i}}\)是没用的,不过确实是没用的,只不过这个很大至少要到\(10^5\)之后了。
不然每个元素都有一个,可以一直合并上去,拼成\(\frac{1}{2}\),没注意到这点。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define pii pair <int, int>
#define fi first
#define se second
int n, a[N];
int res[N];
map <int, vector <int>> mp;
map <int, int> vis;
bool F;
void merge(vector <int> &tmp, int x) {
if (vis[x] == 0) {
mp[x] = tmp;
tmp.clear();
vis[x] = 1;
} else {
if (x == 1) {
for (auto it : mp[x]) res[it] = 1;
puts("YES");
for (int i = 1; i <= n; ++i) printf("%d", res[i]);
puts("");
F = 1;
return;
}
for (auto it : tmp) mp[x].push_back(it);
vis[x] = 0;
tmp.clear();
merge(mp[x], x - 1);
}
}
void solve() {
for (int i = 1; i <= n; ++i) {
vector <int> tmp;
tmp.push_back(i);
merge(tmp, a[i]);
if (F) return;
}
puts("NO");
}
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
mp.clear(); vis.clear();
printf("Case %d: ", kase);
scanf("%d", &n);
F = 0;
for (int i = 1; i <= n; ++i) res[i] = 0;
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
solve();
}
return 0;
}
D.The Moon
题意:
玩一个游戏,步骤如下:
0. 令初始概率\(q = 2\%\)
- 玩家需要玩一个游戏,赢的概率为\(p\)
- 如果玩家赢了,那么进入步骤三,否则进入步骤四
- 玩家会以\(q\)的概率获得宝物,如果获得宝物,游戏结束,否则会令\(q = min(200\%, q + 2\%)\)
- 令\(q = min(100\%, q + 1.5\%)\),然后回到步骤\(1\)
求期望游戏局数。
思路:
令\(f[i]\)表示概率为\(q\)时的期望游戏局数,为了方便转移,可以把概率都扩大\(2\)倍。
最后答案就是\(f[4]\)
代码:
#include <bits/stdc++.h>
using namespace std;
#define db double
db f[300], p;
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
printf("Case %d: ", kase);
scanf("%lf", &p);
p *= 0.01;
f[200] = 1.0 / p;
for (int i = 200; i >= 4; --i) {
db q = i * 0.01 / 2;
f[i] = p * (1 - q) * f[min(200, i + 4)] + (1 - p) * f[min(200, i + 3)] + 1;
}
printf("%.12f\n", f[4]);
}
return 0;
}
F.The Hermit
题意:
有\(n\)做信号塔,每座信号塔的照亮范围为\([i - rad_i + 1, i + rad_i - 1]\),其他信号塔能收到该灯塔当且仅当它的位置在该信号塔的范围内。
并且有\(i - rad_i + 1 \leq i + 1 - rad_{i + 1} + 1\)
现在对于每座灯塔\(i\),要求找出有多少座灯塔\(k\)满足要求:
- \(k < i\),并且\(k\)能收到\(i\)的信号
- 并且存在\(k \leq j < i\),\(k\)和\(i\)都可以收到\(j\)的信号,并且\(k \rightarrow j\)的距离要大于等于\(j \rightarrow i\)的距离
思路:
每个信号塔的答案就是\(max(0, rad_i - 2)\)。
为什么?
考虑第\(i\)个信号塔的覆盖范围左边离它最近的那个是不行的,因为不满足\(k \rightarrow j\)的距离要大于等于\(j \rightarrow i\)的距离,
那其他的都是可以的。
显然当\(i\)的覆盖范围能覆盖到\(i - 1\)的时候,那么\(i - 1\)显然能够覆盖到\(i\),并且\(i - 1\)的左边覆盖范围的边界要小于\(i\),也就是说\(i\)能覆盖到的左边的区域,\(i - 1\)都能够覆盖到。
那么就让\(i - 1\)来当中继节点\(j\),那么\(i\)左边的覆盖范围除了\(i - 1\),其他都是可以的。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
printf("Case %d: ", kase);
int n, x, res = 0;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &x);
res ^= max(0, x - 2);
}
printf("%d\n", res);
}
return 0;
}
H.Lovers
题意:
有\(n\)个字符串,支持两种操作:
- wrap \(l\;r\;d\),将\([l, r]\)区间内的字符串\(s_i\)变成\(ds_id\),\(d \in [0, 9]\)
- query \(l\;r\),询问\([l, r]\)区间内所表示的字符串的数值和
思路:
考虑标记\(T(A, B, lenAB)\):
- \(A\)表示在字符串左边添加的数值大小
- \(B\)表示在字符串串右边添加的数值大小
- \(lenAB\)表示\(A\)或\(B\)的长度的\(10\)的幂次和。
显然\(A\)和\(B\)的长度相等,并且\(B\)是\(A\)的反转。
考虑节点信息\(N(sum, len, cnt)\):
- \(sum\)表示该区间的数值和
- \(len\)表示该区间的数值的长度的\(10\)的幂次和
- \(cnt\)表示该区间的长度
考虑标记合并:
考虑\(T_1(A_1, B_1, lenAB_1)\)与标记\(T_2(A_2, B_2, lenAB_2)\)的合并:
- \(A_3 = A_2 \cdot lenAB_1 + A_1\)
- \(B_3 = B_1 \cdot lenAB_2 +B_2\)
- \(lenAB_3 = lenAB_1 \cdot lenAB_2\)
考虑标记下放:
\(T(A, B, lenAB)\)与\(N(sum, len, cnt)\):
- \(sum = A \cdot len \cdot lenAB + sum \cdot lenAB + cnt \cdot B\)
- \(len = len \cdot lenAB \cdot lenAB\)
考虑节点信息合并:
直接合并即可,注意加的时候也要取模。
然后线段树维护即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
const ll p = 1e9 + 7;
int n, q;
void add(ll &x, ll y) {
x += y;
if (x >= p) x -= p;
}
struct SEG {
struct node {
ll sum, len, cnt;
ll A, B, lenAB;
node(ll _cnt = 0) {
cnt = _cnt;
sum = 0;
A = B = 0;
len = lenAB = 1;
}
void add(ll _A, ll _B, ll _lenAB) {
len = len * _lenAB % p;
sum = (_A * len % p + sum * _lenAB % p + cnt * _B % p) % p;
len = len * _lenAB % p;
A = (_A * lenAB % p + A) % p;
B = (B * _lenAB % p + _B) % p;
lenAB = lenAB * _lenAB % p;
}
node operator + (const node &other) const {
node res = node();
res.sum = (sum + other.sum) % p;
res.len = (len + other.len) % p;
res.cnt = cnt + other.cnt;
return res;
}
}t[N << 2];
void build(int id, int l, int r) {
t[id] = node(r - l + 1);
if (l == r) {
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
t[id] = t[id << 1] + t[id << 1 | 1];
}
void pushdown(int id) {
ll &A = t[id].A;
ll &B = t[id].B;
ll &lenAB = t[id].lenAB;
if (lenAB == 1) return;
t[id << 1].add(A, B, lenAB);
t[id << 1 | 1].add(A, B, lenAB);
A = B = 0;
lenAB = 1;
}
void update(int id, int l, int r, int ql, int qr, ll _A, ll _B, ll _lenAB) {
if (l >= ql && r <= qr) {
t[id].add(_A, _B, _lenAB);
return;
}
int mid = (l + r) >> 1;
pushdown(id);
if (ql <= mid) update(id << 1, l, mid, ql, qr, _A, _B, _lenAB);
if (qr > mid) update(id << 1 | 1, mid + 1, r, ql, qr, _A, _B, _lenAB);
t[id] = t[id << 1] + t[id << 1 | 1];
}
ll query(int id, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr) return t[id].sum;
int mid = (l + r) >> 1;
pushdown(id);
ll res = 0;
if (ql <= mid) add(res, query(id << 1, l, mid, ql, qr));
if (qr > mid) add(res, query(id << 1 | 1, mid + 1, r, ql, qr));
return res;
}
}seg;
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
printf("Case %d:\n", kase);
scanf("%d%d", &n, &q);
seg.build(1, 1, n);
char op[10]; int l, r, d;
while (q--) {
scanf("%s%d%d", op, &l, &r);
switch(op[0]) {
case 'w' :
scanf("%d", &d);
seg.update(1, 1, n, l, r, d, d, 10);
break;
case 'q' :
printf("%lld\n", seg.query(1, 1, n, l, r));
break;
}
}
}
return 0;
}
I.Strength
题意:
\(A\)和\(B\)玩游戏,他们各有\(n\)个怪兽和\(m\)个怪兽,此时是\(A\)的回合,问\(A\)如何操作使得\(B\)受到的伤害最大。
规则如下:
- 两个普通怪兽决斗,一方输了的话,那么另一方会受到两个怪兽攻击力之差的绝对值的伤害,并且输的一方的怪兽将会死去,而活着的另一方没有变化
- 如果一个普通怪兽去击打一个防御怪,那么打死防御怪之后,对方玩家不会受到任何伤害,即使攻击力存在差值
- 只有对方玩家的防御怪全都死后,才可以直接攻击对方玩家,对方玩家收到的伤害便是该怪兽的攻击力
思路:
-
要打防御怪:
如果要打防御怪,那么想法肯定是希望打完所有的怪兽使得伤害更高。
不然的话,防御怪一个不打,打普通怪兽的伤害肯定更高。
那么考虑对于每个防御怪,从小到大遍历,我每次取第一个大于等于它的怪兽去攻击它。
然后判断剩下是否可以击败其他怪兽,显然如果能击败,不论怎么击败,伤害是一样多的。 -
不打防御怪:
那么假定我们要攻击对方的\(m\)只怪兽,那么肯定是拿我们最强的\(m\)支去攻打对方最弱的\(m\)支,并且加入能击败,那么贡献是固定的。
所以产生不同贡献的其实是\(m\),所以我们要确定\(m\)是多少时,伤害最高。
那么我们可以枚举,然后每次相当于对方增加一个较强的怪兽,我方增加一个较弱的怪兽。
按照刚才的思路,那么我方的怪兽应该整体右移,并且增加一个小怪兽,那么我们给每个怪兽一个偏移值,表示这个怪兽最多偏移到哪里就不能偏移了。
那么枚举过程中维护最小偏移值,如果偏移值变为\(0\),那么说明\(m\)不能再增加了。这个过程中更新答案即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
int n, m;
int a[N], b[N];
vector <int> A, B;
//打掉所有怪兽
ll f1() {
ll res = 0;
sort(B.begin(), B.end());
multiset <int> se;
for (int i = 1; i <= n; ++i) se.insert(a[i]);
for (auto it : B) {
auto pos = se.lower_bound(it);
if (pos != se.end()) {
se.erase(pos);
} else {
return 0;
}
}
sort(A.begin(), A.end());
for (auto it : A) {
if (se.empty() || *se.begin() < it) return 0;
res += *se.begin() - it;
se.erase(se.begin());
}
while (!se.empty()) {
res += *se.begin();
se.erase(se.begin());
}
return res;
}
//防御怪一个都不打
ll f2() {
sort(A.begin(), A.end());
ll res = 0;
ll P = 0, Q = 0;
int id = n;
int Move = 1e9;
for (auto it : A) {
int pos = upper_bound(A.begin(), A.end(), a[id]) - A.begin();
Move = min(Move - 1, pos + 1);
if (Move <= 0) break;
P += a[id]; --id;
Q += it;
res = max(res, P - Q);
}
return res;
}
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
scanf("%d%d", &n, &m);
printf("Case %d: ", kase);
A.clear(); B.clear();
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
for (int i = 1; i <= m; ++i) scanf("%d", b + i);
for (int i = 1, x; i <= m; ++i) {
scanf("%d", &x);
x == 0 ?
A.push_back(b[i]):
B.push_back(b[i]);
}
sort(a + 1, a + 1 + n);
ll res = max(f1(), f2());
printf("%lld\n", res);
}
return 0;
}