[折半搜索]or [dp逆推] [[CC-XYHUMOQ]A humongous Query/01 序列 and 貝倫卡斯泰露

題意[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");
   }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章