标签:https 包括 简单 ems open bre define query 维护
\(t\)最多由两个串构成,设分别为\(t1\),\(t2\)(可为空串).
要检查是否合法,首先枚举\(t1\),\(t2\)的分界线,然后一个\(n^3\)\(dp\)显然:
设\(f_{i,j,k}\)表示当前到了\(s_i\),同时已经匹配到\(t1_j\),\(t2_k\),是否合法.
转移就考虑\(s_{i+1}\)不匹配;和\(t1_{j+1}\)匹配;和\(t2_{k+1}\)匹配三种即可.
这样就得到了一个四方算法.
考虑怎么优化\(dp\).
\(f_{i,j,k}\)的\(dp\)值是个\(bool\)变量,存储它是有点浪费的.
有一个巧妙的方法是转换下标和\(dp\)值.
具体的,我们可以考虑贪心,尽量能匹配就匹配(当然匹配\(t1\)还是匹配\(t2\)仍然是需要决策的部分),重新设一个\(dp\): \(f_{j,k}\)表示匹配到\(t1_j\)和\(t2_k\)所需要的\(s\)最短前缀的长度(即原来的下标\(i\)).
实现的时候对每个位置预处理出\(tr_{p,c}\),即从位置\(p\)出发第一个\(c\)的位置,即可把\(dp\)优化到\(n^2\)级别.总复杂度就变成三方的了.
#include<bits/stdc++.h>
using namespace std;
#define REP(i,a,b) for(int i=(a),_ed=(b);i<=_ed;++i)
#define DREP(i,a,b) for(int i=(a),_ed=(b);i>=_ed;--i)
#define mp(x,y) make_pair((x),(y))
#define sz(x) (int)(x).size()
#define pb push_back
typedef long long ll;
typedef pair<int,int> pii;
inline int read(){
register int x=0,f=1;register char ch=getchar();
while(!isdigit(ch)){if(ch==‘-‘)f=0;ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^‘0‘);ch=getchar();}
return f?x:-x;
}
const int N=4e2+5,inf=0x3f3f3f3f;
int n,m,f[N][N],las[26],tr[N][26];
char s[N],t[N];
inline void chkmin(int& x,int y){x=x<y?x:y;}
int main(){
// freopen("in.in","r",stdin);
REP(T,1,read()){
scanf("%s\n%s",s+1,t+1);
n=strlen(s+1),m=strlen(t+1);
REP(i,0,25)las[i]=inf;
DREP(i,n,0){
REP(j,0,25)tr[i][j]=las[j];
if(i)las[s[i]-‘a‘]=i;
}
int flg=0;
REP(bp,1,m){
if(flg)break;
int la=bp,lb=m-bp;
memset(f,inf,sizeof f);
f[0][0]=0;
REP(i,0,la)REP(j,0,lb)if(f[i][j]<inf){
int p=f[i][j];
if(~tr[p][t[i+1]-‘a‘])chkmin(f[i+1][j],tr[p][t[i+1]-‘a‘]);
if(~tr[p][t[la+j+1]-‘a‘])chkmin(f[i][j+1],tr[p][t[la+j+1]-‘a‘]);
}
if(f[la][lb]<inf)flg=1;
}
puts(flg?"YES":"NO");
}
return 0;
}
先对答案差分.维护每一次操作连通块数量的变化量,最后做前缀和就是答案了.
每一次操作把\(a_{i,j}\)变成\(c\),连通块数量的增量是由两方面贡献的:
第一类贡献的维护很简单,用并查集实时维护连通块的状态.变成一个新的颜色就是新开了一个点(原来的点消失),然后看是否要和四周合并,res(增加量)初始是1,每和周围合并一次res-1.
然而并查集只适合用来合并,并不支持分裂操作,第二类贡献看起来没办法直接做...
但是!如果考虑倒序来看的话,每次的分裂就变成了合并!那么第二类贡献就变成了第一类贡献的形式!每次是合并连通块!这就可以并查集简单实现了!当然这个时候res贡献要乘以-1,因为是每一次的减少量.
所以流程大概是先正序把\(c_{old} \rightarrow c_{new}\)带来的合并的增量计算,然后倒序来一遍,把每次带来的分裂的增量计算.然后前缀和一下就是每次的答案.
注意倒序做之前要先把终态的连通块都先合并起来.
#include<bits/stdc++.h>
using namespace std;
#define REP(i,a,b) for(int i=(a),_ed=(b);i<=_ed;++i)
#define DREP(i,a,b) for(int i=(a),_ed=(b);i>=_ed;--i)
#define mp(x,y) make_pair((x),(y))
#define sz(x) (int)(x).size()
#define pb push_back
typedef long long ll;
typedef pair<int,int> pii;
inline int read(){
register int x=0,f=1;register char ch=getchar();
while(!isdigit(ch)){if(ch==‘-‘)f=0;ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^‘0‘);ch=getchar();}
return f?x:-x;
}
const int N=3e2+5,Q=2e6+5,M=Q+N*N;
int n,m,q,col[N][N],id[N][N],cnt,fa[M],ans[Q],res;
struct query{int x,y,a,b;} que[Q];
inline int find(int x){return fa[x]=fa[x]==x?x:find(fa[x]);}
inline void merge(int x,int y){
x=find(x),y=find(y);
if(x!=y)fa[x]=y,--res;
}
int main(){
// freopen("in.in","r",stdin);
memset(col,-1,sizeof col);
n=read(),m=read(),q=read();
REP(i,1,n)REP(j,1,m)col[i][j]=0;
REP(t,1,q){
int x=read(),y=read(),c=read();
que[t]=(query){x,y,col[x][y],c};
col[x][y]=c;
}
ans[0]=1;
cnt=0;
REP(i,1,n)REP(j,1,m)col[i][j]=0,id[i][j]=++cnt,fa[cnt]=cnt;
REP(t,1,q){
if(que[t].a==que[t].b)continue;
int x=que[t].x,y=que[t].y;
col[x][y]=que[t].b,id[x][y]=++cnt,fa[cnt]=cnt,res=1;
if(col[x][y]==col[x-1][y])merge(id[x][y],id[x-1][y]);
if(col[x][y]==col[x+1][y])merge(id[x][y],id[x+1][y]);
if(col[x][y]==col[x][y-1])merge(id[x][y],id[x][y-1]);
if(col[x][y]==col[x][y+1])merge(id[x][y],id[x][y+1]);
ans[t]+=res;
}
cnt=0;
REP(i,1,n)REP(j,1,m)id[i][j]=++cnt,fa[cnt]=cnt;
REP(i,1,n)REP(j,1,m){
if(col[i][j]==col[i-1][j])merge(id[i][j],id[i-1][j]);
if(col[i][j]==col[i+1][j])merge(id[i][j],id[i+1][j]);
if(col[i][j]==col[i][j-1])merge(id[i][j],id[i][j-1]);
if(col[i][j]==col[i][j+1])merge(id[i][j],id[i][j+1]);
}
DREP(t,q,1){
swap(que[t].a,que[t].b);
if(que[t].a==que[t].b)continue;
int x=que[t].x,y=que[t].y;
col[x][y]=que[t].b,id[x][y]=++cnt,fa[cnt]=cnt,res=1;
if(col[x][y]==col[x-1][y])merge(id[x][y],id[x-1][y]);
if(col[x][y]==col[x+1][y])merge(id[x][y],id[x+1][y]);
if(col[x][y]==col[x][y-1])merge(id[x][y],id[x][y-1]);
if(col[x][y]==col[x][y+1])merge(id[x][y],id[x][y+1]);
ans[t]-=res;
}
REP(t,1,q)ans[t]+=ans[t-1],printf("%d\n",ans[t]);
return 0;
}
这种树上的路径统计问题还是要优先往点分治那方面想.
注意点分治的要求是把路径从分治中心劈开,拆成两条相互独立的路径来,再计算答案.
这题同样从这里入手.假设有一条\(u \rightarrow v\)的路径.
设\(u\)到分治中心\(rt\)共有\(la\)个节点\([a_1,a_2,\dots,a_{la}]\)(包括\(rt\)),从\(rt\)到\(v\)共有\(lb\)个节点\([b_1,b_2,\dots,b_{lb}]\)(不包括\(rt\)).
另外设\(p_u=\sum_{i=1}^{la}(la-i+1)w_{a_i}\),\(s_u=\sum_{i=1}^{la}w_{a_i}\).
则\(u \rightarrow v\)的权值就是\(p_u+p_v+s_u*lb\).
这是个一次函数的形式.可以把\(s_u\)看成斜率,\(p_u\)看成截距,\((lb,p_v)\)看成点.
固定\(v\)的话,则要求的就是\((lb,p_v)\)和所有直线匹配的一次函数最大值.
\(p_v\)是不变量可以先拿出来.
剩下的要求就是
这个可以用李超线段树实现.
首先再多记一个\(id\)表示属于哪棵子树,同一棵子树的点之间不能计入答案.另外因为每个点都可以作为路径的\(u\)或者\(v\),所以\(l,s,p_u,p_v\)都要在\(dfs\)时记下来.
然后就正着做一遍再倒着做一遍(反向路径)即可.
实现上有些小细节.比如说上面的\(p_u+p_v+s_u*lb\),把\((s_u,p_u)\)看成点也可以,但是这样的话李超树就需要离散化,更麻烦,这个小细节很巧,节省码量.另外加点的时候不要忘了\(rt\)也可以作为\(u/v\).
#include<bits/stdc++.h>
using namespace std;
#define REP(i,a,b) for(int i=(a),_ed=(b);i<=_ed;++i)
#define DREP(i,a,b) for(int i=(a),_ed=(b);i>=_ed;--i)
#define mp(x,y) make_pair((x),(y))
#define sz(x) (int)(x).size()
#define pb push_back
typedef long long ll;
typedef pair<int,int> pii;
inline int read(){
register int x=0,f=1;register char ch=getchar();
while(!isdigit(ch)){if(ch==‘-‘)f=0;ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^‘0‘);ch=getchar();}
return f?x:-x;
}
const int N=1.5e5+5;
int n,w[N];ll ans;
vector<int> E[N];
namespace LichaoTree{
#define ls(p) p<<1
#define rs(p) p<<1|1
ll k[N<<2],b[N<<2];bool flg[N<<2];
inline ll cal(ll k,ll b,ll x){return k*x+b;}
void build(int p,int l,int r){
k[p]=b[p]=flg[p]=0;
if(l==r)return;
int mid=(l+r)>>1;
build(ls(p),l,mid),build(rs(p),mid+1,r);
}
void insert(int p,int l,int r,ll _k,ll _b){
if(!flg[p])return k[p]=_k,b[p]=_b,flg[p]=1,void();
ll l1=cal(k[p],b[p],l),r1=cal(k[p],b[p],r),l2=cal(_k,_b,l),r2=cal(_k,_b,r);
if(l1<=l2&&r1<=r2)return k[p]=_k,b[p]=_b,void();
if(l1>l2&&r1>r2)return;
int mid=(l+r)>>1;
if(cal(k[p],b[p],mid)<cal(_k,_b,mid))swap(k[p],_k),swap(b[p],_b);
if(_k<=k[p])insert(ls(p),l,mid,_k,_b);
else insert(rs(p),mid+1,r,_k,_b);
}
ll query(int p,int l,int r,int x){
if(l==r)return cal(k[p],b[p],x);
int mid=(l+r)>>1;
if(x<=mid)return max(cal(k[p],b[p],x),query(ls(p),l,mid,x));
else return max(cal(k[p],b[p],x),query(rs(p),mid+1,r,x));
}
}using namespace LichaoTree;
namespace Partition{
int rt,rtsiz,siz[N],vis[N],cnt;
struct node{
int id;ll p1,p2,s,l;
inline node(int _id=0,ll _p1=0,ll _p2=0,ll _s=0,ll _l=0):id(_id),p1(_p1),p2(_p2),s(_s),l(_l){}
} a[N];
void getnode(int u,int pa,int id,int rt,ll p1,ll p2,ll s,ll l){
int flg=0;
s+=w[u],l+=1,p1+=(l+1)*w[u],p2+=s-w[rt];
for(int v:E[u]){
if(v==pa||vis[v])continue;
flg=1;
getnode(v,u,id,rt,p1,p2,s,l);
}
if(!flg)a[++cnt]=node(id,p1,p2,s,l);
}
void work(int rt,int n){
a[cnt=1]=node(rt,w[rt],0,w[rt],0);
for(int v:E[rt]){
if(vis[v])continue;
getnode(v,0,v,rt,w[rt],0,w[rt],0);
}
build(1,1,n);
for(int l=1,r;l<=cnt;l=r+1){
r=l;while(r<cnt&&a[r+1].id==a[l].id)++r;
REP(i,l,r)ans=max(ans,query(1,1,n,a[i].l)+a[i].p2);
REP(i,l,r)insert(1,1,n,a[i].s,a[i].p1);
}
build(1,1,n);
for(int r=cnt,l;r;r=l-1){
l=r;while(l>1&&a[l-1].id==a[r].id)--l;
REP(i,l,r)ans=max(ans,query(1,1,n,a[i].l)+a[i].p2);
REP(i,l,r)insert(1,1,n,a[i].s,a[i].p1);
}
}
void getrt(int u,int pa,int sum){
siz[u]=1;int mx=0;
for(int v:E[u]){
if(v==pa||vis[v])continue;
getrt(v,u,sum),siz[u]+=siz[v];
mx=max(mx,siz[v]);
}
mx=max(mx,sum-siz[u]);
if(mx<rtsiz)rt=u,rtsiz=mx;
}
void solve(int u,int sum){
rt=0,rtsiz=n+1;
getrt(u,0,sum),vis[u=rt]=1;
work(u,sum);
for(int v:E[u]){
if(vis[v])continue;
solve(v,siz[u]>siz[v]?siz[v]:sum-siz[u]);
}
}
}
int main(){
// freopen("in.in","r",stdin);
n=read();
REP(i,1,n-1){
int u=read(),v=read();
E[u].pb(v),E[v].pb(u);
}
REP(i,1,n)w[i]=read();
Partition::solve(1,n);
printf("%lld\n",ans);
return 0;
}
标签:https 包括 简单 ems open bre define query 维护
原文地址:https://www.cnblogs.com/fruitea/p/12741322.html