CSP-S模拟37

T1:回文(string)

思路:

由于本题的数据范围较小,所以可能有多种 \(dp\) 状态,这里只呈现其中可能较典的两种外加一种暴搜最优解。

DP1:

我们设 \(f_{i,j,x,y}\) 表示使用 \(a\) 串的 \(i\) ~ \(j\)\(b\) 串的 \(x\) ~ \(y\) 能否拼成一个回文串。

首先考虑原始状态是什么样的。显然原始状态有三种大情况: \(a,b\) 中的单个字符,\(a,b\) 中相邻的两个相同的字符以及 \(a,b\) 串中相同的字符。这些显然都是初始能构成回文串的字符。

然后再考虑转移。显然有四种转移方式:\(a\) 串自己左右扩展, \(b\) 串自己左右扩展, \(a\) 串左端与 \(b\) 串右端匹配, \(a\) 串右端与 \(b\) 串左端匹配。

最后我们枚举每个串截取的长度,然后枚举起点,计算出终点。若 \(f_{i,j,x,y}\)\(1\) ,则 \(ans=max(ans,lena+lenb)\)

\(O(Tn^4)\) 的时间复杂度。跑得还是比较快的。

代码:

$code$
#include<iostream>
#include<cstring>
using namespace std;
int T,n,m,ans,f[55][55][55][55];
string a,b;
int main(){
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	ios::sync_with_stdio(false);
	cin>>T;
	while(T--){
		ans=1;
		memset(f,0,sizeof(f));
		cin>>a>>b;
		a=' '+a;b=' '+b;
		n=a.size()-1;m=b.size()-1;
		for(int i=1;i<=n;i++) 
			for(int j=1;j<=m+1;j++) 
				f[i][i][j][j-1]=1;
		for(int j=1;j<=m;j++) 
			for(int i=1;i<=n+1;i++) 
				f[i][i-1][j][j]=1;
		for(int i=1;i<=n;i++) 
			for(int j=1;j<=m;j++) 
				if(a[i]==b[j]) 
					f[i][i][j][j]=1;
		for(int i=1;i<n;i++){
			if(a[i]==a[i+1]){
				for(int j=1;j<=m+1;j++){
					ans=2;
					f[i][i+1][j][j-1]=1;
				}
			}
		}
		for(int j=1;j<m;j++){
			if(b[j]==b[j+1]){
				for(int i=1;i<=n+1;i++){
					ans=2;
					f[i][i-1][j][j+1]=1;
				}
			}
		}
		for(int lena=0;lena<=n;lena++){
			for(int i=1;i<=n-lena+1;i++){
				int j=i+lena-1;
				for(int lenb=0;lenb<=m;lenb++){
					for(int x=1;x<=m-lenb+1;x++){
						int y=x+lenb-1;
						if(f[i][j][x][y]){
							ans=max(ans,lena+lenb);
							if(i>1&&j<n&&a[i-1]==a[j+1]) f[i-1][j+1][x][y]=1;
							if(x>1&&y<m&&b[x-1]==b[y+1]) f[i][j][x-1][y+1]=1;
							if(i>1&&y<m&&a[i-1]==b[y+1]) f[i-1][j][x][y+1]=1;
							if(x>1&&j<n&&a[j+1]==b[x-1]) f[i][j+1][x-1][y]=1;
						}
					}
				}
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}//题解方法 (较快) 

DP2:

我们设 \(f_{i,j,x,y}\) 表示使用 \(a\) 串的 \(i\) ~ \(j\)\(b\) 串的 \(x\) ~ \(y\) 能拼成回文串的最长长度。

还是先考虑初始状态,显然跟上面的是一样的,不过上述的状态一初始值为 \(1\) ,状态二、三的初始值为 \(2\) (因为存的是长度嘛)

然后转移就是正常的转移啦~~

最后取 \(max\) 就好啦~~

时间复杂度也是 \(O(Tn^4)\) 的,不过跑起来不如上面那个快。

$code$
#include<iostream>
#include<cstring>
using namespace std;
int T,m,n,ans,f[55][55][55][55];
string a,b;
int main(){
//	freopen("string.in","r",stdin);
//	freopen("string.out","w",stdout);
	ios::sync_with_stdio(false);
	cin>>T;
	while(T--){
		memset(f,0,sizeof(f));
		cin>>a>>b;
		a=' '+a;b=' '+b;
		n=a.size()-1;m=b.size()-1;
		for(int lena=0;lena<=n;lena++){
			for(int i=1;i<=n-lena+1;i++){
				int j=i+lena-1;
				for(int lenb=0;lenb<=m;lenb++){
					for(int x=1;x<=m-lenb+1;x++){
						int y=x+lenb-1;
						if(lena+lenb==1) f[i][j][x][y]=1;
						if(lena+lenb==2){
							if(!lena&&b[x]==b[y]) f[i][j][x][y]=2;
							else if(!lenb&&a[i]==a[j]) f[i][j][x][y]=2;
							else if(lena&&lenb&&a[i]==b[x]) f[i][j][x][y]=2;
						}
						if(lena+lenb!=f[i][j][x][y]) continue;
						if(i>1&&j<n&&a[i-1]==a[j+1]) f[i-1][j+1][x][y]=max(f[i-1][j+1][x][y],f[i][j][x][y]+2); 
						if(x>1&&y<m&&b[x-1]==b[y+1]) f[i][j][x-1][y+1]=max(f[i][j][x-1][y+1],f[i][j][x][y]+2);
						if(i>1&&y<m&&a[i-1]==b[y+1]) f[i-1][j][x][y+1]=max(f[i-1][j][x][y+1],f[i][j][x][y]+2);
						if(x>1&&j<n&&b[x-1]==a[j+1]) f[i][j+1][x-1][y]=max(f[i][j+1][x-1][y],f[i][j][x][y]+2);
					}
				}
			}
		}
		int ans=0;
		for(int i=1;i<=n;i++){
			for(int j=i-1;j<=n;j++){
				for(int x=1;x<=m;x++){
					for(int y=x-1;y<=m;y++){
						ans=max(ans,f[i][j][x][y]);
					}
				}
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}//分讨(较慢) 

暴搜:

听说或许可以构造数据 \(hack\) 掉?

但目前看来的确是最优解无疑了。

我们分别枚举 \(a,b\) 串的回文中心(记得特判一下回文串长度为偶数的情况呀),然后跟上面的 \(dp\) 转移相似,分别向左右暴搜就行了。

复杂度为 \(O(能过)\)(其实是我不会算😛)

update: 据本人说这个代码的时间复杂度是假的,所以请谨慎使用哦~~

代码:

$code$
#include<iostream>
using namespace std;
int T,m,n,ans;
string a,b;
inline void dfs(int la,int ra,int lb,int rb,int len){
	ans=max(ans,len);
	if(la>=1&&ra<=n&&a[la]==a[ra]) dfs(la-1,ra+1,lb,rb,len+2);
	if(la>=1&&rb<=m&&a[la]==b[rb]) dfs(la-1,ra,lb,rb+1,len+2);
	if(lb>=1&&ra<=n&&b[lb]==a[ra]) dfs(la,ra+1,lb-1,rb,len+2);
	if(lb>=1&&rb<=m&&b[lb]==b[rb]) dfs(la,ra,lb-1,rb+1,len+2);

}
int main(){
//	freopen("string.in","r",stdin);
//	freopen("string.out","w",stdout);
	ios::sync_with_stdio(false);
	cin>>T;
	while(T--){
		ans=0;
		cin>>a>>b;
		a=' '+a;b=' '+b;
		n=a.size()-1;m=b.size()-1;
		for(int i=1;i<=n+1;i++){
			for(int j=1;j<=m+1;j++){
				dfs(i-1,i,j-1,j,0);
				if(i!=n+1) dfs(i-1,i+1,j-1,j,1);
				if(j!=m+1) dfs(i-1,i,j-1,j+1,1);//回文长度为偶数 
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}//暴搜(最快) 

T2:数排列(perm)

思路:

嘿,有个 \(O(n^3)\) 的做法没听,当时光顾着笑(一些不明事物)了。这里只提供 \(O(n^2)\) 的做法。

我们设 \(f_{i,j}\) 表示数字 \(i\) 放到 \(j\) 的位置上的合法方案数,直接枚举 \(i-1\) 的位置,然后再前/后缀和优化一下撒~~

代码:

$code$
#include<iostream>
using namespace std;
const int N=2025,mod=1e9+7;
int n,s[N],f[N][N],ans;
int main(){
	freopen("perm.in","r",stdin);
	freopen("perm.out","w",stdout);
	ios::sync_with_stdio(false);
	cin>>n;
	f[0][1]=1;
	for(int i=1;i<n;i++) cin>>s[i];
	for(int i=1;i<n;i++){
		if(s[i]==1) for(int j=2;j<=i+1;j++) f[i][j]=(f[i][j-1]+f[i-1][j-1])%mod;//i放到j位置的方案数等价于i-1放到所有j-1及以前位置的方案数加和
		else for(int j=i;j>=1;j--) f[i][j]=(f[i][j+1]+f[i-1][j])%mod;
	}
	for(int i=1;i<=n;i++) ans=(ans+f[n-1][i])%mod;
	cout<<ans<<'\n';
	return 0;
}//O(n^2)

T3:树上的背包(knapsack)

思路:

这里提供根号分治和折半搜索两种思路。

折半搜索:

对于每一次查询,我们把该节点及其祖先节点单独记录下来,这样就转化为了一个简单问题:在一堆物品里选代价不超过 \(L\) 且价值最大的物品。不过千万不要被题目的背包限制住思维,考虑折半搜索。

我们先来浅浅算一下时间复杂度。每个节点的深度为 \(log~ _n\) (显然这是一颗二叉树),所以折半搜的时间复杂度为 \(2^{ \frac{log_n}{2}}\),转化一下其实就是 \(\sqrt n\)。再算上多测那就是 \(O(q \sqrt n)\) 。显然可过,不过跑得不大快,而且码量相对较大就是了。(相比于根号分治来说)

代码:

$code$
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N=1e5+5;
int n,Q,x,L,cnt,tot1,tot2,ans,v[N],w[N],vl[N],wei[N];
struct flower{
	int val,weight;
	bool operator<(const flower &css)const{
		if(weight!=css.weight) return weight<css.weight;
		else return val<css.val;
	}
}a[N],s1[N],s2[N];
inline void dfs1(int pos,int weight,int val){
	if(weight>L) return ;
	if(pos==cnt/2+1){
		s1[++tot1]={val,weight};
		return ;
	}
	dfs1(pos+1,weight,val);
	dfs1(pos+1,weight+a[pos].weight,val+a[pos].val);
}
inline void dfs2(int pos,int weight,int val){
	if(weight>L) return ;
	if(pos==cnt+1){
		s2[++tot2]={val,weight};
		return ;
	}
	dfs2(pos+1,weight,val);
	dfs2(pos+1,weight+a[pos].weight,val+a[pos].val);
}
signed main(){
	freopen("knapsack.in","r",stdin);
	freopen("knapsack.out","w",stdout);
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	cin>>Q;
	while(Q--){
		tot1=tot2=ans=cnt=0;
		cin>>x>>L;
		a[++cnt]={0,0};
		while(x){
			a[++cnt]=(flower){v[x],w[x]};
			x/=2;
		}
		dfs1(1,0,0);
		dfs2(cnt/2+1,0,0);
		sort(s1+1,s1+tot1+1);
		sort(s2+1,s2+tot2+1);
		for(int i=1;i<=tot2;i++) s2[i].val=max(s2[i].val,s2[i-1].val);
		for(int i=1,j=tot2;i<=tot1;i++){
			while(j&&s2[j].weight+s1[i].weight>L) j--;
			if(s1[i].weight+s2[j].weight<=L) ans=max(ans,s1[i].val+s2[j].val);
		}
		cout<<ans<<'\n';
	}
	return 0;
}//折半搜 (慢) 
/*
3
1 2
2 3
3 4
3
1 1
2 5
3 5

15
123 119
129 120
132 112
126 109
118 103
115 109
102 100
130 120
105 105
132 115
104 102
107 107
127 116
121 104
121 115
8
8 234
9 244
10 226
11 227
12 240
13 237
14 206
15 227

*/

根号分治:

嗨,一种优雅的暴力,我觉得没啥难理解的地方,就不展开啦~~

代码:

$code$
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e5+5,maxn=511;
int n,x,l,Q,dp[520][N];
struct flower{
	int v,w;
}a[N];
inline int work(int x,int l){
	if(l<0) return -1e9;
	if(x<=maxn) return dp[x][l];
	return max(work(x>>1,l),work(x>>1,l-a[x].w)+a[x].v);
}
int main(){
//	freopen("knapsack.in","r",stdin);
//	freopen("knapsack.out","w",stdout);
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i].v>>a[i].w;
	for(int i=1;i<=min(maxn,n);i++){
		if(i>>1) memcpy(dp[i],dp[i>>1],sizeof(dp[i]));
		for(int j=N-5;j>=a[i].w;j--){
			dp[i][j]=max(dp[i][j],dp[i][j-a[i].w]+a[i].v);
		}
	}
	cin>>Q;
	while(Q--){
		cin>>x>>l;
		cout<<work(x,l)<<'\n';
	}
	return 0;
}

后言:

感谢gyh提醒。

祝阿联节日快乐呀~~

posted @ 2025-10-24 22:09  晏清玖安  阅读(20)  评论(0)    收藏  举报