n × m ≤ 5 × 1 0 5 , 1 ≤ a i , j ≤ 1 0 9 n\times m\leq 5\times 10^5,1\leq a_{i,j}\leq 10^9 n×m≤5×105,1≤ai,j≤109
固定左右端点 [ l , r ] [l,r] [l,r]和字符串 S S S,设所有构成字符串为 S S S的左右端点为 [ l , r ] [l,r] [l,r]的行集合 P ∣ S ∣ , [ l , r ] = { a 1 , a 2 , . . . , a ∣ P ∣ } P_{|S|,[l,r]}=\{a_1,a_2,...,a_{|P|}\} P∣S∣,[l,r]={a1,a2,...,a∣P∣}。
统计每个子矩阵价值时,不妨从上到下遍历每行,在某行所构成的字符串第一次出现时将价值+1。
反过来对于 P ∣ S ∣ , [ l , r ] P_{|S|,[l,r]} P∣S∣,[l,r]中的每个元素,对答案的贡献即为 ( a i − a i − 1 ) × ( n − a i + 1 ) (a_{i}-a_{i-1})\times(n-a_i+1) (ai−ai−1)×(n−ai+1)(设 a 0 = 0 a_0=0 a0=0)。
暴力枚举 [ l , r ] , ∣ S ∣ [l,r],|S| [l,r],∣S∣显然不可取。考虑固定左端点。
首先设 l = 1 l=1 l=1,建立横向的 trie \text{trie} trie树。 trie \text{trie} trie树上的每个点都包含着一个 P P P集合,可以用 s e t set set维护。
每次 l l l右移一位,相当于启发式合并 t r i e trie trie树上的一些结点。
复杂度 O ( n m log 2 ( n m ) ) O(nm\log^2(nm)) O(nmlog2(nm))
#include
#define rv(r,c) ((r-1)*m+c)
#define ip map::iterator
#define it set::iterator
#define fi first
#define sc second
using namespace std;
const int N=5e5+10;
typedef long long ll;
int n,m,a[N],bs,cnt,son[N],cot;
ll ans,nw,vl[N];
map<int,int>ch[N];
set<int>st[N];
char cp;
inline void rd(int &x)
{
cp=getchar();x=0;
for(;!isdigit(cp);cp=getchar());
for(;isdigit(cp);cp=getchar()) x=x*10+(cp^48);
}
inline void ins(int rw,int pos)
{
int i,j,u=0;it d;
for(i=1;i<=m;++i){
j=a[pos+i];
if(!ch[u][j]){ch[u][j]=++cnt;st[cnt].insert(0);}
u=ch[u][j];d=--st[u].lower_bound(rw);vl[u]+=(ll)(rw-(*d))*(n-rw+1);
st[u].insert(rw);
}
}
inline int merge(int x,int y)
{
if((!x)||(!y)) return x+y;
if(st[x].size()>st[y].size()) swap(x,y);
nw-=(vl[x]+vl[y]);int i,j;it d,e;ip c;
for(d=++st[x].begin();d!=st[x].end();++d){
e=--st[y].lower_bound(*d);i=*e;j=*d;
vl[y]+=(ll)(j-i)*(n-j+1);
if((++e)!=st[y].end()) vl[y]+=(ll)(i-j)*(n-(*e)+1);
st[y].insert(j);
}
nw+=vl[y];
for(c=ch[x].begin();c!=ch[x].end();++c) ch[y][c->fi]=merge(ch[y][c->fi],c->sc);
return y;
}
inline void sol()
{
ip c;int x;
for(c=ch[0].begin();c!=ch[0].end();++c) son[++cot]=c->sc;
ch[0].clear();
for(;cot;--cot){
x=son[cot];nw-=vl[x];
for(c=ch[x].begin();c!=ch[x].end();++c) ch[0][c->fi]=merge(ch[0][c->fi],c->sc);
}
}
int main(){
int i,j;
rd(n);rd(m);bs=n*m;
for(i=1;i<=bs;++i) rd(a[i]);
for(j=1,i=1;i<=bs;i+=m,++j) ins(j,i-1);
for(i=1;i<=cnt;++i) nw+=vl[i];ans=nw;
for(i=2;i<=m;++i) {sol();ans+=nw;}
printf("%lld",ans);
return 0;
}
n ≤ 1 0 5 , q ≤ 5 × 1 0 5 , k ≤ 3 , a i < 2 30 n\leq 10^5,q\leq 5\times10^5,k\leq 3,a_i<2^{30} n≤105,q≤5×105,k≤3,ai<230。
k ≤ 3 k\leq 3 k≤3并没有什么用(虽然有暴力分)。
考虑每个数向右并的过程中最多变换 log a i \log_{a_i} logai次。
直接离线线段树维护即可。
时间复杂度 O ( n log n log a i + q log n ) O(n\log n\log a_i+q\log n) O(nlognlogai+qlogn)
#include
#define mid (l+r>>1)
#define lc k<<1
#define rc k<<1|1
using namespace std;
const int N=1e5+10,MX=(1<<30)-1;
typedef long long ll;
int n,m,K,a[N],nxt[N][30];
int ad[N<<2];ll ss[N<<2],ans[N*5];
struct Q{int l,r,id;bool operator<(const Q&ky)const{return l>ky.l;}}q[N*5];
struct P{int w,p;bool operator<(const P&ky)const{return p<ky.p;}}ex[30];
char cp;
inline void rd(int &x)
{
cp=getchar();x=0;
for(;!isdigit(cp);cp=getchar());
for(;isdigit(cp);cp=getchar()) x=x*10+(cp^48);
}
void prit(ll x){if(x>9) prit(x/10);putchar('0'+x%10);}
void ins(int k,int l,int r,int L,int R)
{
ss[k]+=(R-L+1);
if(l==L && r==R) {ad[k]++;return;}
if(L<=mid) ins(lc,l,mid,L,min(R,mid));
if(R>mid) ins(rc,mid+1,r,max(L,mid+1),R);
}
inline void ext(int pos)
{
int i,j,vl=MX,pr=pos;
for(i=0;i<30;++i) ex[i]=(P){i,nxt[pos][i]};
sort(ex,ex+30);
for(i=j=0;i<30;i=j){
if(ex[i].p>pos && vl%K==0) ins(1,1,n,pr,ex[i].p-1);
for(;j<30 && ex[j].p==ex[i].p;++j) vl^=(1<<ex[j].w);pr=ex[i].p;
}
if(pr<=n) ins(1,1,n,pr,n);
}
ll ask(int k,int l,int r,int R)
{
if(R==r) return ss[k];
ll re=ask(lc,l,mid,min(R,mid));
if(R>mid) re+=ask(rc,mid+1,r,R);
return re+(ll)ad[k]*(R-l+1);
}
int main(){
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
int i,j;
rd(n);rd(m);rd(K);
for(i=1;i<=n;++i) rd(a[i]);
for(i=0;i<30;++i) nxt[n+1][i]=n+1;
for(i=n;i;--i)
for(j=0;j<30;++j)
nxt[i][j]=((a[i]>>j)&1)?nxt[i+1][j]:i;
for(i=1;i<=m;++i) rd(q[i].l),rd(q[i].r),q[i].id=i;
sort(q+1,q+m+1);
for(i=n,j=1;i && j<=m;--i){
for(ext(i);j<=m && q[j].l==i;++j)
ans[q[j].id]=ask(1,1,n,q[j].r);
}
for(i=1;i<=m;++i) prit(ans[i]),putchar('\n');
return 0;
}
CF960G
题解
只能拿 99 p t s 99pts 99pts。因为存在一个 O ( n log n ) O(n\log n) O(nlogn)倍增求解第一类斯特林数的方法(之后会学)。
小结
我真傻,真的,明明T2是个一眼题,非要去肝T1,还没有肝出来。
我真傻,真的,明明T2离线下来只需要裸的线段树,写了个主席树又 M L E MLE MLE又 W A WA WA。
T3做过不说了。
不过T1匹配转 t r i e trie trie树真的挺巧妙的。