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;
}