CF1303

E Erase Subsequences

Link

Solution

\(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\)级别.总复杂度就变成三方的了.

Code

#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;
}

F Number of Components

Link

Solution

先对答案差分.维护每一次操作连通块数量的变化量,最后做前缀和就是答案了.

每一次操作把\(a_{i,j}\)变成\(c\),连通块数量的增量是由两方面贡献的:

  • 变成\(c\)之后连通块的增加量(可能为负值,因为可能会和四周的连通块进行合并)
  • 从原来的颜色变成c连通块的减少量(可能为正值,因为原来的连通块可能会分裂)

第一类贡献的维护很简单,用并查集实时维护连通块的状态.变成一个新的颜色就是新开了一个点(原来的点消失),然后看是否要和四周合并,res(增加量)初始是1,每和周围合并一次res-1.

然而并查集只适合用来合并,并不支持分裂操作,第二类贡献看起来没办法直接做...

但是!如果考虑倒序来看的话,每次的分裂就变成了合并!那么第二类贡献就变成了第一类贡献的形式!每次是合并连通块!这就可以并查集简单实现了!当然这个时候res贡献要乘以-1,因为是每一次的减少量.

所以流程大概是先正序把\(c_{old} \rightarrow c_{new}\)带来的合并的增量计算,然后倒序来一遍,把每次带来的分裂的增量计算.然后前缀和一下就是每次的答案.

注意倒序做之前要先把终态的连通块都先合并起来.

Code

#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;
}

G Sum of Prefix Sums

Link

Solution

这种树上的路径统计问题还是要优先往点分治那方面想.

注意点分治的要求是把路径从分治中心劈开,拆成两条相互独立的路径来,再计算答案.

这题同样从这里入手.假设有一条\(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\)是不变量可以先拿出来.

剩下的要求就是

  • 动态加入直线
  • 查询所有直线和\(x=x_0\)交点纵坐标的最大值

这个可以用李超线段树实现.

因为每个点都可以作为路径的\(u\)或者\(v\),所以\(l,s,p_u,p_v\)都要在\(dfs\)时记下来.另外再多记一个\(id\)表示属于哪棵子树,同一棵子树的点之间不能计入答案.

然后就正着做一遍再倒着做一遍(反向路径)即可.

实现上有些小细节.比如说上面的\(p_u+p_v+s_u*lb\),把\((s_u,p_u)\)看成点也可以,但是这样的话李超树就需要离散化,更麻烦,这个小细节很巧,节省码量.
还有就是加点的时候不要忘了\(rt\)也可以作为\(u/v\).

Code

#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;
}
posted @ 2020-04-20 23:02  Fruitea  阅读(158)  评论(0编辑  收藏  举报