NOI.ac2020省选模拟赛8

比赛链接

A.SAM

挖坑

B.T1

problem

给出一个字符串\(S\),\(S\)只包含阿拉伯数字,问\(S\)有多少个回文子串\(S[i,j]\)构成了不含前导零的能被三整除的整数。

\(|S|\le 4\times 10^6\)

solution

题面不给数据范围真**

先用\(manacher\)求出来所有回文串。

然后对奇串和偶串分类讨论。

如果一个串是偶串,那么要找的其实就是以中心为左端点\(l\),右半子串中所有满足\(sum_x-sum_l\% 3=0\)\(x\)\(sum_i\)表示前\(i\)个数字的前缀和。注意\(S_x\)不能为0。我们可以先预处理出\(cnt[i][j]\)表示前i个不为0的位置前缀和对3取模为j的位置的数量。

如果一个串是奇串,分类讨论一下发现,如果中心位置的数字是\(k(0\le k\le2)\),那么就是要在右半边找对满足\(sum_x-sum_l\% 3 = x\)\(x\)个数。和上面同样的方法处理即可。

code

/*
* @Author: wxyww
* @Date:   2020-06-08 08:40:06
* @Last Modified time: 2020-06-08 10:12:39
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 5000010;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
int f[N];
char s[N],S[N];

int sum[N][4];

int m,ans,sssum[N];
void manacher() {
	int id = 0,mx = 0;
	for(int i = 1;i <= m;++i) {
		if(i <= id + mx)
			f[i] = min(f[id + id - i],id + mx - i);


		while(i + f[i] <= m && i - f[i] >= 1 && S[i + f[i]] == S[i - f[i]]) ++f[i];
		// if(i == 32) printf("!!%d\n",f[i]);


		int pos = i / 2;

		if(i & 1) {	
			int r = pos + f[i] / 2;
			int t = sssum[pos];
			// for(int j = 0;j < 3;++j) {
				ans += sum[r][t] - sum[pos][t];
			// }
			// printf("%d %d %d\n",pos,f[i],ans - t);
		}
		else {
			if((S[i] - '0') % 3 == 0) ans++;
			int k = S[i] - '0',r = pos + f[i] / 2 - 1;
			ans += sum[r][(k + sssum[pos]) % 3] - sum[pos][(k + sssum[pos]) % 3];
			// printf("%d %d\n",pos,sum[r][(k + sssum[pos]) % 3] - sum[pos][(k + sssum[pos]) % 3]);
		}
		if(i + f[i] >= id + mx) id = i,mx = f[i];
	}
}

int main() {
	// freopen("1.in","r",stdin);
	scanf("%s",s + 1);
	int n = strlen(s + 1);
	int now = 0;
	for(int i = 1;i <= n;++i) {
		now = (now + s[i] - '0') % 3;
		sssum[i] = now;
		for(int j = 0;j < 3;++j) sum[i][j] = sum[i - 1][j];
		if(s[i] == '0') continue;
		sum[i][now]++;
	}
	m = 0;
	S[++m] = '#';
	for(int i = 1;i <= n;++i) {
		S[++m] = s[i];
		S[++m] = '#';
	}
	// printf("%s",S + 1);
	manacher();

	cout<<ans<<endl;
	return 0;
}

C.海盗

problem

\(n\)个海盗从\(1\)\(n\)标号,按编号从小到大来分硬币。当轮到\(i\)分硬币时,他应该保证\(1\sim \{i\}\)号海盗中至少有\(v_i\)个海盗分到的硬币比\(i-1\)分硬币时分得多。同时分出去的总硬币数量应该不超过\(K\)。每个海盗分硬币时都会尽量给自己分的最多。如果无法保证分出去的硬币数量不超过\(K\),那么当前海盗就会分得\(-1\)的硬币。

问对于所有\(i\in [1,n]\)第i个海盗分硬币时会给自己分的多少硬币。

solution

其实整个过程就是从左边找到\(v_i-1\)个分的硬币最少的海盗,让他们分得的硬币数加1,同时让其他海盗分得的硬币数变为0.剩下的硬币就是自己的。

发现这个过程需要区间赋值,区间加,分裂,合并。用平衡树\((Splay)\)可以完成。

注意及时下放标记。

code

/*
* @Author: wxyww
* @Date:   2020-06-08 19:09:06
* @Last Modified time: 2020-06-08 21:52:12
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 2000010;
#define ls TR[cur].ch[0]
#define rs TR[cur].ch[1]
#define int ll
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}

struct node {
	int lazy1,lazy2,ch[2],pre,siz;//lazy1是赋值标记,lazy2是区间加标记.
	ll sum,val;
}TR[N];
void up(int cur) {
	TR[cur].siz = TR[ls].siz + TR[rs].siz + 1;
	TR[cur].sum = TR[ls].sum + TR[rs].sum + TR[cur].val;
}

void pushdown(int cur) {
	if(TR[cur].lazy1 != -1) {
		TR[ls].val = TR[rs].val = TR[ls].lazy1 = TR[rs].lazy1 = TR[cur].lazy1;
		TR[ls].lazy2 = TR[rs].lazy2 = 0;
		TR[ls].sum = TR[ls].val * TR[ls].siz;
		TR[rs].sum = TR[rs].val * TR[rs].siz;
		TR[cur].lazy1 = -1;
	}
	if(TR[cur].lazy2) {
		TR[ls].val += TR[cur].lazy2;TR[rs].val += TR[cur].lazy2;
		TR[ls].sum += TR[cur].lazy2 * TR[ls].siz;
		TR[rs].sum += TR[cur].lazy2 * TR[rs].siz;
		TR[ls].lazy2 += TR[cur].lazy2;
		TR[rs].lazy2 += TR[cur].lazy2;
		TR[cur].lazy2 = 0;
	}
}

int getwh(int cur) {
	return TR[TR[cur].pre].ch[1] == cur;
}

void rotate(int cur) {
	int fa = TR[cur].pre,gr = TR[fa].pre,f = getwh(cur);
	TR[cur].pre = gr;TR[gr].ch[getwh(fa)] = cur;
	TR[TR[cur].ch[f ^ 1]].pre = fa;TR[fa].ch[f] = TR[cur].ch[f ^ 1];
	TR[fa].pre = cur;TR[cur].ch[f ^ 1] = fa;
	up(fa);up(cur);
}
int sta[N],top,root;
void splay(int cur,int to) {

	while(TR[cur].pre != to) {
		if(TR[TR[cur].pre].pre != to) {
			if(getwh(cur) == getwh(TR[cur].pre)) rotate(TR[cur].pre);
			else rotate(cur);
		}
		rotate(cur);
	}
	if(!to) root = cur;
}
int tot;
void ins(int &cur,ll x,int fa) {
	if(!cur) {
		cur = ++tot;
		TR[cur].val  = TR[cur].sum = x;
		TR[cur].siz = 1;
		TR[cur].lazy1 = -1;
		TR[cur].pre = fa;
		splay(cur,0);
		return;
	}

	pushdown(cur);
	if(x > TR[cur].val)
		ins(rs,x,cur);
	else ins(ls,x,cur);
}


int kth(int cur,int k) {
	while(1) {
		pushdown(cur);
		if(k <= TR[ls].siz) cur = ls;
		else if(k > TR[ls].siz + 1) k -= TR[ls].siz + 1,cur = rs;
		else return cur;
	}
}
int n;
ll K;
ll solve(int x) {
	int cur = kth(root,x);
	splay(cur,0);
	ll ret = K - TR[ls].sum - TR[cur].val - x;
	if(ret < 0) {
		ins(root,-1,0);return -1;
	}
	int k = rs;rs = 0;
	up(cur);
	TR[cur].lazy2++;
	TR[cur].val++;TR[cur].sum += TR[cur].siz;
	while(ls) {
		pushdown(cur);
		cur = ls;
	}
	pushdown(cur);	ls = k;TR[k].pre = cur;
	if(k)
		TR[k].lazy1 = TR[k].lazy2 = TR[k].val = TR[k].sum = 0;
	up(cur);
	splay(cur,0);
	ins(root,ret,0);
	return ret;
}

signed main() {
	n = read(),K = read();
	for(int i = 1;i <= n;++i) {
		int x = read();
		if(x == 1) {
			TR[root].lazy1 = TR[root].lazy2 = TR[root].val = TR[root].sum = 0;
			ins(root,K,0);
			printf("%lld\n",K);
		}
		else printf("%lld\n",solve(x - 1));
	}

	return 0;
}
posted @ 2020-06-09 07:52  wxyww  阅读(11)  评论(0编辑  收藏  举报