[折半搜索]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");
   }
}

 

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