本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
【Codeforces】Codeforces Round #748 (Div. 3) F. Red-Black Number | 动态规划
题目链接
题目
题目大意
给一串长度为 的数字,从中选择 个涂成红色,剩下的没被选择的 个涂成黑色,要求满足涂成红色的数字顺次链接形成的大整数是 的倍数,涂成黑色的数字顺次链接形成的大整数是 的倍数,输出最小化 的绝对值的染色方案。
思路
动态规划。
设状态 表示前 个数字我们把 个涂成了黑色,对 取余是 ,对 取余是 的答案是否存在,1 表示存在,0 表示不存在。
表示前 个数字我们把 个涂成了黑色,对 取余是 ,对 取余是 的染色方案。
如果存在前 个数字我们把 个涂成了黑色,对 取余是 ,对 取余是 的染色方案,即 ,我们只需要考虑第 个格子染什么颜色:
- 如果染成红色,则前 个格子被染成黑色的格子数量为 ,且对 取余的值变成了 ,对 取余的值还是
- 如果染成黑色,则前 个格子被染成黑色的格子数量为 ,且对 取余的值变成了 ,对 取余的值还是
所以转移方程为:
最后我们只需要检查所有的合法解,即在前 个格子,对 取余是 ,对 取余是 的条件下,最小的红黑格子数量之差。输出最优解记录的染色方案即可。
代码
#include <bits/stdc++.h>
using namespace std;
using LL=long long;
const LL mod=1000000007;
int n,A,B;
int a[41],dp[41][41][41][41];
LL ans[41][41][41][41];
int solve()
{
cin>>n>>A>>B;
memset(dp,0,sizeof(dp));
memset(ans,0,sizeof(ans));
for (int i=1;i<=n;++i) scanf("%1d",&a[i]);
dp[0][0][0][0]=1;
for (int i=0;i<n;++i)
for (int j=0;j<=i;++j)
for (int x=0;x<A;++x)
for (int y=0;y<B;++y)
{
if (!dp[i][j][x][y]) continue;
//i+1 给R
dp[i+1][j][(x*10+a[i+1])%A][y]=1;
ans[i+1][j][(x*10+a[i+1])%A][y]=(ans[i][j][x][y]<<1);
//i+1 给B
dp[i+1][j+1][x][(y*10+a[i+1])%B]=1;
ans[i+1][j+1][x][(y*10+a[i+1])%B]=(ans[i][j][x][y]<<1|1);
}
int minn=n;
LL outpt=-1;
for (int i=1;i<n;++i)
if (dp[n][i][0][0]&&(minn>=abs(n-i-i)))
{
minn=abs(n-i-i);
outpt=ans[n][i][0][0];
}
if (outpt==-1) printf("-1");
else for (int i=n-1;i>=0;--i)
if (outpt&(1ll<<i)) printf("B");
else printf("R");
cout<<"\n";
return 0;
}
int main()
{
int T=1;
for (scanf("%d",&T);T--;) solve();
return 0;
}
其他
一看到这个题第一反应是折半搜索,于是用以下思路开始写代码:
- 先将整个数组划分成两个部分,前半部分长度 ,后半部分长度 。求出前半部分和后半部分对 取余是 ,对 取余是 ,用 存储红色格子数量减去黑色格子数量是 的划分方案,用 long long 类型变量按二进制位存储染色的红或黑。
- 为了将前后半段合并在一起,枚举前半部分对 取余是 ,对 取余是 ,第二段染成红色的长度是 ,则染成黑色的长度是 ,可以计算出:
第二段对 取余的结果应为
第二段对 取余的结果应为
如果存在合法的第二段划分,再枚举第一段合法划分的红色格子数量减去黑色格子数量,更新答案。
提交 T17,计算了时间复杂度发现 基本跑满,遂换思路。
TLE代码如下:
#include <bits/stdc++.h>
using namespace std;
using LL=long long;
typedef pair<int,int> PII;
const int N=500001;
const LL mod=1000000007;
int n,A,B;
int a[41];
unordered_map<int,LL> dp[2][41][41];
int md[41][41];
int ed,o;
void dfs(LL S,int r,int b,LL aa,LL bb,int t)
{
if (t==ed)
{
dp[o][aa][bb][r-b]=S;
return;
}
dfs(S<<1,r+1,b,(aa*10+a[t])%A,bb,t+1);
dfs(S<<1|1,r,b+1,aa,(bb*10+a[t])%B,t+1);
}
int solve()
{
cin>>n>>A>>B;
for (int i=0;i<=40;++i)
for (int j=0;j<=40;++j)
{
dp[0][i][j].clear();
dp[1][i][j].clear();
}
for (int i=1;i<=n;++i) scanf("%1d",&a[i]);
ed=n/2+1;
o=0;
dfs(0,0,0,0,0,1);
ed=n+1;
o=1;
dfs(0,0,0,0,0,n/2+1);
LL ans=-1,minn=n,t;
for (int i1=0,i2,j2,x;i1<A;++i1)
for (int j1=0;j1<B;++j1)
{
if (!dp[0][i1][j1].size()) continue;
for (int len2R=0;len2R<=n-n/2;++len2R)
{
i2=(A-i1*md[len2R][A]%A)%A;
j2=(B-j1*md[n-n/2-len2R][B]%B)%B;
if (!dp[1][i2][j2].count(len2R-(n-n/2-len2R))) continue;
t=dp[1][i2][j2][len2R-(n-n/2-len2R)];
for (auto k1:dp[0][i1][j1])
if (abs(k1.first+(len2R-(n-n/2-len2R)))<minn)
{
ans=k1.second<<(n-n/2)|t;
minn=abs(k1.first+(len2R-(n-n/2-len2R)));
}
}
}
if (ans==-1) printf("-1");
else for (int i=n-1;i>=0;--i)
if ((ans>>i)&1) printf("B");
else printf("R");
printf("\n");
}
int main()
{
int T=1;
for (int i=1;i<=40;++i) md[0][i]=1%i;
for (int i=1;i<=40;++i)
for (int j=1;j<=40;++j) md[i][j]=md[i-1][j]*10%j;
for (scanf("%d",&T);T--;) solve();
return 0;
}