练习题题解 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;
}