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提醒。
祝阿联节日快乐呀~~