并不对劲的复健训练-bzoj5253:loj2479:p4384:[2018多省联考]制胡窜

题目大意

给出一个字符串\(S\),长度为\(n\)(\(n\leq 10^5\)),\(S[l:r]\)表示\(S_l,S_{l+1}...,S_r\)这个子串。有\(m\)(\(m\leq 3\times 10^5\))次询问,每次询问给出\(l,r\),问有多少对\((i,j)\)(\(1\leq i<i+1<j\leq n\)),使与\(S[l:r]\)本质相同的子串出现在\(S[1:i]\)中或\(S[i+1:j-1]\)中或\(S[j:n]\)中。

题解

询问相当于是问有多少种方案在\(S\)中找两个缝隙切两刀,使两刀不重合,且存在一个与\(S[l:r]\)本质相同的子串没被切。
这个比较难考虑,可以把它转换成 在\(n-1\)个缝隙中选两个的总方案数-所有与\(S[l:r]\)本质相同的子串都被切的方案数。
所有与\(S[l:r]\)本质相同的子串都被切的方案数分以下几种情况考虑:
一、\(S\)中存在三个与\(S[l:r]\)本质相同的子串,且其中每两个的交集大小不超过1
会发现此时怎么切两刀都切不全这三个串,方案数为0。
二、\(S\)中不存在三个与\(S[l:r]\)本质相同的子串,且其中每两个的交集大小不超过1:假设所有这样的子串的最左的左端点为\(ll\),最左的右端点为\(lr\),最右的左端点为\(rl\),最右的右端点为\(rr\),\(S[l:r]\)长度为\(len\),\(r_i\)表示right集合的第\(i\)个。
\(\space\space\space\space\) 1.\(S\)中所有与\(S[l:r]\)本质相同的子串的交集不为空
\(\space\space\space\space\) \(\space\space\space\space\)(1)靠前的一刀没切到与\(S[l:r]\)本质相同的子串:另一刀必须切在所有这样的子串的交集\([rl,lr]\)上。这部分答案=\(ll\times (lr-rl)\)。注意刀只能切在缝隙上,所以是\((rl-lr)\)而不是\((rl-lr+1)\)。
\(\space\space\space\space\) \(\space\space\space\space\)(2)靠前的一刀切到了一些与\(S[l:r]\)本质相同的子串,但没切在所有与\(S[l:r]\)本质相同的子串的交集上:会发现第二刀肯定要切在第一刀没切到的子串的交集上。所以对于两种方案,当它们第一刀切的串相同时,第二刀可行的取值范围相同。两种方案的第一刀都在\([r_i-len+1,r_{i+1}-len+1)\)时,它们切的串都是\([1,i]\)这个区间中的,它们第二刀的取值范围都是\([rl,r_{i+1}]\)。也就是说,这部分答案=\(\sum(r_{i+1}-r_i)\times(r_{i+1}-rl)\)
\(\space\space\space\space\) \(\space\space\space\space\)(3)靠前的一刀切到所有与\(S[l:r]\)本质相同的子串的交集上:其实这部分与(1)合并后就是“存在一刀切在所有与\(S[l:r]\)本质相同的子串的交集上,但靠前的一刀不是(2)的情况”。两刀都切在交集上的方案数是\(C_{lr-rl}^2\)。对于只有一刀切在交集上的情况,相当于其中一刀不能在\([lr-len+1,lr]\)上,另一刀必须在\([lr,rl]\)上,所以这部分是 \((n-len)\times(lr-rl)\)。也就是说(1)和(3)总共是\(C_{lr-rl}^2+(n-len)\times(lr-rl)\)。
\(\space\space\space\space\) 2. \(S\)中所有与\(S[l:r]\)本质相同的子串的交集为空:两刀必须都切到与\(S[l:r]\)本质相同的子串,且都切不到这些子串的交集,和上一种情况的(2)类似。但是第一刀可能切不到最左的串,\([rl,r_{i+1}]\)也可能是空集。发现当\(r_i-len+1<lr\)且\(r_{i+1}>rl\)时,第一刀切在\([r_i,r_{i+1})\)都是合法的。这种情况的答案是\(\sum\limits_{r_{i+1}>rl}^{r_i-len+1<lr}(r_{i+1}-r_i)\times(r_{i+1}-rl)\)。还可能有一段\([r_i,r_{i+1})\),满足\(lr\in[r_i-len+1,r_{i+1}-len+1)\)即\((lr+len-1)\in[r_i,r_{i+1})\),这一段中比较靠前的地方\([r_i-len+1,lr]\)可以切,靠后的不可以切。计算这种情况需要求\(lr+len-1\)左边第一个right集合的元素\(r_p\)和右边第一个\(r_s\)。这部分的答案是\((lr-r_p+len-1)\times (r_s-rl)\),需要注意的是,当\(r_s\leq rl\)时这部分的答案是0。

需要维护right集合和\(\sum(r_{i+1}-r_i)\times(r_{i+1}-rl)\),可以拆成\(s_1(l,r)=\sum(r_{i+1}-r_i)\)和\(s_2(l,r)=\sum(r_{i+1}-r_i)\times r_{i+1}\),这样就可以线段树维护区间right集合中最左和最右,合并时\(s1(l,r)=s1(l,mid)+s1(mid+1,r)+min(mid+1,r)-max(l,mid)\),\(s2(l,r)=s2(l,mid)+s2(mid+1,r)+(min(mid+1,r)-max(l,mid))\times min(mid+1,r)\)。
求right集合需要线段树合并。据说不离线的话,“可持久化 线段树合并”会MLE。

代码
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define rep(i,x,y) for(register int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
#define view(u,k) for(int k=fir[u];~k;k=nxt[k])
#define LL long long
#define maxn 100007
#define maxnd 200007
#define maxq 300007
#define maxk 6400007
#define ls son[u][0]
#define rs son[u][1]
#define mi (l+r>>1)
#define Ls(u) son[u][0]
#define Rs(u) son[u][1]
#define pb push_back
#define int long long
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)&&ch!='-')ch=getchar();
    if(ch=='-')f=-1,ch=getchar();
    while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    return x*f;
}
void write(LL x)
{
    if(x==0){putchar('0'),putchar('\n');return;}
    int f=0;char ch[20];
    if(x<0)putchar('-'),x=-x;
    while(x)ch[++f]=x%10+'0',x/=10;
    while(f)putchar(ch[f--]);
    putchar('\n');
    return;
}
int n,ch[maxnd][10],dis[maxnd],rt[maxnd],fa[maxnd],pt[maxn],lst,cnt,rot;
int son[maxk][2],cntnd,m,c[maxn],ord[maxnd],anc[maxnd][20];
char s[maxn];
LL ans[maxq];
struct node{int mx,mn;LL sum[2];}tr[maxk];
struct quest{int l,r,id;};
vector<quest>qt[maxnd];
void gt(node & x,node y){x.mx=y.mx,x.mn=y.mn,x.sum[0]=y.sum[0],x.sum[1]=y.sum[1];return;}
void pu(node & x,node ld,node rd)
{
    x.mx=rd.mx,x.mn=ld.mn,x.sum[0]=ld.sum[0]+rd.sum[0]+(LL)(rd.mn-ld.mx),x.sum[1]=ld.sum[1]+rd.sum[1]+(LL)(rd.mn-ld.mx)*rd.mn;
    return;
}
int build(int u,int l,int r,int x)
{
    if(!u)u=++cntnd;
    if(x<=l&&r<=x){tr[u].mx=tr[u].mn=x,tr[u].sum[0]=tr[u].sum[1]=0;return u;}
    if(x<=mi)ls=build(ls,l,mi,x);
    else rs=build(rs,mi+1,r,x);
    if(!ls||!rs)gt(tr[u],tr[ls|rs]);
    else pu(tr[u],tr[ls],tr[rs]);return u;
}
int merge(int ua,int ub,int l,int r)
{
    if(!ua||!ub)return ua|ub;
    Ls(ua)=merge(Ls(ua),Ls(ub),l,mi);
    Rs(ua)=merge(Rs(ua),Rs(ub),mi+1,r);
    if(!Ls(ua)||!Rs(ua))gt(tr[ua],tr[Ls(ua)|Rs(ua)]);
    else pu(tr[ua],tr[Ls(ua)],tr[Rs(ua)]);return ua;
}
void ask(int u,int l,int r,int x,int y,node & k)
{
    if(!u||x>y)return;
    if(x<=l&&r<=y){if(k.mx==0)gt(k,tr[u]);else pu(k,k,tr[u]);return;}
    if(x<=mi&&ls)ask(ls,l,mi,x,y,k);
    if(y>mi&&rs)ask(rs,mi+1,r,x,y,k);
    return;
}
int pref(int u,int x){node kk;kk.mx=kk.mn=0,ask(u,1,n,1,x,kk);return kk.mx;}
int sufx(int u,int x){node kk;kk.mx=kk.mn=0,ask(u,1,n,x,n,kk);return kk.mn;}
int gx(char c){return c-'0';}
void ext(int i)
{
    int p=lst,v=gx(s[i]),np=++cnt;dis[np]=i,lst=pt[i]=np;rt[np]=build(rt[np],1,n,i);
    for(;p&&!ch[p][v];p=fa[p])ch[p][v]=np;
    if(!p)fa[np]=rot;
    else
    {
        int q=ch[p][v];
        if(dis[p]+1==dis[q])fa[np]=q;
        else
        {
            int nq=++cnt;
            dis[nq]=dis[p]+1;memcpy(ch[nq],ch[q],sizeof(ch[q]));
            fa[nq]=fa[q],fa[q]=fa[np]=nq;
            for(;p&&ch[p][v]==q;p=fa[p])ch[p][v]=nq;
        }
    }
}
int tp[maxq];
LL c2(int x){return (LL)x*(x-1)/2ll;}
signed main()
{
    rot=lst=cnt=1;
    scanf("%d%d%s",&n,&m,s+1);
    rep(i,1,n)ext(i);
    rep(i,1,cnt)c[dis[i]]++;
    rep(i,1,n)c[i]+=c[i-1];
    rep(i,1,cnt)ord[c[dis[i]]--]=i;
    rep(j,1,cnt)
    {
        int i=ord[j];
        anc[i][0]=fa[i];
        rep(k,1,19)anc[i][k]=anc[anc[i][k-1]][k-1];
    }
    rep(i,1,m)
    {
        int l=read(),r=read(),nd=pt[r];
        dwn(j,19,0)if(dis[anc[nd][j]]>=r-l+1)nd=anc[nd][j];
        quest q;q.l=l,q.r=r,q.id=i;qt[nd].pb(q);
    }
    dwn(j,cnt,1)
    {
        int i=ord[j],sz=qt[i].size(),rr=tr[rt[i]].mx,lr=tr[rt[i]].mn;
        rep(k,0,sz-1)
        {
            int len=qt[i][k].r-qt[i][k].l+1,rl=rr-len+1,nd3=0;
            if(lr+len-1<=n)nd3=sufx(rt[i],lr+len-1);
            if(nd3&&nd3<=rl){tp[qt[i][k].id]=3;continue;}
            if(lr>rl){tp[qt[i][k].id]=1;ans[qt[i][k].id]=-tr[rt[i]].sum[0]*((LL)rl)+tr[rt[i]].sum[1]+c2(lr-rl)+(LL)(lr-rl)*(n-len);}
            else
            {
                int sec,fst=pref(rt[i],rl);
                node kk;kk.mx=kk.mn=0;if(fst)ask(rt[i],1,n,fst,lr+len-1,kk);
                if(kk.mx)ans[qt[i][k].id]=-(LL)rl*kk.sum[0]+kk.sum[1];
                fst=sec=0;
                if(lr+len-1<=n)fst=pref(rt[i],lr+len-1),sec=sufx(rt[i],lr+len-1);
                if(sec&&fst&&sec>rl)ans[qt[i][k].id]+=(LL)(lr-fst+len-1)*(sec-rl);
                tp[qt[i][k].id]=2;
            }
        }
        rt[fa[i]]=merge(rt[fa[i]],rt[i],1,n);
    }
    rep(i,1,m){write(c2(n-1)-ans[i]);}
    return 0;
}
一些感想

写一下调一年,自闭不花一分钱

原文地址:https://www.cnblogs.com/xzyf/p/11559743.html

时间: 2024-10-18 20:01:15

并不对劲的复健训练-bzoj5253:loj2479:p4384:[2018多省联考]制胡窜的相关文章

并不对劲的复健训练-p3674

题目大意 给出序列$ a_1,...,a_n $ ( $ n\leq10^5,a\leq 10^5 $ ),有\(m\) ( \(m\leq 10^5\))个以下三类询问: (1)给出\(l,r,k\)(\(k\leq 10^5\)),问是否存在\(x,y\)使\(x\in[l,r],y\in[l,r],a_x-a_y=k\) (2)给出\(l,r,k\)(\(k\leq 10^5\)),问是否存在\(x,y\)使\(x\in[l,r],y\in[l,r],a_x+a_y=k\) (3)给出\(

并不对劲的复健训练-CF1187D

题目大意 有两个长度为\(n\)的序列\(a_1,...,a_n\),\(b_1,...,b_n\)($a,b\leq n\leq 3\times 10^5 \().一次操作是选取\)[l,r]\(,将\)a_l,...,a_r$排序.问能否通过若干次操作把 \(a_1,...,a_n\) 变得和 \(b_1,...,b_n\) 一样. 题解 首先,如果\(a,b\)中每个数的出现次数不一样,那么一定不能. 其余的部分的问题在于能不能通过交换\(a\)中一些数的位置使\(a\)变得和\(b\)一

[全是废话的日记]离梦醒还有82天,中老年退役选手复健之路

帮兴dalao劝小学妹,顺便收获了一波嘲讽,比如一个人闷头做题 我是真的不适合这种劝人的活啊,我只会开嘲讽怼回去 然后发现辣鸡兴大佬,天天就知道骂人家妹子,骂完了,妹子玻璃心,又开始疯狂找人劝,你当初别骂那么狠啊! 我现在心态爆炸了,我也想有学长劝. 好了,吐槽时间结束,开始伟大的复健之路,又称咸鱼不忍心白白死去的垂死挣扎. (不要相信这句话,我真的全篇都在吐槽一点干货都没留) 你们能相信我打一个动态规划,从19号22点,打到现在20号快1点还没打完吗. 真是复健之路艰辛啊,明天(实际上应该是今

复健计划

这里就是复健计划啦!虽然实际上我只是把以前的归档复制了一遍而已啦,当然我加了一些基础的东西在里面就是了. 计划打算在七月初开始(只要不咕). 感觉是在水博客啊……没事,应该没人看(滑稽) 学完的后面打个√. 图论 建图方法 最短路 生成树 拓扑排序 Tarjan相关 2-SAT 欧拉回路 虚树 圆方树 网络流/匈牙利算法 KM 最大流最小割 费用流 二分图匹配 上下界网络流 —————————————————————— 数论 筛法 gcd/exgcd 逆元 康托展开,卡特兰数,斯特林数 卢卡斯定

【读书笔记/复健向】算法竞赛入门经典训练指南1.1贪心部分

例题一(UVa11292) 基础贪心,没什么需要多讲的,直接放上代码.有一点要注意的是我第一遍写的时候竟然犯了两个错误. 错误点 将dragon.knight与i.j对应错误了,仔细一想人有先后对应的天性,下次设置i.j时还是老老实实根据输入顺序来,避免出错 第23行遗漏了(j<n)这个条件,使得在龙已经全被砍头的情况下却雇佣了剩余的骑士. 本题重点 砍龙头的时候设置两个指针,分别移动,使用频率挺高的一个小技巧,不难,但是挺重要的. 1 #include<iostream> 2 #inc

下你现场v那下次v成功的复健科

 http://www.djkk.com/blog/xzqjtw05 http://www.djkk.com/blog/mysong-4439957.html http://www.djkk.com/blog/mydiarry-4439957.html http://www.djkk.com/blog/myfav-4439957.html http://www.djkk.com/blog/zjmore-4439957.html http://www.djkk.com/blog/mypic-4

【读书笔记/解题报告/复健向】动态规划

<挑战程序设计竞赛>2.3.1(POJ3624/NOIP2004采药问题) 最基础的01背包问题,标程性质,又二维和一维两种写法. 1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<cmath> 5 using namespace std; 6 const int MAXN=3403; 7 int w[MAXN]; 8 int v[MAXN]; 9 int

复健小CM

系统 : Windows xp 程序 : Keygenme # 2 程序下载地址 :http://pan.baidu.com/s/1qYIk2HQ 要求 : 注册机编写 使用工具 : OD 可在“PEDIY CrackMe 2007”中查找关于此程序的讨论,标题为“一个据说是新手级Crackme的分析”. 运行程序,查找字符串定位关键算法位置.大致的看一下程序主体: 004014AF |. C74424 04 CE0>mov dword ptr [esp+4], 004400CE ; enter

噢耶肉体上的分就该的加萨克的恢复四复健科

http://f.dangdang.com/group/24297/953021/ http://f.dangdang.com/group/24297/953022/ http://f.dangdang.com/group/24297/953023/ http://f.dangdang.com/group/24297/953024/ http://f.dangdang.com/group/24297/953025/ http://f.dangdang.com/group/24297/953026