练习题题解 CF1051D/CF196D

练习题题解

第1题 连通块 DP CF 1051 D

题目链接

给定一个 \(2\)\(n\) 列的矩阵 \(a\) ,你可以在矩阵中填数,任选第 \(i\) 行第 \(j\) 列,可以填 \(1\)\(0\) ,即可以使 \(a[i,j]=0\) 或使 \(a[i,j]=1\) ,填满矩阵 \(a\) 后,求连通块数目恰好等于 \(k\) 填数的方案数。

思路

直接搜索会超时,考虑DP。

状态设计\(f_{i,j,u}\) 表示考虑前 \(i\) 列,有 \(j\) 个连通块,且第 \(i\) 列的状态为 \(u\) 的方案数。

一共有 \(4\) 种状态,我们对其进行编码。

 状态0   状态1   状态2   状态3
  0		  0		 1		 1
  0		  1      0		 1

那么答案可以表示为:\(f_{n,k,0}+f_{n,k,1}+f_{n,k,2}+f_{n,k,3}\)

考虑根据不同状态进行转移:

以当前状态为 \(0\) 举例:

前一列状态为 \(0\)

0 0
0 0

连通块数量不会增加。即 \(f_{i-1,j,0}\)

前一列状态为 \(1\)

0 0
1 0

连通块数量不会增加。即 \(f_{i-1,j,1}\)

前一列状态为 \(2\)

1 0
0 0

连通块数量不会增加。即 \(f_{i-1,j,2}\)

前一列状态为 \(3\)

1 0
1 0

连通块数量增加 \(1\)。即 \(f_{i-1,j-1,3}\)

所以状态转移为:\(f_{i,j,0}=f_{i-1,j,0}+f_{i-1,j,1}+f_{i-1,j,2}+f_{i-1,j-1,3}\)

其余情况同理转移即可。

初始化

算出 \(4\) 种状态对应的联通块个数即可。

\(f_{1,1,0}=f_{1,2,1}=f_{1,2,2}=f_{1,3,1}=1\)

代码

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

const int N = 1010;
const int mod = 998244353;

i64 f[N][2*N][4];

void Showball(){
    int n,k;
    cin>>n>>k;
    f[1][1][0]=f[1][2][1]=f[1][2][2]=f[1][1][3]=1;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=k;j++){
            f[i][j][0]=(f[i-1][j][0]+f[i-1][j][1]+f[i-1][j][2]+f[i-1][j-1][3])%mod;
            f[i][j][1]=(f[i-1][j-1][0]+f[i-1][j][1]+f[i-1][max(0,j-2)][2]+f[i-1][j-1][3])%mod;
            f[i][j][2]=(f[i-1][j-1][0]+f[i-1][max(0,j-2)][1]+f[i-1][j][2]+f[i-1][j-1][3])%mod;
            f[i][j][3]=(f[i-1][j-1][0]+f[i-1][j][1]+f[i-1][j][2]+f[i-1][j][3])%mod;
        }
    }     
    auto &t=f[n][k];
    cout<<(t[0]+t[1]+t[2]+t[3])%mod<<"\n";
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

第2题 最小字典序 哈希+贪心 CF 196D

题目链接

给一个数字 \(d\) 和字符串 \(s\) ,求字符串 \(t\) 满足:

\(s\)\(t\) 等长
\(t\) 的字典序大于 \(s\) 且尽可能小
\(t\) 中不包含长度 \(\ge d\) 的回文串

思路:

首先,需要满足不包括长度 \(\ge d\) 的回文串,那么我们只需要判断不包括长度为 \(d\)\(d+1\) 的即可。

因为长度 \(\ge d+2\) 的回文串中一定包含了长度为 \(d\) 的回文子串。

我们先求出 \(s\) 对应的字典序的下一个字符串。接着一位一位进行贪心即可。

每次判断是否存在回文,回文的判断可以维护一下正反哈希值进行解决。

那么整个过程就涉及到 修改一个字符维护正反哈希值 两个操作。

有两种实现方法:

第一种(不需要动脑推公式,时间复杂度多一个 \(log\))。

直接套用 这题 的代码即可。

线段树维护正反哈希值即可。

第二种 (稍微动脑推一下公式,代码好写,时间复杂度更优)。

注意正反哈希值都需要正序枚举开始维护,这样方便我们修改字符后,维护哈希值,不会对后续造成影响。

注意:区间\([l,r]\) 的反哈希值由于需要除以 \(P^{l-1}\)。但是由于我们使用 \(unsigned\ long\ long\) 本质上是对 \(2^{64}\) 取模。

因此,这里不能使用乘法,而应使用乘法逆元。为了方便,我们直接把 \(P^{l-1}\) 乘到正哈希值上,再进行比较即可。

代码一:

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

typedef unsigned long long ULL;

const int N = 4e5+10;
ULL p[N];
string s,ans;
//线段树
struct node{
    int l,r;
    ULL h1,h2;//正反哈希值
    node operator+(const node &u)const{
      node ans;
      ans.l=l,ans.r=u.r;
      ans.h1=h1*p[u.r-u.l+1]+u.h1;
      ans.h2=u.h2*p[r-l+1]+h2;
      return ans;
    }
    
}tr[N*4];
#define ls u<<1
#define rs u<<1|1

void pushup(int u){
    tr[u]=tr[ls]+tr[rs];
}

void build(int u,int l,int r){
    tr[u].l=l,tr[u].r=r;
    if(l==r){
        tr[u].h1=tr[u].h2=ans[l];
        return;
    }
    int mid=l+r>>1;
    build(ls,l,mid),build(rs,mid+1,r);
    pushup(u);
}

void modify(int u,int x,int v){//单点修改
    if(tr[u].l==tr[u].r) tr[u].h1=tr[u].h2=v;
    else{
        int mid=tr[u].l+tr[u].r>>1;
        if(x<=mid) modify(ls,x,v);
        else modify(rs,x,v);
        pushup(u);
    }
}

node query(int u,int l,int r){//区间查询
    if(l<=tr[u].l&&tr[u].r<=r){
        return tr[u];
    }
    int mid=tr[u].l+tr[u].r>>1;
    if(r<=mid) return query(ls,l,r);
    if(l>mid) return query(rs,l,r);
    return query(ls,l,r)+query(rs,l,r); 
}

int d,n;

bool check(int x){
    if(x>=d){
        node u=query(1,x-d+1,x);
        if(u.h1==u.h2) return true;
    }
    if(x>d){
        node u=query(1,x-d,x);
        if(u.h1==u.h2) return true;
    }
    return false;
}

bool dfs(int x,int st){
    if(x>n){
        return true;
    }
    for(char c=(st?s[x]:'a');c<='z';c++){
        modify(1,x,c);
        ans[x]=c;
        if(check(x)) continue;
        if(dfs(x+1,st&&s[x]==ans[x])) return true;
    }
    return false;
}

void Showball(){
    cin>>d>>s;
    n=s.size();
    s="?"+s;
    p[0]=1;
    for(int i=1;i<=n;i++) p[i]=p[i-1]*131;
    for(int i=n;i>=1;i--){
        if(s[i]=='z') s[i]='a';
        else{
            s[i]++;
            break;
        }
        if(i==1) return cout<<"Impossible\n",void();
    }
    ans=s;
    build(1,1,n);
    if(dfs(1,1)) cout<<ans.substr(1)<<"\n";
    else cout<<"Impossible\n",void();
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

代码二:

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

struct Hash{
   typedef unsigned long long ULL;
   const ULL P=131;
   vector<ULL> p,h,t;

   void init(string s){
      int n=(int)s.size()-1;
      p.assign(n+1,0);
      h.assign(n+1,0);
      t.assign(n+1,0);
      p[0]=1;
      for(int i=1;i<=n;i++){
         p[i]=p[i-1]*P;
         h[i]=(h[i-1]*P+s[i]-'0'+1);
         t[i]=(t[i-1]+p[i-1]*(s[i]-'0'+1));
      }
   }

   ULL getHash(int l,int r){
      return (h[r]-(h[l-1]*p[r-l+1]));
   }

   ULL getRevHash(int l,int r){
      return (t[r]-t[l-1]);
   }

   bool check(int l,int r){
        return getHash(l,r)*p[l-1]==getRevHash(l,r);
   }

}Hash;

int d,n;
string s,ans;
bool check(int x){
    if(x>=d&&Hash.check(x-d+1,x)) return true;
    if(x>d&&Hash.check(x-d,x)) return true;
    return false;
}

bool dfs(int x,int st){
    if(x>n){
        return true;
    }
    for(char c=(st?s[x]:'a');c<='z';c++){
        ans[x]=c;
        //更新哈希值
        Hash.h[x]=(Hash.h[x-1]*Hash.P+c-'0'+1);
        Hash.t[x]=(Hash.t[x-1]+Hash.p[x-1]*(c-'0'+1));
        if(check(x)) continue;
        if(dfs(x+1,st&&s[x]==ans[x])) return true;
    }
    return false;
}

void Showball(){
    cin>>d>>s;
    n=s.size();
    s="?"+s;
    for(int i=n;i>=1;i--){
        if(s[i]=='z') s[i]='a';
        else{
            s[i]++;
            break;
        }
        if(i==1) return cout<<"Impossible\n",void();
    }
    ans=s;
    Hash.init(ans);
    if(dfs(1,1)) cout<<ans.substr(1)<<"\n";
    else cout<<"Impossible\n",void();
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}
posted @ 2025-05-04 12:01  Showball  阅读(36)  评论(0)    收藏  举报