【Codeforces】Codeforces Round #748 (Div. 3) F. Red-Black Number | 动态规划

174 阅读2分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

【Codeforces】Codeforces Round #748 (Div. 3) F. Red-Black Number | 动态规划

题目链接

Problem - 1593F - Codeforces

题目

image.png

题目大意

给一串长度为 nn 的数字,从中选择 rr 个涂成红色,剩下的没被选择的 bb 个涂成黑色,要求满足涂成红色的数字顺次链接形成的大整数是 AA 的倍数,涂成黑色的数字顺次链接形成的大整数是 BB 的倍数,输出最小化 rbr-b 的绝对值的染色方案。

思路

动态规划。

设状态 dp[i][j][x][y]dp[i][j][x][y] 表示前 ii 个数字我们把 jj 个涂成了黑色,对 AA 取余是 xx,对 BB 取余是 yy 的答案是否存在,1 表示存在,0 表示不存在。
ans[i][j][x][y]ans[i][j][x][y] 表示前 ii 个数字我们把 jj 个涂成了黑色,对 AA 取余是 xx,对 BB 取余是 yy 的染色方案。

如果存在前 ii 个数字我们把 jj 个涂成了黑色,对 AA 取余是 xx,对 BB 取余是 yy 的染色方案,即 dp[i][j][x][y]=1dp[i][j][x][y]=1,我们只需要考虑第 i+1i+1 个格子染什么颜色:

  • 如果染成红色,则前 i+1i+1 个格子被染成黑色的格子数量为 jj,且对 AA 取余的值变成了 (x×10+a[i+1])modA(x\times 10+a[i+1])\mod A,对 BB 取余的值还是 yy
  • 如果染成黑色,则前 i+1i+1 个格子被染成黑色的格子数量为 j+1j+1,且对 BB 取余的值变成了 (y×10+a[i+1])modB(y\times 10+a[i+1])\mod B,对 AA 取余的值还是 xx

所以转移方程为:

  • dp[i+1][j][(x10+a[i+1])modA][y]=1dp[i+1][j][(x*10+a[i+1])\mod A][y]=1
    ans[i+1][j][(x10+a[i+1])modA][y]=(ans[i][j][x][y]<<1);ans[i+1][j][(x*10+a[i+1])\mod A][y]=(ans[i][j][x][y]<<1);
  • dp[i+1][j+1][x][(y10+a[i+1])modB]=1;dp[i+1][j+1][x][(y*10+a[i+1])\mod B]=1;
    ans[i+1][j+1][x][(y10+a[i+1])modB]=(ans[i][j][x][y]<<11);ans[i+1][j+1][x][(y*10+a[i+1])\mod B]=(ans[i][j][x][y]<<1|1);

最后我们只需要检查所有的合法解,即在前 nn 个格子,对 AA 取余是 00,对 BB 取余是 00 的条件下,最小的红黑格子数量之差。输出最优解记录的染色方案即可。

代码

#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;
}

其他

一看到这个题第一反应是折半搜索,于是用以下思路开始写代码:

  • 先将整个数组划分成两个部分,前半部分长度 n1n_1,后半部分长度 n2n_2。求出前半部分和后半部分对 AA 取余是 xx,对 BB 取余是 yy,用 mapmap 存储红色格子数量减去黑色格子数量是 kk 的划分方案,用 long long 类型变量按二进制位存储染色的红或黑。
  • 为了将前后半段合并在一起,枚举前半部分对 AA 取余是 i1i_1,对 BB 取余是 j1j_1,第二段染成红色的长度是 lenlen,则染成黑色的长度是 n2lenn_2-len,可以计算出:
    第二段对 AA 取余的结果应为 i2=(Ai110lenmodA)modAi_2=(A-i_1*10^{len}\mod A)\mod A
    第二段对 BB 取余的结果应为 j2=(Bj110n2lenmodB)modBj_2=(B-j_1*10^{n_2-len}\mod B)\mod B
    如果存在合法的第二段划分,再枚举第一段合法划分的红色格子数量减去黑色格子数量,更新答案。

提交 T17,计算了时间复杂度发现 1e81e8 基本跑满,遂换思路。

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;
}