并不对劲的马后炮1217
exploit
题目大意
有一棵\(n\)(\(n\leq152501\))个点的树,时间从\(0\)开始,每过一秒,每个点\(i\)的点权就会增加\(v_i\),每个点\(i\)有点权上限\(L_i\),也就是说,没有询问时第\(T\)秒初点\(i\)的点权是\(min(v_i*T,L_i)\)
这棵树有边权,且边权为正
有\(q\)(\(q\leq152501\))个询问,第\(i\)个询问有三个数\(t_i,x_i,l_i\),表示询问在\(t_i\)秒初,点\(x_i\)和它的子树中与点\(x_i\)距离不超过\(l_i\)的点的点权之和,同时清空对答案有贡献的点的点权,但第\(t_i\)秒末这些点的点权还是会增加
题解
设\(dfn_i\)表示点\(i\)在\(dfs\)序中的位置,\(siz_i\)表示点\(i\)和它的子树中一共有多少个点,\(dep_i\)表示点\(i\)到根的距离
那么将所有\((dfn_i,dep_i)\)看成二维平面中的点,第\(j\)个询问涉及的点就是横坐标在\(dfn_{x_j}\)到\(dfn_{x_j}+siz_{x_j}-1\),纵坐标在\(dep_{x_j}\)到\(dep_{x_j}+l_j\),这个矩形中的点
将\(n\)个点建成KD树,就能找出每次询问涉及的点
在KD树中查找时,有两类点会被涉及:(1)这个点KD树中的子树不完全被询问矩形包含,但这个点在询问矩形里;(2)这个点的整个子树在询问矩形里
对每个KD树上的点\(i\)记\(a_i,b_i\),分别表示这个点上次被涉及的时间和这个点的子树上次被整个涉及的时间
第(1)类点会对答案产生\(min(L_i,(t_j-a_i)*v_i)\),计算完贡献后要把\(a_i\)改为\(t\),如果有\(b_i\),那么会发现这次询问过后,它和它的子树不再是整个被涉及了,应先将\(b_i\)下传到左右儿子再将\(b_i\)改为0
对于第(2)类点,可以继续在它的子树里dfs,直到找到在之前的询问中被涉及整个子树的点\(u\),点\(u\)在KD树中的子树的点上次被清空的时间都是\(b_u\),那么点\(u\)在KD树中的子树的点\(k\)的贡献是\(min(v_k*(t-b_u),L_k)\),相当于对于\(\lceil\frac{L_k}{v_k}\rceil\leq t-b_u\)的点有\(L_k\)的贡献,\(\lceil\frac{L_k}{v_k}\rceil> t\)的点有\(v_k*(t-b_u)\)的贡献
这个贡献可以在权值线段树中进行二分,发现与每个点的权值线段树有关的信息是它和它的子树的\(v\)和\(L\),这两个并不会被询问影响,那就可以在构造KD树时把每个点的权值线段树也建出来
第(2)类点子树中在之前的询问中没有被涉及整个子树的点和(1)的计算方式是一样的
计算完(2)类点的子树的贡献要将这个点的\(b\)改为\(t\)
复杂度\(\Theta(势能分析)\)
代码
没看过标程就写对了,令人开心
但产生“这种题就应该看标程才能写”这种想法,还是因为太菜了吧?
#include<algorithm>
#include<cmath>
#include<complex>
#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 maxn 152510
#define LL long long
#define mi (l+r>>1)
#define ls s[u][0]
#define rs s[u][1]
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 q,qx,lim,n,fir[maxn],nxt[maxn],v[maxn],cnt,root,rt[maxn],s[maxn][2],to[maxn<<7][2],ord[maxn];
LL w[maxn],ad[maxn],L[maxn],minvl,maxvl,xx[maxn][2],siz[maxn];
LL t,lst,lstnd[maxn],lsttr[maxn],now,tim,sumv[maxn<<7],suml[maxn<<7],nd,mx[maxn][2][2];
LL ql,qr,qd,qu,ans;
LL cl(int x,int y){return (LL)ceil((1.0*x)/(1.0*y));}
int on(LL n1,LL l1,LL r1){return l1<=n1&&n1<=r1;}
int in(LL l1,LL r1,LL l2,LL r2){return on(l1,l2,r2)&&on(r1,l2,r2);}
int yes(LL l1,LL r1,LL l2,LL r2){return on(l1,l2,r2)||on(r1,l2,r2)||on(l2,l1,r1)||on(r2,l1,r1);}
void ade(int u1,int v1,LL w1){v[cnt]=v1,nxt[cnt]=fir[u1],w[cnt]=w1,fir[u1]=cnt++;}
void getx(int u)
{
xx[u][0]=++tim,siz[u]=1;
for(int k=fir[u];k!=-1;k=nxt[k]){xx[v[k]][1]=xx[u][1]+w[k],getx(v[k]),siz[u]+=siz[v[k]];}
}
bool cmp(int x,int y){return xx[x][now]<xx[y][now];}
int ins(LL l,LL r,LL x,LL vv,LL ll)
{
int u=++nd;
sumv[u]+=vv,suml[u]+=ll;
if(l<r)
{
if(x<=mi)to[u][0]=ins(l,mi,x,vv,ll);
else to[u][1]=ins(mi+1,r,x,vv,ll);
}
return u;
}
int merge(int ua,int ub,LL l,LL r)
{
if(!ua||!ub)return ua^ub;
int u=++nd;
sumv[u]=sumv[ua]+sumv[ub],suml[u]=suml[ua]+suml[ub];
if(to[ua][0]||to[ub][0])
{
if(to[ua][0]&&to[ub][0])to[u][0]=merge(to[ua][0],to[ub][0],l,mi);
else to[u][0]=to[ua][0]^to[ub][0];
}
if(to[ua][1]||to[ub][1])
{
if(to[ua][1]&&to[ub][1])to[u][1]=merge(to[ua][1],to[ub][1],mi+1,r);
else to[u][1]=to[ua][1]^to[ub][1];
}
return u;
}
void add(int u,LL l,LL r)
{
if(r<=(t-lst)){ans+=suml[u];return;}
if(l>(t-lst)){ans+=sumv[u]*(t-lst);return;}
if(to[u][0])add(to[u][0],l,mi);
if(to[u][1])add(to[u][1],mi+1,r);
return;
}
int build(int l,int r)
{
sort(ord+l,ord+r+1,cmp);
int u=ord[mi];
rt[u]=ins(minvl,maxvl,cl(L[u],ad[u]),ad[u],L[u]);
mx[u][0][0]=mx[u][0][1]=xx[u][0],mx[u][1][0]=mx[u][1][1]=xx[u][1];
if(l<=mi-1)
{
ls=build(l,mi-1);
mx[u][0][0]=min(mx[u][0][0],mx[ls][0][0]);
mx[u][0][1]=max(mx[u][0][1],mx[ls][0][1]);
mx[u][1][0]=min(mx[u][1][0],mx[ls][1][0]);
mx[u][1][1]=max(mx[u][1][1],mx[ls][1][1]);
rt[u]=merge(rt[u],rt[ls],minvl,maxvl);
}
if(mi+1<=r)
{
rs=build(mi+1,r);
mx[u][0][0]=min(mx[u][0][0],mx[rs][0][0]);
mx[u][0][1]=max(mx[u][0][1],mx[rs][0][1]);
mx[u][1][0]=min(mx[u][1][0],mx[rs][1][0]);
mx[u][1][1]=max(mx[u][1][1],mx[rs][1][1]);
rt[u]=merge(rt[u],rt[rs],minvl,maxvl);
}
return u;
}
void getrt(int u)
{
if(lsttr[u])
{
lst=lsttr[u];
add(rt[u],minvl,maxvl);
return;
}
if(on(xx[u][0],ql,qr)&&on(xx[u][1],qd,qu))
{
if(cl(L[u],ad[u])<=(t-lstnd[u]))ans+=L[u];
else ans+=ad[u]*(t-lstnd[u]);
}
if(ls)getrt(ls);
if(rs)getrt(rs);
return;
}
void getans(int u)
{
if(!yes(mx[u][0][0],mx[u][0][1],ql,qr)||!yes(mx[u][1][0],mx[u][1][1],qd,qu))return;
if(in(mx[u][0][0],mx[u][0][1],ql,qr)&&in(mx[u][1][0],mx[u][1][1],qd,qu))
{
getrt(u);lsttr[u]=t;
return;
}
if(lsttr[u])lstnd[u]=lsttr[ls]=lsttr[rs]=lsttr[u],lsttr[u]=0;
if(on(xx[u][0],ql,qr)&&on(xx[u][1],qd,qu))
{
if(cl(L[u],ad[u])<=(t-lstnd[u]))ans+=L[u];
else ans+=ad[u]*(t-lstnd[u]);lstnd[u]=t;
}
if(ls)getans(ls);
if(rs)getans(rs);
}
int main()
{
freopen("exploit.in","r",stdin);
freopen("exploit.out","w",stdout);
n=read();
memset(fir,-1,sizeof(fir));
rep(i,1,n)ad[i]=read();
rep(i,1,n)L[i]=read(),minvl=min(minvl,cl(L[i],ad[i])),maxvl=max(maxvl,cl(L[i],ad[i])),ord[i]=i;
rep(i,2,n){int f=read(),d=read();ade(f,i,d);}
getx(1);
root=build(1,n);
q=read();
while(q--)
{
t=read(),qx=read(),lim=read();
ql=xx[qx][0],qr=xx[qx][0]+siz[qx]-1,qd=xx[qx][1],qu=xx[qx][1]+lim;ans=0;
getans(root);
write(ans);
}
return (~(0-0)+1);
}
cruise
题目大意
有\(n\)(\(n\leq152501\))个点的一条链,每个点有位置\(p_i\)(保证\(1\leq p_i\leq m\leq152501\)),到一个点\(i\)会停留\(a_i\)秒,从第\(i\)个点能传送到第\(i+1\)到第\(i+k\)个点之间的一个点\(j\),耗时\(b_i*|p_i-p_j|\)秒,问从第\(1\)个点走到第\(n\)个点至少花多少秒
题解
设\(f_i\)表示从第\(1\)个点走到第\(i\)个点并停留\(a_i\)秒至少要多少时间
则有\(f_i=min\{f_j+|p_i-p_j|*b_j+a_i|i-k\leq j\leq i-1\}\)
看上去像个可斜率优化的式子,但是\(i-k\leq j\leq i-1\)的限制和绝对值让它不太好解决
可以将这条链建线段树,每个点存 能转移到的区间包含这个区间的点
对线段树进行dfs,每次用 能转移到的区间包含这个区间的点 更新 这个区间的点
相当于去掉了\(i-k\leq j\leq i-1\)的限制
设$$ g_j(x)= \begin{cases} b_jx-b_jp_j+f_j, &x\in[p_j,m] \ -b_jx+b_jp_j+f_j, &x\in[1,p_j) \end{cases} $$
那么将\(p_i\)带入\(g_j\)就能得到从\(j\)转移到\(i\)的答案
可以把\(g_j\)看成两条线段,将 能转移到的区间包含这个区间的点的线段 插入 李超树,维护\(g_j(x)\)的最小值
更新完这个区间的点后要清空李超树
时间复杂度\(\Theta(n log^3 n)\)
代码
#include<algorithm>
#include<cmath>
#include<complex>
#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 LL long long
#define maxn 152510
#define mi (l+r>>1)
#define ls (u<<1)
#define rs (u<<1|1)
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 cntnd,tr[maxn<<2],n,m,k,fir[maxn<<2],nxt[maxn<<5],v[maxn<<5],cnt,L,R,id,s[maxn<<2],top;
LL a[maxn],b[maxn],p[maxn],dp[maxn];
void ade(int u1,int v1){v[cnt]=v1,nxt[cnt]=fir[u1],fir[u1]=cnt++;}
void build(int u,int l,int r)
{
if(L<=l&&r<=R)return ade(u,id);
if(L<=mi)build(ls,l,mi);
if(R>mi)build(rs,mi+1,r);
return;
}
LL y(int x,int i)
{
if(i>=0)return dp[i]+p[i]*b[i]-(LL)x*b[i];
i=-i;
return dp[i]+(LL)x*b[i]-p[i]*b[i];
}
void ins(int u,int l,int r)
{
if(!tr[u]){tr[u]=id;s[++top]=u;return;}
if(y(l,tr[u])<=y(l,id)&&y(r,tr[u])<=y(r,id))return;
if(y(l,tr[u])>=y(l,id)&&y(r,tr[u])>=y(r,id)){tr[u]=id;return;}
if(y(mi,tr[u])>=y(mi,id))
{
swap(tr[u],id);
if(y(l,tr[u])>=y(l,id)&&y(r,tr[u])<=y(r,id))ins(ls,l,mi);
else ins(rs,mi+1,r);
return;
}
if(y(l,id)>=y(l,tr[u])&&y(r,id)<=y(r,tr[u]))ins(rs,mi+1,r);
else ins(ls,l,mi);
return;
}
void add(int u,int l,int r)
{
if(L<=l&&r<=R){int tmpid=id;ins(u,l,r);id=tmpid;return;}
if(L<=mi)add(ls,l,mi);
if(R>mi)add(rs,mi+1,r);
return;
}
void ask(int u,int l,int r,int x)
{
if(x<=l&&r<=x){dp[id]=min(dp[id],y(x,tr[u])+a[id]);return;}
if(x<=mi)ask(ls,l,mi,x);
else ask(rs,mi+1,r,x);
dp[id]=min(dp[id],y(x,tr[u])+a[id]);
return;
}
void reset(){rep(i,1,top)tr[s[i]]=0;}
void work(int u,int l,int r)
{
top=0;
for(int k=fir[u];k!=-1;k=nxt[k]){L=1,R=p[v[k]],id=v[k],add(1,1,m);L=p[v[k]],R=m,id=-v[k],add(1,1,m);}
rep(i,l,r){id=i;ask(1,1,m,p[i]);}
reset();
if(l<r)work(ls,l,mi),work(rs,mi+1,r);
return;
}
int main()
{
freopen("cruise.in","r",stdin);
freopen("cruise.out","w",stdout);
n=read(),m=read(),k=read();
memset(fir,-1,sizeof(fir));
memset(dp,0x7f,sizeof(dp));int t=clock();
rep(i,1,n)p[i]=read();
rep(i,1,n)a[i]=read();
rep(i,1,n)b[i]=read();
dp[1]=a[1];
rep(i,1,n-1)L=i+1,R=min(i+k,n),id=i,build(1,1,n);
work(1,1,n);
write(dp[n]);
return (~(0-0)+1);
}
zoo
题目大意
定义KMP树为将一个字符串的\(next\)数组建出来后,将\(next_i\)向\(i\)连边,以\(0\)号点为根节点的有根树
定义\(i\)在KMP树中的深度为\(i\)到\(0\)这条链上的边数
定义\(f(s[l:r])\)表示字符串\(s\)第\(l\)个到第\(r\)个字符组成的子串建成的KMP树中所有点的深度之和
给出长度为\(n\)的字符串\(s\),\(q\)个询问,每个询问给出\(m_i\),计算$$\sum_{r=1}{m_i}\sum_{l=1}$$
题解
设\(g(i)\)表示点\(i\)在\(s[1:i],s[2:i],...,s[i:i]\)这\(i\)个串分别建出的KMP树中的深度之和
发现\(s[1:i]\)建出KMP树后点\(i\)的深度为和后缀相同的前缀个数+1
发现\(s[2:i]\)建出KMP树后点\(i\)的深度为和后缀相同的以2为起点的串的个数+1
……
发现\(s[i-1:i]\)建出KMP树后点\(i\)的深度为和后缀相同的以i-1为起点的串的个数+1
发现\(s[i:i]\)建出KMP树后点\(i\)的深度为1
那么\(g(i)=\)(和后缀相同的前缀数)+(后缀相同的以2为起点的串的个数)+……+(后缀相同的以i-1为起点的串的个数)+i
即\(g(i)=\)(和后缀相同的s[1:i]的子串的个数)+i
这个就是后缀自动机刚刚extend(i)时,np在fail树上的所有祖先\(x\)的\(right_x*(dis_x-dis_{fail_x})\)(right表示后缀自动机上的点包含的串出现的位置有几个,dis表示后缀自动机上的点中存的最长的串的长度)
用LCT维护后缀自动机的fail树
求出\(g(i)\)后,对它求一次前缀和,得到:$$g'(i)=\sum_{l=1}^{i}f(s[l:i])$$
对\(g'(i)\)求一次前缀和,得到:$$g''(i)=\sum_{r=1}{i}\sum_{l=1}f(s[l:r])$$
询问时直接输出\(g(m_i)\)就行了
代码
#include<algorithm>
#include<cmath>
#include<complex>
#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 maxn (152510<<1)
#define ls son[u][0]
#define rs son[u][1]
#define LL 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(int 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;
}
char s[maxn];
const LL mod=998244353;
int n,q,ans[maxn],son[maxn][2],fa[maxn],mk[maxn],ad[maxn],sumad[maxn],val[maxn],sumval[maxn],rgt[maxn],st[maxn],tp;
int fail[maxn],ch[maxn][26],dis[maxn],lst,rt,cnt;
int gx(char c){return c-'a';}
int mo(int x){return x>=mod?x-mod:x;}
void pu(int u)
{
sumad[u]=ad[u],sumval[u]=val[u];
if(ls)sumad[u]=mo(sumad[u]+sumad[ls]),sumval[u]=mo(sumval[u]+sumval[ls]);
if(rs)sumad[u]=mo(sumad[u]+sumad[rs]),sumval[u]=mo(sumval[u]+sumval[rs]);
}
void mark(int u,int k){if(u)mk[u]+=k,rgt[u]+=k,val[u]=mo((LL)ad[u]*(LL)k%mod+val[u]),sumval[u]=((LL)sumad[u]*(LL)k%mod+sumval[u]);}
void pd(int u){if(mk[u])mark(ls,mk[u]),mark(rs,mk[u]),mk[u]=0;}
int getso(int u){return son[fa[u]][0]!=u;}
int nort(int u){return son[fa[u]][0]==u||son[fa[u]][1]==u;}
void rot(int u)
{
int fu=fa[u],ffu=fa[fu],l=getso(u),fl=getso(fu),r=l^1,rson=son[u][r];
if(nort(fu))son[ffu][fl]=u;son[fu][l]=rson,son[u][r]=fu;
fa[u]=ffu,fa[fu]=u,fa[rson]=fu;pu(fu),pu(u);
}
void splay(int u)
{
int v=u;st[tp=1]=u;
while(nort(v))st[++tp]=v=fa[v];
while(tp)pd(st[tp--]);
while(nort(u)){int fu=fa[u];if(nort(fu))rot(getso(u)^getso(fu)?u:fu);rot(u);}
}
void acs(int u){for(int v=0;u;v=u,u=fa[u])splay(u),rs=v,pu(u);}
void cut(int u){acs(u),splay(u),fa[ls]=0,ls=0,pu(u);}
void ext(int i)
{
int p=lst,np=++cnt,to=gx(s[i]);dis[np]=i,lst=np;
for(;p&&!ch[p][to];p=fail[p])ch[p][to]=np;
if(!p)fail[np]=fa[np]=rt;
else
{
int q=ch[p][to];
if(dis[q]==dis[p]+1)fail[np]=fa[np]=q;
else
{
cut(q);
int nq=++cnt;dis[nq]=dis[p]+1;
ad[nq]=dis[nq]-dis[fail[q]],rgt[nq]=rgt[q],val[nq]=(LL)ad[nq]*(LL)rgt[nq]%mod,pu(nq),fail[nq]=fa[nq]=fail[q];
pd(q),ad[q]=dis[q]-dis[nq],val[q]=(LL)ad[q]*(LL)rgt[q]%mod,pu(q),fail[q]=fail[np]=fa[q]=fa[np]=nq;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
for(;p&&ch[p][to]==q;p=fail[p])ch[p][to]=nq;
}
}
acs(np),splay(np);
ad[np]+=dis[np]-dis[fail[np]];
pu(np);
mark(np,1);
pd(np);
ans[i]=sumval[np];
}
int main()
{
freopen("zoo.in","r",stdin);
freopen("zoo.out","w",stdout);
scanf("%s",s+1),n=strlen(s+1);lst=rt=++cnt;
rep(i,1,n)ext(i);
rep(i,1,n)ans[i]=mo(ans[i]+ans[i-1]);
rep(i,1,n)ans[i]=mo(ans[i]+ans[i-1]);
q=read();
while(q--)
{
int x=read();
write(ans[x]);
}
return (~(0-0)+1);
}