CSP-S模拟4

A. 石子游戏

考场记得 \(NIM\) 游戏好像是把什么异或起来,然后瞎推半小时推出 \(\%(i + 1)\)

然后,从始至终都是这个暴力分。。。。。

关于正解,其实是优化了取模和异或的过程

\(cnt_x\) 表示 \(x\) 的出现次数,你会发现这里已经把问题转移到了值域上

\(f_{i, j}\) 表示 \(x - i\)\(2^j\)\(1\) 的数的个数

我们把数字拆成若干二进制位, 对每一位的贡献分开处理, 因为异或不进位,所以这是完全可行的

转移考虑分两部分处理,一部分是 \(f_{i + 2^{j + 1}, j}\) , 因为 \(+2^{j + 1}\) 不会影响后面,这一部分是之前有的, 本次新加入的就是 \(\sum_{k = i + 2^j}^{i + 2 ^{j + 1} - 1}cnt_i\)

我们枚举当前的 \(m = i\), 要对 \(m + 1\) 取模,其实是对于每个 \(x\) 写成 \(x - k (m + 1)\) 的形式, 考虑枚举 \(k\) 每次处理 \([k(m + 1), (k + 1)(m + 1) - 1]\) ,记为区间 \([l, r]\)

我们要找的就是该区间内减去 \(l\) 后该位为 \(1\) 的数的个数

我们利用 \(f\) 进行快速求解,先摆出式子

\(tmp =\lfloor (r - l + 1)/2^{j + 1} \rfloor \times 2^{j + 1}\)

\(\displaystyle f_{l, j} - f_{l + tmp, j} + \sum_{i = l + tmp + 2^{j}}^{r}cnt_i\)

然后我们解释一下,

为啥不能直接相减

因为 \(f_{i, j}\) 中我们减去的 \(i\) 不同, 所以实际上他们没啥关系

那么那个式子在干什么,虽然不能都减去 \(l\) 但是我们只关心第 \(j\) 位的贡献,那么我们多减去几个\(2^{j + 1}\) 都无所谓,所以我们找到了最长的 \(2^{j + 1}\) 的段进行处理

这样剩下了一小段,以 \(l + tmp\)为始,发现减去 \(l + tmp\) 后该段是一个从 \(0\) 开始的序列, 根据二进制发现前 \(2^{j}\)个数第 \(j\)位为 \(0\) ,后面到\(r\)(少于 \(2^{j}\)个数)这些数该位为 \(1\) ,所以就是后面的部分

image

结合丑图看一下,就是 \(f\) 相减得到蓝色部分对应数轴上红色部分, 绿色前 \(2^j\) 没有贡献,后面到 \(r\)有贡献

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 500005;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
int n, cnt[maxn], f[maxn][20];
int main(){
	freopen("stone.in","r",stdin);
	freopen("stone.out","w",stdout);
	// freopen("a.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; ++i){
		int x = read();
		cnt[x] = 1 - cnt[x];
	}
	for(int i = 1; i <= n; ++i)cnt[i] += cnt[i - 1];
	for(int i = n; i >= 0; --i)
		for(int j = 0; i + (1 << j) <= n; ++j)
			f[i][j] = f[min(n + 1, i + (1 << (j + 1)))][j] + cnt[min(n, i + (1 << (j + 1)) - 1)] - cnt[i + (1 << j) - 1];
	int mx = log2(n);
	for(int i = 1; i <= n; ++i){
		int lim = n / (i + 1); bool flag = 0;
		for(int j = 0; j <= mx; ++j){
			int sum = 0;
			for(int k = 0; k <= lim; ++k){
				int l = k * (i + 1), r = min(n, (k + 1) * (i + 1) - 1), tmp = ((r - l + 1) >> (j + 1)) << (j + 1);
				int now = f[l][j] - f[l + tmp][j];
				if(l + tmp + (1 << j) - 1 < r)now += cnt[r] - cnt[l + tmp + (1 << j) - 1];
				sum += now;
			}
			if(sum & 1){flag = 1;break;}
		}
		if(flag)printf("Alice ");
		else printf("Bob ");
	}
	return 0;
}

B. 大鱼吃小鱼

显然每次吃能吃的最大的,一次直接吃到能吃原来第一个他吃不了的,这个过程可以用线段树二分 / 平衡树二分实现

这样每次操作完再吃一个就能扩大一倍,所以复杂度是对的

我使用了 \(FHQ\_Treap\) 二分写法,强烈推荐

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 400005;
const ll inf = 1e18 + 1e5;
inline ll read(){
	ll x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
struct FHQ_Treap{
	#define ls t[x].l
	#define rs t[x].r
	struct node{
		ll val, sum;
		int l, r, size, key;
	}t[maxn];
	int root, cnt;
	int New(ll val){t[++cnt].key = rand(); t[cnt].val = t[cnt].sum = val; t[cnt].size = 1; return cnt;}
	void push_up(int x){
		t[x].sum = (t[ls].sum + t[rs].sum + t[x].val);
		t[x].size = (t[ls].size + t[rs].size + 1);
	}
	void split(int x, int &l, int &r, ll val){
		if(!x)return l = r = 0, void();
		if(val < t[x].val){split(t[x].l, l, t[x].l, val); r = x;}
		else{split(t[x].r, t[x].r, r, val); l = x;}
		push_up(x);
	}
	void split_sum(int x, int &l, int &r, ll sum){
		if(!x)return l = r = 0, void();
		if(t[rs].sum >= sum){split_sum(rs, rs, r, sum); l = x; push_up(x); return;}
		if(t[rs].sum + t[x].val >= sum){l = ls; r = x; t[x].l = 0; push_up(x); return;}
		split_sum(ls, l, ls, sum - t[rs].sum - t[x].val);
		r = x; push_up(x);
	}
	int merge(int x, int y){
		if(!x || !y)return x | y;
		if(t[x].key < t[y].key){t[x].r = merge(t[x].r, y);push_up(x);return x;}
		else {t[y].l = merge(x, t[y].l); push_up(y); return y;}
	}
	void insert(ll val){
		int l = 0, r = 0; split(root, l, r, val);
		root = merge(merge(l, New(val)), r);
	}
	void erase(ll val){
		int l = 0, m = 0, r = 0; split(root, l, r, val); split(l, l, m, val - 1);
		m = merge(t[m].l, t[m].r);
		root = merge(merge(l, m), r);
	}
	ll findmin(int x){
		if(x == 0)return inf;
		while(t[x].l){x = t[x].l;}
		return t[x].val;
	}
	int dep = 0;
	int solve(ll s, ll k){
		if(s > k)return 0;
		int l = 0, m = 0, r = 0;
		split(root, l, r, s - 1);
		ll nxt = min(findmin(r), k);
		if(s + t[l].sum <= nxt){
			root = merge(l, r);
			return -1;
		}
		split_sum(l, l, m, nxt - s + 1);
		root = merge(l, r);
		int ans = t[m].size;
		int down = solve(s + t[m].sum, k);
		l = r = 0;
		split(root, l, r, s - 1);
		root = merge(merge(l, m), r);
		if(down == -1)return -1;
		return ans + down;
	}
}t;

int n, q;
int main(){
	srand(time(NULL));
	freopen("fish.in","r",stdin);
	freopen("fish.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; ++i)t.insert(read());
	q = read();
	for(int i = 1; i <= q; ++i){
		int op = read();
		if(op == 1){
			ll s = read(), k = read();
			printf("%d\n", t.solve(s, k - 1));
		}
		if(op == 2)t.insert(read());
		if(op == 3)t.erase(read());
	}
	return 0;
}

C. 黑客

此为本场真 \(T1\) ,确信

上来发现部分分极多,快速码了一个 \((b - a + 1) * (d - c + 1)\)

然后发现\(0 - 999\) 范围很小,于是反过来枚举一下就行了

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll mod = 1e9 + 7;
inline ll read(){
	ll x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
ll gcd(ll a, ll b){return b ? gcd(b, a % b) : a;}
ll a, b, c, d;
int main(){
	freopen("hacker.in","r",stdin);
	freopen("hacker.out","w",stdout);
	a = read(), b = read(), c = read(), d = read();
	ll ans = 0;
	for(int sum = 1; sum <= 999; ++sum){
		for(int x = 1; x < sum; ++x){
			int y = sum - x;
			if(gcd(x, y) != 1)continue;
			ll l1 = (a + x - 1) / x, l2 = (c + y - 1) / y;
			ll r1 = b / x, r2 = d / y;
			ll cnt = min(r1, r2) - max(l1, l2) + 1; cnt %= mod;
			if(cnt > 0) ans = (ans + 1ll * cnt * sum % mod) % mod;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

D. 黑客-续

上来发现是原题,回想起了被 \(TLE coders\)支配的恐惧,决定最后再打,但是除了 \(T3\) 都不会,所以来打了,然后意外的打的很顺利?

原题《数串》

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
const ull base = 1e17;
struct big{
	ull a[105];
	big operator += (const big x){
		a[0] = max(a[0], x.a[0]);
		for(int i = 1; i <= x.a[0]; ++i)a[i] += x.a[i];
		for(int i = 1; i <= a[0]; ++i)if(a[i] >= base)++a[i + 1], a[i] -= base;
		if(a[a[0] + 1])++a[0];
		return (*this);
	}
	big operator *= (const int x){
		for(int i = 1; i <= a[0]; ++i)a[i] *= x;
		for(int i = 1; i <= a[0]; ++i)a[i + 1] += a[i] / base, a[i] %= base;
		if(a[a[0] + 1])++a[0];
		return (*this);
	}
	void clear(){
		for(int i = 1; i <= a[0]; ++i)a[i] = 0;
		a[0] = 0;
	}
	void print(){
		printf("%llu",a[a[0]]);
		for(int i = a[0] - 1; i > 0; --i)printf("%017llu",a[i]);
		printf("\n");
	}
}sum[2][1025], cnt[2][1025];
int n, m, k, ban[10];
big s, c;
int main(){
	freopen("hacker2.in","r",stdin);
	freopen("hacker2.out","w",stdout);
	// freopen("d.out","w",stdout);
	n = read(), m = read(), k = read();
	for(int i = 1; i <= m; ++i){
		int a = read(), b = read();
		ban[b] |= (1 << (a - 1));
	}
	for(int i = 1; i <= k; ++i){
		int now = (1 << (i - 1));
		sum[1][now].a[0] = 1;
		sum[1][now].a[1] = i;
		cnt[1][now].a[0] = 1;
		cnt[1][now].a[1] = 1;
	}
	int mx = 1 << k;
	for(int i = 1; i < n; ++i){
		int nt = i & 1;
		for(int j = 0; j < mx; ++j){
			if(cnt[nt][j].a[0]){
				sum[nt][j] *= 10; big ls = cnt[nt][j];
				for(int p = 1; p <= k; ++p){
					if(!(ban[p] & j)){
						cnt[1 - nt][j | (1 << (p - 1))] += cnt[nt][j];
						sum[1 - nt][j | (1 << (p - 1))] += sum[nt][j];
						sum[1 - nt][j | (1 << (p - 1))] += ls;
					}
					ls += cnt[nt][j];
				}
				cnt[nt][j].clear();
				sum[nt][j].clear();
			}
		}
	}
	for(int i = 0; i < mx; ++i){
		s += sum[n & 1][i];
		c += cnt[n & 1][i];
	}
	c.print(); s.print();
	return 0;
}

放在后面的前记

Delov的题解

9.12 21:48 upd:今天写不完题解了,先放个半成品

9.13 07:40 upd : 写完了

posted @ 2022-09-13 07:41  Chen_jr  阅读(113)  评论(2编辑  收藏  举报