2024-03-15
2024-03-15
美好的一天
昨天剩下的题
一个字符串重新标号之后可以形成回文串 当且仅当 每个字母在这个串中出现的次数要么全是偶数 要么只有一个奇数
我们只关心出现次数的奇偶性,可以用异或来记录
用一个长度为 26 的 01 串 st
来记录,第 i
位表示 ('a'+i)
这个字母出现次数的奇偶性
把字符串扫描一遍 在每个位置 i
把 st
异或上 (1<<(s[i]-'a'))
但是要快速提取出子串的信息
把 st
存成前缀和的形式就行
问题转化为
在每个区间 \([l-1,r]\) 中,有多少对 \(i<j\) 使得 st[i]^st[j]==0
或 st[i]^st[j]==(1<<k)
根据异或的性质 统计 st[i]==st[j]
和 st[i]==st[j]^(1<<k)
的个数即可
可以用莫队实现
- 添加一个位置 x
- 统计之前已有的
st[x]
的数量 - 统计与
st[x]
只有一位不同的数量 st[x]
的数量 +1
- 统计之前已有的
- 删除一个位置
st[x]
的数量 -1- 除去删除后
st[x]
的数量 - 除去与
st[x]
只有一位不同的数量
顺序很重要
finished
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=60060;
int n,m;
char s[N];
int st[N];
int cnt[(1<<26)+5];
int res;
int len;
#define blk(x) ((x-1)/len+1)
struct Query {
int id;
int l,r;
friend bool operator < (Query A,Query B) {
if(blk(A.l)==blk(B.l)) return A.r<B.r;
return blk(A.l)<blk(B.l);
}
}Q[N];
int ans[N];
void add(int x) {
res+=cnt[st[x]];
for(int i=0;i<26;i++) res+=cnt[st[x]^(1<<i)];
cnt[st[x]]++;
}
void del(int x) {
cnt[st[x]]--;
res-=cnt[st[x]];
for(int i=0;i<26;i++) res-=cnt[st[x]^(1<<i)];
}
int main() {
scanf("%d%d%s",&n,&m,s+1);
len=n/sqrt(m);
for(int i=1;i<=n;i++) st[i]=st[i-1]^(1<<(s[i]-'a'));
for(int i=1;i<=m;i++) scanf("%d%d",&Q[i].l,&Q[i].r),Q[i].id=i;
sort(Q+1,Q+m+1);
int p=0,q=1;
for(int c=1;c<=m;c++) {
int x=Q[c].l-1,y=Q[c].r;
while(p<y) add(++p);
while(q>x) add(--q);
while(q<x) del(q++);
while(p>y) del(p--);
ans[Q[c].id]=res;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
Pudding Monsters (CodeForces 526 F)
# 简述题意 #
有一个 \(n\times n\) 的棋盘,每行每列都恰好有一个棋子,问有多少个 \(k\times k \ (k\in [1,n])\) 的子棋盘满足恰好有 \(k\) 个棋子
将二维问题转化为一位
一个长为 \(n\) 的序列,每一位存的是这一纵坐标上的棋子的横坐标
那么原来的问题就变成了
有多少个子序列 \([l, r]\) 使得 \(max-min=r-l\)
移项得 \(max-min+l-r=0\)
等于零不好维护,注意到 \(max-min+l-r\ge 0\) 那么我们要求的就是最小值的个数
枚举所有前缀,那么所有前缀的后缀就是所有子序列
用线段树来维护当前所有后缀的 \(max-min+l-r\) 值的最小值和最小值的个数
分四个部分维护
- \(l\) : 每个后缀的左端点一直是不变的 因此一开始 build 线段树的时候就把 val 设置为 lft 就行
- \(r\) : 右端点每向后移动一次 所有后缀的 r 就加一,区间 -1 即可
- \(max\) : 用单调栈维护,每次要弹出的时候说明左端点为从
stk[top-1]+1
到stk[top]
的这些后缀的 max 需要修改,减去原来的值,加上新加入的纸即可 - \(min\) : 同 max,但是 min 前面是负号,所以加上原来的值,减去新加入的值
在每个右端点处都统计答案
就完了
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=3e5+10;
int n;
struct Segtree {
#define ls (u<<1)
#define rs (u<<1|1)
struct Node {
int l,r;
ll val,num;
ll tag;
}tr[N*4];
void build(int u,int lft,int rgh) {
tr[u].l=lft,tr[u].r=rgh;
tr[u].num=1,tr[u].val=lft;
if(lft==rgh) return;
int mid=lft+rgh>>1;
build(ls,lft,mid),build(rs,mid+1,rgh);
}
void pushup(int u) {
tr[u].val=min(tr[ls].val,tr[rs].val);
tr[u].num=0;
if(tr[ls].val==tr[u].val) tr[u].num+=tr[ls].num;
if(tr[rs].val==tr[u].val) tr[u].num+=tr[rs].num;
}
void pushdown(int u) {
if(!tr[u].tag) return;
tr[ls].val+=tr[u].tag,tr[rs].val+=tr[u].tag;
tr[ls].tag+=tr[u].tag,tr[rs].tag+=tr[u].tag;
tr[u].tag=0;
}
void update(int u,int ul,int ur,ll k) {
if(tr[u].l>=ul&&tr[u].r<=ur) {
tr[u].val+=k,tr[u].tag+=k;
return;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(ul<=mid) update(ls,ul,ur,k);
if(ur>mid) update(rs,ul,ur,k);
pushup(u);
}
}T;
struct Pudding {
int x,y;
friend bool operator < (Pudding A,Pudding B) {
return A.x<B.x;
}
}a[N];
int stkmx[N],stkmn[N];
int tpmx,tpmn;
ll ans;
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y);
sort(a+1,a+n+1);
T.build(1,1,n);
for(int i=1;i<=n;i++) {
T.update(1,1,n,-1);
while(tpmx&&a[stkmx[tpmx]].y<a[i].y) T.update(1,stkmx[tpmx-1]+1,stkmx[tpmx],a[i].y-a[stkmx[tpmx]].y),tpmx--;
stkmx[++tpmx]=i;
while(tpmn&&a[stkmn[tpmn]].y>a[i].y) T.update(1,stkmn[tpmn-1]+1,stkmn[tpmn],a[stkmn[tpmn]].y-a[i].y),tpmn--;
stkmn[++tpmn]=i;
ans+=T.tr[1].num;
}
printf("%lld\n",ans);
return 0;
}
晚上和初三的一块听课,群论,掉线了……
然后看了一晚上的数学……
复习了一下莫比乌斯变换/反演
在看 # 约数个数和 # 这道题的时候
看到了这个公式
\(d(x)\) 表示 \(x\) 的约数个数
还是有必要记住的
数学真的很弱诶,有必要弄一个数学集锦了
然后研究了老师发的数学的课件