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\%\)

  1. 玩家需要玩一个游戏,赢的概率为\(p\)
  2. 如果玩家赢了,那么进入步骤三,否则进入步骤四
  3. 玩家会以\(q\)的概率获得宝物,如果获得宝物,游戏结束,否则会令\(q = min(200\%, q + 2\%)\)
  4. \(q = min(100\%, q + 1.5\%)\),然后回到步骤\(1\)
    求期望游戏局数。

思路:
\(f[i]\)表示概率为\(q\)时的期望游戏局数,为了方便转移,可以把概率都扩大\(2\)倍。

\[f[i] = \left\{ \begin{array}{cccc} p * (1 - q) * f[min(200, i + 4)] + (1 - p) * f[min(200, i + 3)] + 1 && other \\ (1 - p)f[n] + 1 && i = n \end{array} \right. \]

最后答案就是\(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;
}
posted @ 2019-07-17 07:42  Dup4  阅读(635)  评论(0编辑  收藏  举报