ARC 157 F Sol
嫌弃讲题人的我,准备好好写一篇题解。
link to problem
观察数据范围:\(1\le N\le 50\)。
如果是 \(20\),想到 \(2^{20}\);如果是 \(40\),想到 \(2^{40\div 2}\);若果是 \(50\) 呢?\(2^{25}\) 有点大,于是想到 \(2^{50\div 3}\)。但是如何去凑?
Hint
\(N\) | \(S\) | \(T\) | \(res\) |
---|---|---|---|
\(6\) | \(\tt{XXXXXX}\) | \(\tt{YYYYYY}\) | \(\tt{XYXYX}\) |
\(8\) | \(\tt{XXXXXXXX}\) | \(\tt{XXXXXXXX}\) | \(\tt{XXXXXXXX}\) |
\(20\) | \(\tt{YYYXYYXXXXXYYXYYXXXY}\) | \(\tt{YXYYXXXYYXYYYYYYXXYY}\) | \(\tt{YYYXYXXYXXYYYYYXXY}\) |
备注:第一行是最坏情况,第二行最好,第三行随机。
What you can see
答案长度都很大?再看看。最小长度多少?
lemma: 答案 \(\ge \lfloor \frac{2N}{3}\rfloor\)。
证明:\(N=3k+r\)证明长度是 \(3\) 的答案 \(\ge 2\) 和 \(r\) 的部分。
-
长度为 \(1\)。最坏 \(S=\tt{X},T=\tt{Y}\),答案是 \(0\)。
-
长度为 \(2\)。若有一个相同,答案 \(\ge 1\);若都不相同即 \(S=\tt{XX},T=\tt{YY}\),可交换使答案为 \(2\)。
-
长度为 \(3\)。第一个和第三个若有一个相同,转化为 长度为 \(2\) 的加上 \(1\),答案显然 \(\ge 2\);都不相同,若第二个相同(\(S=\tt{XXX},T=\tt{YXY}\))或不相同(\(S=\tt{XXX},T=\tt{YYY}\)),均可答案 \(\ge 2\)。
考虑 \(r\) 是几。
-
\(r=0\) 显然 答案 \(\ge \lfloor \frac{2N}{3}\rfloor\)。
-
\(r=1\),\(1\times 2\) 对除 \(3\) 下取整没有影响。
-
\(r=2\),\(2\times 2\) 贡献一个 \(1\),正好。
即证。
lemma \(2\): 如果将两个数匹配,若设第一个是 \(S\) 的第 \(i\) 位,第二个 \(T\) 的第 \(j\) 位,那么 \(\mid i-j \mid\le \frac{N}{3}\)
证明:若不满足,上面的 lemma 就不满足了。
我们得到了一个 \(\frac{N}{3}\)。看来,\(2^{50\div 3}\) 凑好了?
(Small) Hint $2$
考虑状压 dp?
此后令 \(l=(N+2)/3+1\)。设计一个 dp 状态。设 \(dp_{i,msk}\) 代表 \(S,T\) 匹配到第 \(i\) 位了,现在没有用上的是 \(msk\)。
在转移之前,先弄清如何将一个 \(\tt{XY}\) 串 encode 成 \(01\) 串的 \(msk\)。考虑到:
-
答案要求在长度最长的基础下,字典序最小的。
-
\(01\) 串长度得记录,否则会出现不同字符串对应相同的 \(01\) 串。
现在有两个选择:
-
\(\texttt{X}=0,\texttt{Y}=1\),dp 时求 min。
-
\(\texttt{X}=1,\texttt{Y}=0\),dp 时求 max。
显然第二种更好处理长度的记录。为了让更长的长度更优,在最前面的一位放上一个 \(1\)。
一些栗子
字符串 | 对应的 \(msk\) |
---|---|
\(\tt{XXX}\) | \(1111\) |
\(\tt{YY}\) | \(100\) |
\(\tt{YX}\) | \(101\) |
lemma \(3\): 如果你要用 \(msk\) 里面的东西来匹配 \(i+1\),那么越往前的越优。
证明:显然的贪心。
这样我们来预处理一下对于每一个 \(msk\),现在要匹配一个 \(a\),加上一个 \(b\),会怎样。
具体实现
令 \(to_{msk,a,b}\) 是预处理的答案。
-
\(a=0\)。找到除了第一位第一个 \(0\),把前面的全删除,左移 \(1\),或上 \(b\)。
-
\(a=1\)。找到除了第一位第一个 \(1\),把前面的全删除,左移 \(1\),或上 \(b\)。
code
void init(){
// to_{msk,a,b}: you have msk, you want to get one a, you add b.
for (ll msk=1; msk<(1<<l); msk++){
ll len=0;
while ((msk^(1<<len))>(1<<len)){
len++;
}
len--;
ll cur=len;
while (~cur && (msk&(1ll<<cur))>0){
cur--;
}
if (~cur){
to[msk][0][0]=((msk&((1ll<<cur)-1))^(1ll<<cur));
to[msk][0][0]<<=1ll;
to[msk][0][1]=to[msk][0][0]^1;
}
cur=len;
while (~cur && (msk&(1ll<<cur))==0){
cur--;
}
if (~cur){
to[msk][1][0]=((msk&((1ll<<cur)-1))^(1ll<<cur));
to[msk][1][0]<<=1ll;
to[msk][1][1]=to[msk][1][0]^1;
}
}
}
万事具备,只欠东风!考虑 dp 怎么转移。
分三种情况:
-
\(S_i=\texttt{X},T_i=\texttt{X}\)。
-
\(S_i=\texttt{Y},T_i=\texttt{Y}\)。
-
\(S_i=\texttt{X},T_i=\texttt{Y}\) 或 \(S_i=\texttt{Y},T_i=\texttt{X}\)。
情况 \(1,2\):
-
可以这两个匹配。
-
可以找前面 \(msk\) 里面的匹配。
-
可以不匹配。
情况 \(3\):
-
匹配 \(\tt{X}\),\(\tt{Y}\) 加入 \(msk\)。
-
匹配 \(\tt{Y}\),\(\tt{X}\) 加入 \(msk\)。
-
可以不匹配。
初始:\(dp_{0,1}=1\)。
code
dp[0][1]=1;
for (int i=0; i<n; i++){
for (int msk=0; msk<(1ll<<l); msk++){
if (dp[i][msk]==0){
continue;
}
if (s[i]=='X' && t[i]=='X'){
// use this
dp[i+1][1]=max(dp[i+1][1],dp[i][msk]<<1ll|1);
if (to[msk][1][1]!=0){
// use before
dp[i+1][to[msk][1][1]]=max(dp[i+1][to[msk][1][1]],dp[i][msk]<<1ll|1);
}
if (msk<(1<<l-1)){
// no use
dp[i+1][msk<<1ll|1]=max(dp[i+1][msk<<1ll|1],dp[i][msk]);
}
}
else if (s[i]=='Y' && t[i]=='Y'){
// use this
dp[i+1][1]=max(dp[i+1][1],dp[i][msk]<<1ll);
if (to[msk][0][0]!=0){
// use before
dp[i+1][to[msk][0][0]]=max(dp[i+1][to[msk][0][0]],dp[i][msk]<<1ll);
}
if (msk<(1<<l-1)){
// no use
dp[i+1][msk<<1ll]=max(dp[i+1][msk<<1ll],dp[i][msk]);
}
}
else{
// use x
if (to[msk][1][0]!=0){
dp[i+1][to[msk][1][0]]=max(dp[i+1][to[msk][1][0]],dp[i][msk]<<1ll|1);
}
// use y
if (to[msk][0][1]!=0){
dp[i+1][to[msk][0][1]]=max(dp[i+1][to[msk][0][1]],dp[i][msk]<<1ll);
}
// no use
if (msk<(1<<l-1)){
dp[i+1][msk<<1ll]=max(dp[i+1][msk<<1ll],dp[i][msk]);
dp[i+1][msk<<1ll|1]=max(dp[i+1][msk<<1ll|1],dp[i][msk]);
}
}
}
}
输出答案 \(\max_{i=1}^{l-1} dp_{N,i}\) 即可。
完整 code
#include <bits/stdc++.h>
using namespace std;
#define de(x) cout<<#x<<"="<<x<<endl
using ll = long long;
const int N = 51;
const int M = 1<<18;
ll n,l;
string s,t;
ll to[M][2][2];
ll dp[N][M];
void init(){
// to_{msk,a,b}: you have msk, you want to get one a, you add b.
for (ll msk=1; msk<(1<<l); msk++){
ll len=0;
while ((msk^(1<<len))>(1<<len)){
len++;
}
len--;
ll cur=len;
while (~cur && (msk&(1ll<<cur))>0){
cur--;
}
if (~cur){
to[msk][0][0]=((msk&((1ll<<cur)-1))^(1ll<<cur));
to[msk][0][0]<<=1ll;
to[msk][0][1]=to[msk][0][0]^1;
}
cur=len;
while (~cur && (msk&(1ll<<cur))==0){
cur--;
}
if (~cur){
to[msk][1][0]=((msk&((1ll<<cur)-1))^(1ll<<cur));
to[msk][1][0]<<=1ll;
to[msk][1][1]=to[msk][1][0]^1;
}
}
}
void print(ll x){
ll len=0;
while ((x^(1ll<<len))>(1ll<<len)){
len++;
}
len--;
for (ll i=len; i>=0; i--){
int t=(x>>i)&1;
if (t){
cout<<"X";
}
else{
cout<<"Y";
}
}
cout<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>s>>t;
l=(n+2)/3+1;
init();
// push
dp[0][1]=1;
for (int i=0; i<n; i++){
for (int msk=0; msk<(1ll<<l); msk++){
if (dp[i][msk]==0){
continue;
}
if (s[i]=='X' && t[i]=='X'){
// use this
dp[i+1][1]=max(dp[i+1][1],dp[i][msk]<<1ll|1);
if (to[msk][1][1]!=0){
// use before
dp[i+1][to[msk][1][1]]=max(dp[i+1][to[msk][1][1]],dp[i][msk]<<1ll|1);
}
if (msk<(1<<l-1)){
// no use
dp[i+1][msk<<1ll|1]=max(dp[i+1][msk<<1ll|1],dp[i][msk]);
}
}
else if (s[i]=='Y' && t[i]=='Y'){
// use this
dp[i+1][1]=max(dp[i+1][1],dp[i][msk]<<1ll);
if (to[msk][0][0]!=0){
// use before
dp[i+1][to[msk][0][0]]=max(dp[i+1][to[msk][0][0]],dp[i][msk]<<1ll);
}
if (msk<(1<<l-1)){
// no use
dp[i+1][msk<<1ll]=max(dp[i+1][msk<<1ll],dp[i][msk]);
}
}
else{
// use x
if (to[msk][1][0]!=0){
dp[i+1][to[msk][1][0]]=max(dp[i+1][to[msk][1][0]],dp[i][msk]<<1ll|1);
}
// use y
if (to[msk][0][1]!=0){
dp[i+1][to[msk][0][1]]=max(dp[i+1][to[msk][0][1]],dp[i][msk]<<1ll);
}
// no use
if (msk<(1<<l-1)){
dp[i+1][msk<<1ll]=max(dp[i+1][msk<<1ll],dp[i][msk]);
dp[i+1][msk<<1ll|1]=max(dp[i+1][msk<<1ll|1],dp[i][msk]);
}
}
}
}
ll mx=0;
for (int msk=1; msk<(1ll<<l); msk++){
mx=max(mx,dp[n][msk]);
}
print(mx);
return 0;
}
submission(有一些调试用的注释)