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);if(!lena&&y<m&&a[i]==b[y+1]) f[i][i][x][y+1]=max(f[i][i][x][y+1],f[i][j][x][y]+2);if(!lena&&x>1&&a[i]==b[x-1]) f[i][i][x-1][y]=max(f[i][i][x-1][y],f[i][j][x][y]+2);if(!lenb&&j<n&&b[x]==a[j+1]) f[i][j+1][x][x]=max(f[i][j+1][x][x],f[i][j][x][y]+2);if(!lenb&&i>1&&b[x]==a[i-1]) f[i-1][j][x][x]=max(f[i-1][j][x][x],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(能过)\)(其实是我不会算😛)
代码:
$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;
}//暴搜(最快)
一组 \(hack\) 数据
T2:数排列(perm)
思路:
嘿,有个 \(O(n^3)\) 的做法没听,当时光顾着笑(一些不明事物)了。这里只提供 \(O(n^2)\) 的做法。
我们设 \(f_{i,j}\) 表示数字 \(i\) 放到 \(j\) 的位置上的合法方案数,然后再前/后缀和优化一下撒~~
对于一个数 \(i\) ,若 \(s_i=1\) ,则我们设 \(f_{i,j}\) 表示 \(i\) 放在前 \(j\) 个位置的方案数 \(dp_{i,j}=\sum_{j=1}^{i} dp_{i-1,j}\)
代码:
$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;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)
思路:
这里提供根号分治和折半搜索两种思路。
折半搜索:
代码:
$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 515
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;
}
T2,T3未完...
后言:
感谢gyh提醒。
祝阿联生日快乐呀~~