題意[soj187]
有一個以 11 開頭,00 結尾的 0101 序列(即只包含 00 和 11 的序列)SS,令 f(S)f(S) 表示序列 SS 中包含的 1010 交錯的子序列的個數,1010 交錯子序列是指 11 和 00 交錯出現且第一個字符是 11 最後一個字符是 00 的子序列(即形如 1010⋯101010⋯10 的子序列)。
例如 f(1100)=4f(1100)=4,因爲有 44 個 1010 交錯子序列,他們的位置分別爲 {1,3},{1,4},{2,3},{2,4}{1,3},{1,4},{2,3},{2,4}。f(1010)=4f(1010)=4,他們的位置分別爲 {1,4},{1,2},{3,4},1,2,3,4{1,4},{1,2},{3,4},1,2,3,4。
現在給定 SS,你需要通過修改 SS 中的某些位置的字符(把 11 改爲 00 或把 00 改爲 11)得到 TT,使得 TT 也是一個以 11 開頭,00 結尾的 0101 序列,且 f(T)=Xf(T)=X。求是否有解,如果有解,輸出需要至少修改幾個字符。
看到32的數據,就應該想到折半搜索啊!!!
折半搜索難度在於如何合併兩段
考慮如何合併兩段
我們分別統計兩段末尾/首位爲0/1的(01間隔)子序列的數量
構成一段當且僅當前一段末尾爲0後一段首位爲1或前一段末尾爲1後一段首位爲0;
因爲通過暴力搜索可知第一段以末尾爲0/1的(01間隔)子序列的數量不超過1597,
我們可以用f[i][j]記錄以末尾爲0/1的(01間隔)子序列的數量爲i/j時最少要使第一段轉換f[i][j]次
如果要符合條件要在第二段找到對應a,b使 ib+ja=k+1(注意要多爲空的一種情況)
這是在第二段每次搜索到一組a,b時使用拓展歐幾里得定理[據說這也是取逆最使用的方式](注意此時對a,b公約數不是1的情況的處理,因爲和爲k+1而不是1)快速解出滿足條件的ij可能解,比較更新值即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
char s[100];
long long T,len,k,len1,len2,ans,f[2000][2000];
void dfs1(long long w,int t0,int t1,long long c)
{ if (w==len1) { f[t0][t1]=min(f[t0][t1],c); return;}
dfs1(w+1,t0+t1,t1,c+(s[w]=='1'));
dfs1(w+1,t0,t0+t1,c+(s[w]=='0'));
}
int exgcd(int x0,int y0,int &x,int &y)
{
if (!y0) {x=1; y=0;return x0;}
int x1,y1;
int u=exgcd(y0,x0%y0,x1,y1);
x=y1;
y=x1-y1*(x0/y0);
return u;
}
void dfs2(int w,int t0,int t1,int c)
{ if (w==len2)
{ long long x=0,y=0; long long ww=exgcd(t0,t1,x,y);
// cout<<t0<<' '<<t1<<' '<<x<<' '<<y<<endl;
if (k%ww) return;
x=k/ww*x;
x=x%(t1/ww);
if (x<0) x+=t1;
long long min1=1e9;
for (;x<=1597;x=x+(t1/ww))
{ y=(k-x*t0)/t1;
// cout<<x<<' '<<y<<endl;
if (y>=1 && y<=1597) {min1=min(min1,f[y][x]); //cout<<f[y][x]<<endl;
}
}
ans=min(min1+c,ans);
return;}
dfs2(w-1,t0+t1,t1,c+(s[w]=='1'));
dfs2(w-1,t0,t0+t1,c+(s[w]=='0'));
}
signed main()
{ cin>>T;
while (T--)
{ memset(f,0x3f,sizeof(f));
cin>>s; cin>>k; k++;
len=strlen(s);
len1=len/2; len2=len1-1;
ans=1e9;
dfs1(1,1,1,0);
dfs2(len-2,1,1,0);
if (ans==1e9) cout<<"NO"<<endl; else {cout<<"YES"<<endl; cout<<ans<<endl;}
}
}
接着說一種神奇的解法
考慮一個暴力的做法,枚舉T,f[i][0/1]表示到i這個位置,以1開頭,0/1結尾子序列有多少個。顯然當f[n+1][0]=m+1的T滿足條件。
而我們現在已經知道了X,由於前面DP的轉移是唯一的,因此我們只需要枚舉f[n+1][1]即可倒推得到整個f數組,進而得知T。
爲什麼可以推?
因爲考慮 第i+1位爲1 f[i+1][1]=f[i][0]+f[i][1]; f[i+1][0]=f[i+1][0] ( f[i+1][1]>f[i+1][0]) 否則 ( f[i+1][1]<f[i+1][0]) 通過比較大小就可知這一位是0/1 ,且由此可知f[n+1][1]<f[n+1][0]
代碼:
#include<bits/stdc++.h>
using namespace std;
inline int read() {
register char ch;
while(!isdigit(ch=getchar()));
register int x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return x;
}
const int N=33;
char s[N];
int f[2];
int main() {
for(register int T=read();T;T--) {
scanf("%s",s);
const int n=strlen(s),m=read();
for(register int i=0;i<n;i++) s[i]-='0';
int ans=INT_MAX;
for(register int i=1;i<=m;i++) {
f[0]=m+1;
f[1]=i;
int tmp=0;
for(register int i=n-1;i>=1;i--) {
if(f[0]>=f[1]) {
f[0]-=f[1];
tmp+=s[i];
} else {
f[1]-=f[0];
tmp+=!s[i];
}
}
if(f[0]==1&&f[1]==1) ans=std::min(ans,tmp);
}
if(ans==INT_MAX) {
puts("NO");
continue;
}
puts("YES");
printf("%d\n",ans);
}
return 0;
}
時間複雜度O(nm)
再補充一道折半搜索
題意:
作爲品茶的消遣,貝倫正在解一道簡單的謎題。
給出一個長度爲 nn 的數列 AiAi,問是否能將這個數列分解爲兩個長度爲 n2n2 的子序列,滿足
- 兩個子序列不互相重疊。
- 兩個子序列中的數要完全一樣,{1,2}={1,2},{1,2}≠{2,1}{1,2}={1,2},{1,2}≠{2,1}。
輸入格式
第一行包含一個正整數 TT,表示數據組數。
每組數據的第一行,包含一個正整數 nn,第二行包含 nn 個正整數 AiAi。
輸出格式
對於每組數據輸出一行,如果可以完成,輸出 Frederica bernkastel,否則輸出 Furude Rika。
分別將數分到兩組,合併時多餘部分用hash判斷
#include<bits/stdc++.h>
using namespace std;
const int maxn=45, mod=1e9+7,s1=3100;
int T,l,r,n;
int a[maxn];
int b[maxn], c[maxn];
set<pair<int, int> >s;
void dfs(int k)
{
if (k==n/2+1)
{ int v=0;
if (l<r) for (int i=l;i<=r-1;i++) v=(v*s1+c[i])%mod;
else for (int i=r;i<=l-1;i++) v=(v*s1+b[i])%mod;
s.insert(make_pair(abs(r-l),v));
return;
}
b[l]=a[k];
if (l>=r || b[l]==c[l]) l++,dfs(k+1),l--;
c[r]=a[k];
if (l<=r || b[r]==c[r]) r++,dfs(k+1),r--;
}
void dfs2(int k)
{
// cout<<k<<' '<<l<<' '<<r<<endl;
if (k==n/2)
{ int v=0;
if (l<r) for (int i=r-1;i>=l;i--) v=(v*s1+c[i])%mod;
else for (int i=l-1;i>=r;i--) v=(v*s1+b[i])%mod;
// cout<<v<<endl;
if (s.count(make_pair(abs(l-r),v))) throw 0;
return;
}
b[l]=a[k];
if (l>=r || b[l]==c[l]) l++,dfs2(k-1),l--;
c[r]=a[k];
if (l<=r || b[r]==c[r]) r++,dfs2(k-1),r--;
}
int main()
{
cin>>T;
while (T--)
{
cin>>n;
s.clear();
for (int i=1;i<=n;i++)
{ cin>>a[i];}
b[1]=a[1];
l=2;r=1;
dfs(2);
l=r=1;
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
try{dfs2(n);}
catch(...){ puts("Frederica Bernkastel"); continue; }
puts("Furude Rika");
}
}