Educational Codeforces Round 131 E,F

E

一道DP题。

首先发现我们有最优解的策略:一定是在后面删掉一些数(用left和delete),然后用(home)到开头,再从前面删掉一些数(用right和delete)。

那么我们可以设f(i,j)表示我们使用s中前i个字符,最后成为t的前j个字符,并且光标最后在i字符之后。

有转移:

f(i,j)f(i1,j1)+1si=tjf(i,j)f(i1,j)+2

同理设g(i,j)表示我们使用s中后i个字符,最后成为t的后j个字符,并且光标最后在i字符之前。

也有转移:

g(i,j)g(i+1,j+1)+1si=tjg(i,j)g(i+1,j)+1

注意我们从前往后要想删掉一个字符,必须先按right,然后再按下delete,但是从后往前删的时候,只要按delete就可以了(因为delete相当于按了后自动往左走),这也是为什么f的第二个转移要加二,而g的第二个转移只要加1

那么答案就是:

min(f(i,m),g(j,1),f(i,t)+g(j,q)+1[s.substr(i+1,j1)=t.substr(t+1,q1)])

这样是O(n3)的,过不了。

那么考虑在f(i,j)转移完成后,设f(i,j)表示光标不动s的前i个字符匹配t的前j个字符,有转移:

f(i,j)f(i,j)f(i,j)f(i1,j1)si=tj

对于g同理。

这样,我们的答案就为:

min(f(n,m),g(1,1),f(i,t)+g(i+1,t+1)+1)

时间复杂度就降为O(n2)。(相当于做了前缀和后缀min)

注意直接开f,g,f,g四个数组,会MLE,那么我们可以利用原来的f储存f,原来的g储存g,这样就只要开两个数组即可。

#include<bits/stdc++.h>
#define debug(...) std::cerr<<#__VA_ARGS__<<" : "<<__VA_ARGS__<<std::endl

const int INF=1000000000;
int n,m;
char a[5005],b[5005];
int f[5005][5005],g[5005][5005];

void solve() {
	scanf("%d%d",&n,&m);
	scanf("%s%s",a+1,b+1);
	for(int i=0;i<n+5;i++) {
		for(int j=0;j<m+5;j++) {
			f[i][j]=g[i][j]=INF;
		}
	}
	f[0][0]=0;
	for(int i=1;i<=n;i++) {
		for(int j=0;j<=i;j++) {
			f[i][j]=std::min(f[i][j],f[i-1][j]+2);
			if(j&&a[i]==b[j]) {
				f[i][j]=std::min(f[i][j],f[i-1][j-1]+1);
			}
		}
	}
	g[n+1][m+1]=0;
	for(int i=n;i>=1;i--) {
		for(int j=m+1;m-j<=n-i;j--) {
			g[i][j]=std::min(g[i][j],g[i+1][j]+1);
			if(j!=m+1&&a[i]==b[j]) {
				g[i][j]=std::min(g[i][j],g[i+1][j+1]+1);
			}
		}
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(a[i]==b[j]) {
				f[i][j]=std::min(f[i][j],f[i-1][j-1]);
			}
		}
	}
	for(int i=n;i>=1;i--) {
		for(int j=m;j>=1;j--) {
			if(a[i]==b[j]) {
				g[i][j]=std::min(g[i][j],g[i+1][j+1]);
			}
		}
	}
	int ans=std::min(f[n][m]+1,g[1][1]);
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			ans=std::min(ans,f[i][j]+g[i+1][j+1]+1);
		}
	}
	if(ans==INF) printf("-1\n");
	else printf("%d\n",ans);
	return;
}

int main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}

F

好像有别的做法,这里我取题解的做法因为我不会

就是首先我们想如何O(n2)的做这题。考虑对于询问,我们枚举i。假设目前[i+1,k]区间内共有f(i)个点,那么合法的j,k(2f(i))个。

那么考虑优化,发现我们其实就是要维护f(i)。相当于进行如下操作:

  1. 对某一段区间的f(i)加或减1
  2. lir(2f(i))

发现(2f(i))=f(i)(f(i)1)2=f(i)2f(i)2

那么我们就相当于要维护f(i)f(i)2

这可以用矩阵实现。我们设矩阵A=(f(i)0,f(i)1,f(i)2)

区间加一:由于f(i)=f(i)+1,f(i)2=f(i)2+2f(i)+1,故可以看成一个矩阵乘法:

A×(111012001)

区间减一也是相似的:

A×(111012001)

至此,我们就是要进行矩阵区间乘法和矩阵区间和,这可以使用线段树解决这个问题。

注意有可能有些点目前没有加入,所以我们不能以它作为i,这个可以使用一个tag(l,r)表示区间(l,r)是否有加入其中的点。在update时,如果左或右儿子的tag=1,我们才进行转移。

在加入或删除一个点时,我们也相应用线段树单点修改更新tag

时间复杂度为O(nlogn×A3)A=3为矩阵大小。

我之前用了vector储存矩阵,结果在2e5的数据上运行300多秒都没出结果,换成指针数组才用了3.8秒。

#include<bits/stdc++.h>
#define debug(...) std::cerr<<#__VA_ARGS__<<" : "<<__VA_ARGS__<<std::endl

using ll=long long;

ll unit[3][3]={{1,0,0},{0,1,0},{0,0,1}};
ll del[3][3]={{1,-1,1},{0,1,-2},{0,0,1}};
ll add[3][3]={{1,1,1},{0,1,2},{0,0,1}};

ll val[800005][1][3],lazy[800005][3][3];
bool flag[800005];

void plus(ll (*a1)[3],ll (*a2)[3]) {
	for(int i=0;i<3;i++) a1[0][i]+=a2[0][i];
}

void mul1(ll (*a1)[3],ll (*a2)[3]) {
	ll pre[3]={a1[0][0],a1[0][1],a1[0][2]};
	a1[0][0]=a1[0][1]=a1[0][2]=0;
	for(int i=0;i<3;i++) for(int j=0;j<3;j++) a1[0][i]+=pre[j]*a2[j][i];
}

void mul2(ll (*a1)[3],ll (*a2)[3]) {
	ll pre[3][3]={{a1[0][0],a1[0][1],a1[0][2]},{a1[1][0],a1[1][1],a1[1][2]},{a1[2][0],a1[2][1],a1[2][2]}};
	a1[0][0]=0; a1[0][1]=0; a1[0][2]=0; a1[1][0]=0; a1[1][1]=0; a1[1][2]=0; a1[2][0]=0; a1[2][1]=0; a1[2][2]=0;
	for(int i=0;i<3;i++) for(int j=0;j<3;j++) for(int k=0;k<3;k++) a1[j][k]+=pre[j][i]*a2[i][k];
}

void push(int pos) {
	mul1(val[pos<<1],lazy[pos]);
	mul1(val[pos<<1|1],lazy[pos]);
	mul2(lazy[pos<<1],lazy[pos]);
	mul2(lazy[pos<<1|1],lazy[pos]);
	for(int i=0;i<3;i++) for(int j=0;j<3;j++) lazy[pos][i][j]=unit[i][j];
}

void upd(int pos) {
	val[pos][0][0]=val[pos][0][1]=val[pos][0][2]=0;
	if(flag[pos<<1]) plus(val[pos],val[pos<<1]);
	if(flag[pos<<1|1]) plus(val[pos],val[pos<<1|1]);
	flag[pos]=flag[pos<<1]|flag[pos<<1|1];
}

void SetMatrix(int l,int r,ll (*mt)[3],int pos,int lef,int rig) {
	if(l<=lef&&rig<=r) {
		mul2(lazy[pos],mt); mul1(val[pos],mt);
	} else if(l<=rig&&r>=lef) {
		int mid=lef+rig>>1;
		push(pos);
		SetMatrix(l,r,mt,pos<<1,lef,mid);
		SetMatrix(l,r,mt,pos<<1|1,mid+1,rig);
		upd(pos);
	}
}

void SetNode(int p,int f,int pos,int lef,int rig) {
	if(lef==rig) {flag[pos]=f; return;}
	push(pos);
	int mid=lef+rig>>1;
	if(p<=mid) SetNode(p,f,pos<<1,lef,mid);
	else SetNode(p,f,pos<<1|1,mid+1,rig);
	upd(pos);
}

void build(int pos,int lef,int rig) {
	for(int i=0;i<3;i++) for(int j=0;j<3;j++) lazy[pos][i][j]=unit[i][j];	
	if(lef==rig) {
		val[pos][0][0]=1;
		return;
	}
	int mid=lef+rig>>1;
	build(pos<<1,lef,mid);
	build(pos<<1|1,mid+1,rig);
	upd(pos);
}

int d,in[200005];

int main() {
	int q; scanf("%d%d",&q,&d);
	build(1,1,200000);
	for(int i=1;i<=q;i++) {
		int x; scanf("%d",&x);
		if(in[x]) {
			in[x]=0; SetNode(x,0,1,1,200000);
			int l=std::max(1,x-d),r=x-1; 
			if(l<=r) SetMatrix(l,r,del,1,1,200000);
		} else {
			in[x]=1; SetNode(x,1,1,1,200000);
			int l=std::max(1,x-d),r=x-1;
			if(l<=r) SetMatrix(l,r,add,1,1,200000);
		}
		printf("%lld\n",(val[1][0][2]-val[1][0][1])/2);
	}
	return 0;
}
posted @   Nastia  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示