【做题纪要】4月“祝祷转过千年诗篇”- 『雪山之眼』
(做题纪要前放点闲话应该没啥问题...吧?)
禾念你虽然pv里的藏文格式全都错了但是应该不至于直接把藏文全删了吧(
还有天依游学记怎么这么快就还剩十几天就要完结了,哭哭
P2408 不同子串个数
板子题,最后结果为
这个我应该在学习笔记里写了,这里挂一下
代码
点击查看代码
namespace solve{
int height[N],sa[N],oldsa[N],rk[N],oldrk[N],cnt[N],key[N];
namespace SA{
inline bool cmp(int x,int y,int w){
return (oldrk[x]==oldrk[y])&&(oldrk[x+w]==oldrk[y+w]);
}
inline void Init(char *s){
int n=strlen(s+1),m=127,tot;
for_(i,1,n)
rk[i]=s[i],
++cnt[rk[i]];
for_(i,1,m)
cnt[i]+=cnt[i-1];
_for(i,n,1) sa[cnt[rk[i]]--]=i;
for(int w=1;;w<<=1,m=tot) {
tot=0;
_for(i,n,n-w+1)
oldsa[++tot]=i;
for_(i,1,n)
if(sa[i]>w)
oldsa[++tot]=sa[i]-w;
memset(cnt,0,sizeof(cnt));
for_(i,1,n)
++cnt[key[i]=rk[oldsa[i]]];
for_(i,1,m)
cnt[i]+=cnt[i-1];
_for(i,n,1)
sa[cnt[key[i]]--]=oldsa[i];
memcpy(oldrk+1,rk+1,n*sizeof(int));
tot=0;
for_(i,1,n)
rk[sa[i]]=((cmp(sa[i],sa[i-1],w))?(tot):(++tot));
if(tot==n)
break;
}
}
inline void Init_H(char *s){
int n=strlen(s+1),tot=0;
for_(i,1,n){
if(!rk[i]) continue;
if(tot) --tot;
while(s[i+tot]==s[sa[rk[i]-1]+tot]) ++tot;
height[rk[i]]=tot;
}
}
}
using namespace SA;
inline void In(){
int n,ans=0;
char s[N];
FastI>>n>>(s+1);
Init(s);Init_H(s);
for_(i,2,n) ans+=height[i];
FastO<<((n*(n+1)/2)-ans)<<endl;
}
}
using namespace solve;
P3181 「HAOI2016」找相同字符
大到小扫描 数组,合并相邻后缀
当前块中的贡献就是第一个串的后缀数 第二个串的后缀数 当前枚举的 。
因此我们直接用并查集维护即可
代码
点击查看代码
namespace solve{
int height[N],sa[N],oldsa[N],rk[N],oldrk[N],cnt[N],key[N];
namespace SA{
inline bool cmp(int x,int y,int w){
return (oldrk[x]==oldrk[y])&&(oldrk[x+w]==oldrk[y+w]);
}
inline void Init(char *s){
int n=strlen(s+1),m=127,tot;
for_(i,1,n)
rk[i]=s[i],
++cnt[rk[i]];
for_(i,1,m)
cnt[i]+=cnt[i-1];
_for(i,n,1) sa[cnt[rk[i]]--]=i;
for(int w=1;;w<<=1,m=tot) {
tot=0;
_for(i,n,n-w+1)
oldsa[++tot]=i;
for_(i,1,n)
if(sa[i]>w)
oldsa[++tot]=sa[i]-w;
memset(cnt,0,sizeof(cnt));
for_(i,1,n)
++cnt[key[i]=rk[oldsa[i]]];
for_(i,1,m)
cnt[i]+=cnt[i-1];
_for(i,n,1)
sa[cnt[key[i]]--]=oldsa[i];
memcpy(oldrk+1,rk+1,n*sizeof(int));
tot=0;
for_(i,1,n)
rk[sa[i]]=((cmp(sa[i],sa[i-1],w))?(tot):(++tot));
if(tot==n)
break;
}
}
inline void Init_H(char *s){
int n=strlen(s+1),tot=0;
for_(i,1,n){
if(!rk[i]) continue;
if(tot) --tot;
while(s[i+tot]==s[sa[rk[i]-1]+tot]) ++tot;
height[rk[i]]=tot;
}
}
}
using namespace SA;
int f[N],g[N],A[N],B[N];
inline bool cmp1(int a,int b){
return height[a]>height[b];
}
inline int find(int x){
return f[x]=((f[x]!=x)?find(f[x]):x);
}
char s1[N],s2[N],s[N];
inline void In(){
FastI>>(s1+1)>>(s2+1);
int len1=strlen(s1+1);
int len2=strlen(s2+1);
int n=len1+len2+1;
for_(i,1,n){
if(i==len1+1) s[i]='#';
else if(i<=len1) s[i]=s1[i];
else s[i]=s2[i-len1-1];
}
Init(s);Init_H(s);
for_(i,1,n){
g[i]=i+1;f[i]=i;
if(sa[i]>len1+1) B[i]=1;
if(sa[i]<=len1) A[i]=1;
}
sort(g+1,g+1+n,cmp1);
int ans=0;
for_(i,1,n-1){
int x=find(g[i]),y=find(g[i]-1);
ans+=(A[x]*B[y]+A[y]*B[x])*height[g[i]];
A[y]+=A[x];B[y]+=B[x];
f[x]=y;
}
write(ans);
}
}
P4070「SDOI2016」生成魔咒
题意
在一个字符串后加上字符,问加上这个字符后有多少本质不同的字符串
思路
首先看到不同的字符串可以很容易的想到本题需要使用
正着做不好写,所以离线,把插入字符串换成删除字符串,直接求出最后全部加入完情况下的 数组
静态求本质不同的字符串很好求,上面第一道题就是,结果为
这样我们贡献就好维护了,对于删除一个位置为 的字符,我们定义其前一个存在的和后一个存在的后缀字符串为 last 和 nxt
结果很明显是
随便挂个类似 set
的东西来维护就行
代码
警钟长鸣,char
数组直接存巨大数字会溢出
点击查看代码
namespace solve{
int height[N],sa[N],oldsa[N],rk[N],oldrk[N];
ll cnt[N],key[N];
namespace SA{
inline bool cmp(ll x,ll y,ll w){
return (oldrk[x]==oldrk[y])&&(oldrk[x+w]==oldrk[y+w]);
}
inline void Init(int *s,int n){
int m=127000,tot;
for_(i,1,n)
rk[i]=s[i],
++cnt[rk[i]];
for_(i,1,m)
cnt[i]+=cnt[i-1];
_for(i,n,1) sa[cnt[rk[i]]--]=i;
for(ll w=1;;w<<=1,m=tot) {
tot=0;
_for(i,n,n-w+1)
oldsa[++tot]=i;
for_(i,1,n)
if(sa[i]>w)
oldsa[++tot]=sa[i]-w;
memset(cnt,0,sizeof(cnt));
for_(i,1,n)
++cnt[key[i]=rk[oldsa[i]]];
for_(i,1,m)
cnt[i]+=cnt[i-1];
_for(i,n,1)
sa[cnt[key[i]]--]=oldsa[i];
memcpy(oldrk+1,rk+1,n*sizeof(int));
tot=0;
for_(i,1,n)
rk[sa[i]]=((cmp(sa[i],sa[i-1],w))?(tot):(++tot));
if(tot==n)
break;
}
}
inline void Init_H(int *s,int n){
int tot=0;
for_(i,1,n){
if(!rk[i]) continue;
if(tot) --tot;
while(s[i+tot]==s[sa[rk[i]-1]+tot]) ++tot;
height[rk[i]]=tot;
}
}
}
using namespace SA;
ll val[N],a[N],san[N];char s[N];
namespace STree{
class Tree{
public:
int maxm,minm,lazy,l,r;
}T[N];
inline void build(int q,int l,int r){
T[q].l=l;T[q].r=r;
if(l==r) {
T[q].maxm=T[q].minm=val[l];
}
build(lc(q),l,mid(l,r));
build(rc(q),mid(l,r)+1,r);
T[q].maxm=max(T[lc(q)].maxm,T[rc(q)].maxm);
T[q].minm=min(T[lc(q)].minm,T[rc(q)].minm);
}
inline void lazy(int q){
if(T[q].lazy){
T[lc(q)].lazy+=T[q].lazy;
T[rc(q)].lazy+=T[q].lazy;
T[lc(q)].minm+=T[q].lazy;
T[rc(q)].minm+=T[q].lazy;
T[lc(q)].maxm+=T[q].lazy;
T[rc(q)].maxm+=T[q].lazy;
T[q].lazy=0;
}
}
inline void change(int q,int l,int r,int val){
if(T[q].l>r || T[q].r<l) return;
if(T[q].lazy) lazy(q);
if(T[q].l>=l && r<=T[q].r){
T[q].maxm+=val;
T[q].minm+=val;
}
change(lc(q),l,r,val);
change(rc(q),l,r,val);
T[q].maxm=max(T[lc(q)].maxm,T[rc(q)].maxm);
T[q].minm=min(T[lc(q)].minm,T[rc(q)].minm);
}
inline int Askmin(int q,int l,int r){
if(T[q].l>r || T[q].r<l) return inf;
if(T[q].lazy) lazy(q);
if(T[q].l>=l&&r<=T[q].r) return T[q].minm;
return min(Askmin(lc(q),l,r),Askmin(rc(q),l,r));
}
inline int Askmax(int q,int l,int r){
if(T[q].l>r || T[q].r<l) return inf;
if(T[q].lazy) lazy(q);
if(T[q].l>=l&&r<=T[q].r) return T[q].maxm;
return max(Askmax(lc(q),l,r),Askmax(rc(q),l,r));
}
}
//these are useless,TianYi do not know why she wrote it QAQ
int Log[N];
namespace ST{
ll minn[N][25];
inline void build(int n){
for_(i,1,n) {minn[i][0]=height[i];}
for_(j,1,20) {
for_(i,1,n-(1<<j)+1){
minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
}
}
}
inline ll askLCP(ll i,ll j){
ll qwq=Log[j-i];
return min(minn[i+1][qwq],minn[j-(1<<qwq)+1][qwq]);
}
}
using namespace ST;
// the ST table is useful but not SegTree QAQ
stack<int> S;
set<ll> vis;
map<ll,ll> mp;
inline void In(){
int n;read(n);
_for(i,n,1){
read(a[i]);
}
for_(i,1,n)
san[i]=a[i];
sort(san+1,san+n+1);
int SAs=unique(san+1,san+n+1)-san-1;
for_(i,1,n)
a[i]=lower_bound(san+1,san+SAs+1,a[i])-san;
Init(a,n);Init_H(a,n);
Log[1]=0;
for_(i,2,n){
Log[i]=Log[i>>1]+1;
}
build(n);
vis.insert(inf) ;
vis.insert(-inf);
ll ans=0;
_for(i,n,1){
ans+=n-i+1;
ll p=*--vis.upper_bound(rk[i]);
ll q=*vis.lower_bound(rk[i]);
if (p!=-inf && q==inf) {ans-=askLCP(p,rk[i]);}
if (p==-inf && q!=inf) {ans-=askLCP(rk[i],q);}
if (p!=-inf && q!=inf) {ans-=max(askLCP(p,rk[i]),askLCP(rk[i],q));}
write(ans,'\n');
vis.insert(rk[i]);
}
}
}
using namespace solve;
P6793「SNOI2020」字符串
题意
给定两个长度为 的小写字符串 ,求出他们所有长为 的子串,分别组成集合 ,每次可以修改 中一个元素的后缀,费用为后缀的长度,求将 修改成 的最小费用之和。
思路
首先依然是看到后缀,所以考虑使用后缀数组来解决这个问题
还是老样子,先把字符串连起来,然后用特殊字符隔开,然后我们可以很明显的发现对于 和 两个串,如果想要让其对应我们需要的费用为
然后我们贪心的先把 较大的串都对应起来,用并查集维护即可
代码
代码
namespace solve{
int height[N],sa[N],oldsa[N],rk[N],oldrk[N];
ll cnt[N],key[N];
namespace SA{
inline bool cmp(ll x,ll y,ll w){
return (oldrk[x]==oldrk[y])&&(oldrk[x+w]==oldrk[y+w]);
}
inline void Init(char *s){
int n=strlen(s+1),m=128,tot;
for_(i,1,n)
rk[i]=s[i],
++cnt[rk[i]];
for_(i,1,m)
cnt[i]+=cnt[i-1];
_for(i,n,1) sa[cnt[rk[i]]--]=i;
for(ll w=1;;w<<=1,m=tot) {
tot=0;
_for(i,n,n-w+1)
oldsa[++tot]=i;
for_(i,1,n)
if(sa[i]>w)
oldsa[++tot]=sa[i]-w;
memset(cnt,0,sizeof(cnt));
for_(i,1,n)
++cnt[key[i]=rk[oldsa[i]]];
for_(i,1,m)
cnt[i]+=cnt[i-1];
_for(i,n,1)
sa[cnt[key[i]]--]=oldsa[i];
memcpy(oldrk+1,rk+1,n*sizeof(int));
tot=0;
for_(i,1,n)
rk[sa[i]]=((cmp(sa[i],sa[i-1],w))?(tot):(++tot));
if(tot==n)
break;
}
}
inline void Init_H(char *s){
int n=strlen(s+1),tot=0;
for_(i,1,n){
if(!rk[i]) continue;
if(tot) --tot;
while(s[i+tot]==s[sa[rk[i]-1]+tot]) ++tot;
height[rk[i]]=tot;
}
}
}
using namespace SA;
int Log[N];
namespace ST_table{
ll minn[N][20];
inline void build(int n){
for_(i,1,n) {minn[i][0]=height[i];}
for_(j,1,20) {
for_(i,1,n-(1<<j)+1){
minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
}
}
}
inline ll askLCP(ll i,ll j){
ll qwq=Log[j-i];
return min(minn[i+1][qwq],minn[j-(1<<qwq)+1][qwq]);
}
}
//using namespace ST_Table;
int fa[N];
inline int find(int x){
return ((fa[x]==x)?x:fa[x]=find(fa[x]));
}
inline void merge(int x,int y){
fa[x]=y;
}
char s1[N],s2[N],s[N];
vector<int> vec[300005];
int cnt1[N];
inline void In(){
Log[1]=0;
for_(i,1,N-1) Log[i]=Log[i>>1]+1;
for_(i,1,N-1) fa[i]=i;
int n,k;read(n,k);
FastI>>(s1+1)>>(s2+1);
for_(i,1,2*n+1){
if(i==n+1) s[i]='#';
else if(i<=n) s[i]=s1[i];
else s[i]=s2[i-n-1];
}
int len=n*2+1;
Init(s),Init_H(s);
int ans=(n-k+1)*k;
for_(i,1,len){
cnt1[i]=0;
if(sa[i]<=n-k+1) cnt1[i]=1;
else if(sa[i]>n+1 && sa[i]<=len-k+1) cnt1[i]=-1;
if(i>1) vec[height[i]].push_back(i);
}
_for(i,n,0){
int sz=vec[i].size();
for_(j,0,sz-1){
int qwq=vec[i][j];
int x=find(qwq-1),y=find(qwq);
ans-=((cnt1[x]*cnt1[y]>0)?(0):(min(abs(cnt1[x]),abs(cnt1[y]))))*min(i,k);
cnt1[y]=cnt1[x]+cnt1[y];
merge(x,y);
}
}
write(ans,'\n');
}
}
using namespace solve;
CF955D Scissors
思路
首先考虑对于传进来的两个串 和 ,预处理出 的每个前缀在 中出现的最早位置和 每个后缀在 中出现的最晚位置
然后直接扫一遍就行了,复杂度
代码
点击查看代码
namespace solve{
const int N=5e5+5;
int mod=1e9,base=131;
int n,m,k,ans1,ans2,lm[N],rm[N];
char s[N],t[N];
class Hash{
public:
int Hash[N], poww[N];
inline void Init(char *s) {
int len=strlen(s+1);
poww[0]=1;
for_(i,1,len) {
Hash[i]=(Hash[i-1]*base+s[i]-'a'+1) % mod;
poww[i]=poww[i-1]*base%mod;
}
}
inline int get(int l, int r){
return ((Hash[r]-Hash[l-1]*poww[r-l+1])%mod+mod)%mod;
}
}S,T;
inline bool Judge(){
if (n<(k<<1)||m>(k<<1)) return 0;
S.Init(s),T.Init(t);
int pos=k;
for_(i,1,m) lm[i]=n+1;
for_(i,1,min(m,k)){
while(pos<=n&&S.get(pos-i+1,pos)!=T.get(1,i))
pos++;
if(S.get(k-i+1,k)==T.get(1,i))
pos=k;
lm[i]=pos;
}
pos=n-k+1;
for_(i,1,min(m,k)){
while(pos && S.get(pos, pos + i - 1) != T.get(m - i + 1, m)) pos--;
if(S.get(n-k+1,n-k+i)==T.get(m-i+1,m)) pos=n-k+1;
rm[m-i+1]=pos;
}
for_(i,1,n-m+1){
if(S.get(i,i+m-1)==T.get(1,m)){
if(k>=i&&n-k+1<=i+m-1) continue;
ans1=min(max(1ll,i-k+1),n-k+1-k);
ans2=max(k+1,min(n-k+1,i));
return 1;
}
}
for_(i,1,m-1){
if(lm[i]<rm[i+1]&&lm[i]<=n&&rm[i+1]){
ans1=lm[i]-k+1;
ans2=rm[i+1];
return 1;
}
}
return 0;
}
inline void In() {
srand(time(0));
mod+=rand();
base+=rand()%10;
read(n,m,k);
FastI>>(s+1)>>(t+1);
if(Judge())
write("Yes\n",ans1," ",ans2);
else
write("No");
}
}
CF1037H Security
题意
给定一个字符串 和 次询问,每次询问给出一个 和 ,你需要选出一个满足以下条件的字符串 。
-
是 的子串。
-
。
-
是所有满足以下条件内字符串字典序最小的。
输出 的长度,如果不存在 则输出 。
思路
先把 和所有的询问 串都连起来,用小于 的字符连接,连成字符串
枚举 的前缀,判断区间中是否存在一个子串满足以下条件
-
这个子串等于这个前缀加上一个字母
-
加上的字母大于 的前缀的后面一个字符
如果前缀为 本身,那么后面一个字符是极小的。
在 上二分出一个区间,满足区间中的后缀与 的 长度等于正在枚举的 的前缀长度 ,记录上次的二分结果
求这个区间中满足 的最小的 ,用主席树或者扫描线维护,复杂度
代码
点击查看代码
namespace solve{
const int N=4e5+5;
char stp[N];
int s[N*2],n,len;
class SA{
public:
int sa[N*2],height[N*2],cnt[N*2],rk[N*2];
void S_sort(int *s,int l,int m){
int cnt1;
for_(i,1,l) cnt[height[i]=s[i]]++;
for_(i,2,m) cnt[i]+=cnt[i-1];
_for(i,l,1) sa[cnt[height[i]]--]=i;
For_(k,1,l,k){
cnt1=0;
for_(i,l-k+1,l) rk[++cnt1]=i;
for_(i,1,l)
if(sa[i]>k)
rk[++cnt1]=sa[i]-k;
for_(i,1,m) cnt[i]=0;
for_(i,1,l) cnt[height[i]]++;
for_(i,2,m) cnt[i]+=cnt[i-1];
_for(i,l,1) sa[cnt[height[rk[i]]]--]=rk[i],rk[i]=0;
swap(height,rk);
height[sa[1]]=m=1;
for_(i,2,l)
height[sa[i]]=m=m+!(rk[sa[i]]==rk[sa[i-1]]&&rk[sa[i]+k]==rk[sa[i-1]+k]);
if(m==l) return;
}
}
inline void build(int *s,int l,int m){
int k=0;
S_sort(s,l,m);
for_(i,1,l)
rk[sa[i]]=i;
for_(i,1,l){
if(rk[i]==1) k=0;
else{
if(k>0) k--;
int j=sa[rk[i]-1];
while(i+k<=l&&j+k<=l&&s[i+k]==s[j+k])
k++;
}
height[rk[i]]=k;
}
}
}a;
class SegTree{
public:
int t[N*4],d;
inline void build(int n){
for(d=1;d<n;d<<=1);
memset(t,0x7f,sizeof(t));
}
inline int ask(int l,int r){
int mn=2e9;
for(l=l+d-1,r=r+d+1;l^r^1;l>>=1,r>>=1){
if(l&1^1) mn=min(mn,t[l^1]);
if(r&1) mn=min(mn,t[r^1]);
}
return mn;
}
inline void add(int x,int v){
for(int i=x+d;i;i>>=1)
t[i]=v;
}
}T;
pair<int,int> q[N];
namespace ST_Table{
int val[N],l[N],ans[N],ansl[N],Log[N*2],ST[20][N*2];
inline int askmin(int l,int r){
int k=Log[r-l+1];
return min(ST[k][l],ST[k][r-(1<<k)+1]);
}
}
using namespace ST_Table;
namespace Solve{
class node{
public:
int w,l,r,id,len;
};
vector<node> que[N*2];
inline void ask(int l,int r,int val,int slen,int id){
int w,cnt2=a.rk[val];
for(int j=1<<20;j;j>>=1)
((cnt2+j<=len)&&(askmin(a.rk[val]+1,cnt2+j)>=(min(slen,r-l)+1)))&&(
cnt2+=j
);
_for(i,min(slen,r-l),0){
w=cnt2;
for(int j=1<<20;j;j>>=1)
((w+j<=len)&&(askmin(cnt2+1,w+j)>=i))&&(
w+=j
);
que[cnt2+1].push_back(node{w,l,r-i,id,i});
cnt2=w;
}
}
inline void solve(){
T.build(l[0]);
int tp,id;
_for(i,len,1){
(a.sa[i]<=l[0])&&(T.add(a.sa[i],i),0);
int len1=que[i].size();
for_(j,0,len1-1)
(que[i][j].len>ansl[id=que[i][j].id]&&(tp=T.ask(que[i][j].l,que[i][j].r))<=que[i][j].w)&&(
ans[id]=a.sa[tp],ansl[id]=que[i][j].len,
0
);
}
}
}
using namespace Solve;
inline void In(){
memset(ansl,-1,sizeof(ansl));
int m;
FastI>>stp>>m;
l[0]=strlen(stp);
for_(i,0,l[0]-1)
s[++len]=stp[i];
s[++len]='z'+1;
for_(i,1,m){
read(q[i].first,q[i].second);
FastI>>stp;
l[i]=strlen(stp),val[i]=len+1;
for_(j,0,l[i]-1)
s[++len]=stp[j];
s[++len]='a'-1;
}
a.build(s,len,'z'+1);
for_(i,2,len)
ST[0][i]=a.height[i];
for(int j=1;(1<<j)<=len;j++)
for_(i,1,len-(1<<j)+1)
ST[j][i]=min(ST[j-1][i],ST[j-1][i+(1<<j-1)]);
for_(i,2,len)
Log[i]=Log[i>>1]+1;
for_(i,1,m)
ask(q[i].first,q[i].second,val[i],l[i],i);
solve();
for_(i,1,m){
if(ans[i]){
for_(j,ans[i],ans[i]+ansl[i])
write(char(s[j]));
write("\n");
}
else
write("-1\n");
}
}
}
using namespace solve;
P1117「NOI2016」优秀的拆分
题意
求一个字符串所有的子串可以拆成 的本质不同的形式个数,其中 均为任意非空字符串
多组测试
思路
多测不清空,亲人两行泪
之前听说过好几次这道 的后缀数组好题,所以直接考虑如何使用后缀数组来解决这道题
题目要求 的数量,我们拆分问题可以转化为求每个点相邻的 串的数量(以这个点为头或以这个点为尾)
定义 以 为结尾的 串的数量, 为以 开头的 串的数量
这样我们就可以把两个相连的 的合并成一个 的拆分,答案为
可暴力求明显是 的,会超时
我们枚举 代表 形子串的 长度,然后判断 和 ,然后
代码
P5048【 YnOI2019 模拟赛】Yuno loves sqrt technology II
查询区间逆序对,要求时间小于 ,空间
假设我们现在的区间是 ,也就是图中红色的部分
现在我们需要向右移动一格
这一次的移动为我们带来的贡献是 内大于 的数量减去 内大于
用图中的数字来表示就是 内值大于 的数量 减去 内大于 的数量
考虑第一个(也就是 内大于 的数量)是定值,可以直接预处理,但是在后面那个里 是不确定的,我们无法直接预处理,所以这个也就是主要要维护的地方
我们可以首先先对于每个位置都开一个 来维护,把当前询问的编号和 扔到 所在的 里,进行第二次离线
莫队一共会移动端点 次,内存较大
我们发现,从 移动到我们想要移动的 时 不会发生变化,所以我们可以直接把 扔到 内,这样空间就是 的了
然后可以值域分块,单次 修改 查询,但是我们发现我们有 次询问和 次修改
所以我们适当增加修改的复杂度至 ,借此将查询的复杂度降为
总复杂度 ,空间
按照上面的区间逆序对问题来实现即可
特殊的,本题因为是 所以比较卡常,不能#define int long long
不然会 TLE
FastIO
都救不回来那种
点击查看代码
#include<bits/stdc++.h>
#include <sys/mman.h>
#define N 100010
#define firein(a) freopen(a".in","r",stdin)
#define fireout(a) freopen(a".out","w",stdout);
#define fire(a) firein(a),fireout(a)
#define for_(a,b,c) for(int a=b;a<=c;a++)
#define _for(a,b,c) for(int a=b;a>=c;a--)
#define For_(a,b,c,d) for(int a=b;a<=c;a+=d)
#define _For(a,b,c,d) for(int a=b;a>=c;a-=d)
using namespace std;
namespace Solve{
const char *I=(char*)mmap(0,1<<22,1,2,0,0);
inline int read() {
int x=0,f=0;
while(*I<48)f|=*I++==45;
while(*I>47)x=x*10+(*I++&15);
return f?-x:x;
}
char O[1<<22],*o=O;
void print(long long x) {
if(x<0)*o++=45,x=-x;
if(x>9)print(x/10);
*o++=48+x%10;
}
struct Query{
int l,r,v,id;
}q[N];
vector<Query> L[N],R[N];
int n,m,blo,a[N],b[N],c[N],d[N],mp[N],tot,pos[N],bL[N],bR[N];
long long sum1[N],sum2[N],ans[N],Ans[N];
inline bool cmp(const Query &a,const Query &b){
if((a.l-1)/blo!=(b.l-1)/blo) return (a.l-1)/blo<(b.l-1)/blo;
return a.r<b.r;
}
namespace BIT{
#define lowbit(x) ((x)&(-x))
inline void Add(int x,int v){
For_(i,x,n,lowbit(i))
b[i]+=v;
}
inline int Ask(int x){
int ans=0;
_For(i,x,1,lowbit(i)){
ans+=b[i];
}
return ans;
}
}
using namespace BIT;
inline void Solve(){
sort(q+1,q+m+1,cmp);
q[0].l=1;
for_(i,1,m){
ans[i]=sum1[q[i].r]-sum1[q[i-1].r]+sum2[q[i].l]-sum2[q[i-1].l];
if(q[i-1].r<q[i].r) {
L[q[i-1].l-1].push_back({q[i-1].r+1,q[i].r,-1,i});
}
if(q[i].r<q[i-1].r) {
L[q[i-1].l-1].push_back({q[i].r+1,q[i-1].r,1,i});
}
if(q[i].l<q[i-1].l) {
R[q[i].r+1].push_back({q[i].l,q[i-1].l-1,-1,i});
}
if(q[i-1].l<q[i].l) {
R[q[i].r+1].push_back({q[i-1].l,q[i].l-1,1,i});
}
}
}
void In(){
n=read(),m=read();
blo=sqrt(n)+1;
for_(i,1,n) {
a[i]=read();
mp[i]=a[i];
}
sort(mp+1,mp+n+1);
tot=unique(mp+1,mp+n+1)-mp-1;
for_(i,1,n)
a[i]=lower_bound(mp+1,mp+tot+1,a[i])-mp;
for_(i,1,n){
sum1[i]=sum1[i-1]+i-1-Ask(a[i]);
Add(a[i],1);
}
memset(b,0,sizeof(b));
_for(i,n,1){
sum2[i]=sum2[i+1]+Ask(a[i]-1);
Add(a[i],1);
}
for_(i,1,m){
q[i].l=read();
q[i].r=read();
q[i].id=i;
}
Solve();
for_(i,1,1e5){
pos[i]=(i-1)/blo+1;
if(pos[i]!=pos[i-1]) {
bL[pos[i]]=i;
bR[pos[i-1]]=i-1;
}
}
bR[pos[(int)1e5]]=1e5;
int sum,l,r,v,id;
for_(i,1,n){
for_(j,1,pos[a[i]]-1)
c[j]++;
for_(j,bL[pos[a[i]]],a[i])
d[j]++;
int Size=L[i].size();
for_(j,0,Size-1){
l=L[i][j].l;
r=L[i][j].r;
v=L[i][j].v;
id=L[i][j].id;
sum=0;
for_(k,l,r)
sum+=c[pos[a[k]+1]]+d[a[k]+1];
ans[id]+=v*sum;
}
}
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
_for(i,n,1){
for_(j,pos[a[i]]+1,blo)
c[j]++;
for_(j,a[i],bR[pos[a[i]]])
d[j]++;
int Size=R[i].size();
for_(j,0,Size-1){
l=R[i][j].l;
r=R[i][j].r;
v=R[i][j].v;
id=R[i][j].id;
sum=0;
for_(k,l,r)
sum+=c[pos[a[k]-1]]+d[a[k]-1];
ans[id]+=v*sum;
}
}
for_(i,1,m){
ans[i]+=ans[i-1];
Ans[q[i].id]=ans[i];
}
for_(i,1,m)
print(Ans[i]),*o++='\n';
fwrite(O,1,o-O,stdout);
}
}
using namespace Solve;
signed main(){
#ifndef ONLINE_JUDGE
fire("data");
#endif
In();
}
P4887 第十四分块(前体)
点缀光辉的第十四分块(前体),分块不可做,二次离线莫队
首先分析题面,考虑对于每次进行移动,贡献是 内和 异或和中 的个数 减去 内和 异或和中 的个数
前面依然是预处理,后面的直接 vector
存进去,和上一道题一样
然后我们发现,后面的值域分块维护,解决
数组开 ,交上去,诶 了,调了半天发现只要改到 就能过
不过这道题似乎不是很卡常,没用 Fread
和 Fwrite
也过了,直接关同步流 cin/cout
点击查看代码
#define ONLINE_JUDGE
#include<bits/stdc++.h>
#include<sys/mman.h>
#include<fcntl.h>
#define N 200010
#define firein(a) freopen(a".in","r",stdin)
#define fireout(a) freopen(a".out","w",stdout);
#define fire(a) firein(a),fireout(a)
#define for_(a,b,c) for(int a=b;a<=c;a++)
#define _for(a,b,c) for(int a=b;a>=c;a--)
#define For_(a,b,c,d) for(int a=b;a<=c;a+=d)
#define _For(a,b,c,d) for(int a=b;a>=c;a-=d)
#define lowbit(x) ((x)&(-x))
#define int long long
typedef long long ll;
using namespace std;
namespace Solve{
// #ifndef ONLINE_JUDGE
// int _if=open("data.in",O_RDONLY);
// FILE* _of=fopen("data.out","w");
// #else
// int _if=fileno(stdin);
// FILE* _of=stdout;
// #endif
// const char *_I=(char*)mmap(0,1<<24,1,2,_if,0);
// inline ll read(){
// int x=0;
// while(*_I<48)++_I;
// while(*_I>47)x=x*10+(*_I++&15);
// return x;
// }
// char O[1<<24],*o=O;
// void print(ll x){
// if (x>9)print(x/10);
// *o++=x%10+48;
// }
int a[N],bl[N],st[N],blo,top,n,m,k;
ll s1[N],s2[N],s[N],ret[N],ans[N],res;
struct node{
int l,r,id;
inline node(){}
inline node(int L,int R,int Id):l(L),r(R),id(Id){}
inline bool operator <(const node &b)const{
return bl[l]==bl[b.l]?r<b.r:l<b.l;
}
}q[N];
inline void Init(){
int Cnt,X;
for(int i=0;i<16384;++i){
Cnt=0,X=i;
for(;X;X^=lowbit(X))
++Cnt;
if(Cnt==k)
st[++top]=i;
}
}
vector<node>Q[N];
inline void Solve1(){
int l=q[1].r+1,r=q[1].r;
for_(i,1,m){
if(l<q[i].l){
Q[r].push_back(node(l,q[i].l-1,q[i].id<<1));
}
if(l>q[i].l){
Q[r].push_back(node(q[i].l,l-1,q[i].id<<1));
}
l=q[i].l;
if(r<q[i].r){
Q[l-1].push_back(node(r+1,q[i].r,q[i].id<<1|1));
}
if(r>q[i].r){
Q[l-1].push_back(node(q[i].r+1,r,q[i].id<<1|1));
}
r=q[i].r;
}
}
inline void Solve2(){
int l=q[1].r+1,r=q[1].r;
for_(i,1,m){
if(l<q[i].l)
res-=ret[q[i].id<<1]-s2[q[i].l-1]+s2[l-1];
if(l>q[i].l)
res+=ret[q[i].id<<1]-s2[l-1]+s2[q[i].l-1];
l=q[i].l;
if(r<q[i].r)
res+=s1[q[i].r]-s1[r]-ret[q[i].id<<1|1];
if(r>q[i].r)
res-=s1[r]-s1[q[i].r]-ret[q[i].id<<1|1];
r=q[i].r;
ans[q[i].id]=res;
}
}
inline void In(){
std::ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cin>>n>>m>>k;
blo=800;
Init();
for_(i,1,n){
cin>>a[i];
bl[i]=(i-1)/blo+1;
}
for_(i,1,m){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
sort(q+1,q+1+m);
Solve1();
for_(i,1,n){
s1[i]=s1[i-1]+s[a[i]];
for_(k,1,top)
++s[a[i]^st[k]];
s2[i]=s2[i-1]+s[a[i]];
for(vector<node>::iterator it=Q[i].begin();it!=Q[i].end();++it)
for_(k,it->l,it->r)
ret[it->id]+=s[a[k]];
}
Solve2();
for_(i,1,m)
cout<<ans[i]<<endl;
}
}
using namespace Solve;
signed main(){
// fire("data");
In();
}