DP problems (3)
Codeforces Round 893 (Div. 2) D
题意
有一个长度为 n n n的 01 01 01串,反转其中一个元素( 0 0 0变为 1 1 1,或者 1 1 1变为 0 0 0)称为一次操作,我们可以对它进行不超过 k k k次操作, l 0 l_0 l0表示字符串中最长的连续的 0 0 0的长度, l 1 l_1 l1表示字符串中最长的连续的 1 1 1的长度,我们需要输出 n n n个数,第 i i i个表示对原串操作后 i ∗ l 0 + l 1 i*l_0+l_1 i∗l0+l1的最大值。
解法
经过分析发现,结果中会存在某个分界点,分界点左边全为
0
0
0变为
1
1
1的操作,右边全为
1
1
1变为
0
0
0的操作(或左边全为
1
1
1变为
0
0
0的操作,右边全为
0
0
0变为
1
1
1操作),于是我们用
p
r
e
[
i
]
[
j
]
[
k
]
pre[i][j][k]
pre[i][j][k]表示前
j
j
j个数进行不超过
k
k
k次操作后连续的
i
i
i的最长的长度,
s
u
f
[
i
]
[
j
]
[
k
]
suf[i][j][k]
suf[i][j][k]表示后缀。对于要输出的第
i
i
i个数,我们二重循环枚举分界点和操作次数,得到一个
O
(
n
3
)
O(n^3)
O(n3)的算法。
我们需要再进行优化,可以发现答案只与
l
0
l_0
l0和
l
1
l_1
l1有关,我们并不关心分界点的位置,于是我们处理出中间数组
f
f
f,
f
[
i
]
f[i]
f[i]表示
l
0
l_0
l0为
i
i
i时,
l
1
l_1
l1最大为多少,于是对于要输出的第
i
i
i个数,我们只需要枚举
l
0
l_0
l0即可。此时的复杂度为
O
(
n
2
)
O(n^2)
O(n2)。注意不要#define int long long
,这样会MLE,不过下面的方法不会。
还有一种计算
f
f
f 数组的方法,我们二重循环枚举最长的连续的
0
0
0的起始位置再用
p
r
e
pre
pre和
s
u
f
suf
suf数组
O
(
1
)
O(1)
O(1)求出此时的
l
1
l_1
l1。
inline void solve(){
cin>>n>>k;
string s;cin>>s;s=" "+s;
for(int i=1;i<=n;i++)a[i]=s[i]-'0';
for(int i=1;i<=n;i++)a[i]+=a[i-1];
for(int j=0;j<=k;j++){
int l=1;
for(int i=1;i<=n;i++){//1->0
while(l<=i&&a[i]-a[l-1]>j)l++;
pre[0][i][j]=max(pre[0][i-1][j],i-l+1);
}
l=1;
for(int i=1;i<=n;i++){//0->1
while(l<=i&&i-l+1-(a[i]-a[l-1])>j)l++;
pre[1][i][j]=max(pre[1][i-1][j],i-l+1);
}
}
for(int j=0;j<=k;j++){
suf[0][n+1][j]=suf[1][n+1][j]=0;
int r=n;
for(int i=n;i>=1;i--){//1->0
while(r>=i&&a[r]-a[i-1]>j)r--;
suf[0][i][j]=max(suf[0][i+1][j],r-i+1);
}
r=n;
for(int i=n;i>=1;i--){//0->1
while(r>=i&&r-i+1-(a[r]-a[i-1])>j)r--;
suf[1][i][j]=max(suf[1][i+1][j],r-i+1);
}
}
vector<int>f(n+1,-inf);
for(int j=0;j<=k;j++){
for(int i=0;i<=n;i++){
for(int t=0;t<=1;t++){
int len1=pre[t][i][j],len2=suf[t^1][i+1][k-j];
if(!t)f[len1]=max(f[len1],len2);
else f[len2]=max(f[len2],len1);
}
}
}
for(int i=n-1;i>=0;i--)f[i]=max(f[i],f[i+1]);
for(int j=1;j<=n;j++){
ans[j]=0;
for(int i=0;i<=n;i++){
ans[j]=max(ans[j],j*i+f[i]);
}
}
for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
cout<<'\n';
}
Educational Codeforces Round 153 (Rated for Div. 2) D
题意
当一个 01 01 01串中子序列 01 01 01的数量等于子序列 10 10 10的数量时,我们称它是平衡的,交换字符串中的两个元素称为一次操作,我们要求出使当前字符串变成平衡的所需要的最少操作次数。
解法
容易知道只有
0
0
0和
1
1
1交换才有用,我们可以把一次操作看为同时反转两个不同位置的字符,但是这样不好写
d
p
dp
dp。
我们观察到操作时,
0
0
0和
1
1
1的个数是不变的,我们不妨定义新操作为只反转一个位置的元素,最后通过限制操作之后
0
0
0或
1
1
1的个数来求出结果,最后得到的操作数除以
2
2
2便是答案。所以我们定义
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]表示前
i
i
i位,有
j
j
j个
1
1
1,
01
01
01比
10
10
10多
k
个
k个
k个时的最少操作数即可,答案为
d
p
[
n
]
[
c
n
t
1
]
[
0
+
d
l
t
]
/
2
dp[n][cnt1][0+dlt]/2
dp[n][cnt1][0+dlt]/2(其中
c
n
t
1
cnt1
cnt1表示原串中
1
1
1的个数,
d
l
t
dlt
dlt表示偏移量)。注意需要滚动优化掉第一维,
k
k
k可能为负数,需要加一个偏置量。
Educational Codeforces Round 154 (Rated for Div. 2) E
题意
给出
n
n
n和
k
k
k,定义一个数组的花费如下:
将数组分为若干子数组,每个数属于最多一个子数组,所有划分方案中是
k
k
k的排列的子数组的最大数量被称为这个数组的花费。
求出所有长度为
n
n
n且元素的值在
1
1
1到
k
k
k之间的数组的花费之和。
解法
- 方法一
定义 d p [ i ] [ j ] [ t ] dp[i][j][t] dp[i][j][t]表示长度为 i i i的数组结尾有 j j j个互不相同的数,且前 i − j i-j i−j个数的花费是 t t t(这个dp数组只有 O ( n 2 ) O(n^{2}) O(n2)个状态),转移的时候用差分优化可以做到 O ( n 2 ) O(n^{2}) O(n2)的复杂度
void solve() {
int n,k;cin>>n>>k;
vector<vector<vector<int>>>dp(n+1,vector(k+1,vector(n/k+2,0)));
dp[0][0][0] = 1;
for(int i=0;i<n;i++) {
for(int t = 0;t<=i/k;t++) {
vector<int>tmp(k+1,0);
for(int j=0;j<k;j++) {
tmp[j]=(tmp[j]+dp[i][j][t])%mod;
if(j==k-1)dp[i+1][0][t+1]=(dp[i+1][0][t+1]+dp[i][j][t])%mod;
else dp[i+1][j+1][t]=(dp[i+1][j+1][t]+dp[i][j][t]*(k-j)%mod)%mod;
}
for(int j = k - 1; j >= 1; j--) {
dp[i+1][j][t]=(dp[i+1][j][t]+tmp[j])%mod;
tmp[j - 1]=(tmp[j-1]+tmp[j])%mod;
}
}
}
int ans=0;
for(int j=0;j<k;j++) {
for(int t=0;t<=n/k;t++) {
ans=(ans+t*dp[n][j][t]%mod)%mod;
}
}
cout<<ans<<'\n';
}
- 方法二
link