火柴棍等式 ,但是数据范围有一点大……
题目描述
给你
注意:
- 加号与等号各自需要两根火柴棍;
- 如果
,则 与 视为不同的等式( ); 根火柴棍必须全部用上。
原题中
当然,方案数(也许)会相当多,为了不写高精,我们可以将答案对某个质数取模(比如
定个小目标,看看能不能做到
由于
尝试搜索,首先去除加号与等于号(将
想想我们需要知道什么:当前搜索到的深度(即从低到高的位数),上一位是否有进位,当前用了多少火柴棍。
容易写出简单的代码。
int cnt[]={6,2,5,5,4,5,6,3,7,6};
void dfs(int k,int carry,int sum){//k :当前考虑的位置,carry :上一位的进位,sum :还未用的火柴棍数量
//calc the answer
for(int a=0;a<=9;a++)
for(int b=0;b<=9;b++){//枚举A,B的第 k 位所填的数
int tmp=a+b+carry;
int c=tmp/10,new_carry=tmp/10;
dfs(k+1,new_carry,sum-cnt[a]-cnt[b]-cnt[c]);
}
}
但这个代码会有些问题:当
由此,我们可以在填到某个位置后钦定
每次填数后,考虑
这样一来,统计答案变得十分方便:当
int ans;
int cnt[]={6,2,5,5,4,5,6,3,7,6};
void dfs(int k,int carry,bool cuta,bool cutb,int sum){//cuta,cutb:a,b是否被截断
if(cuta&&cutb){
if((!carry&&sum==0)||(carry&&sum==2))ans++;
return;
}
for(int a=0;a<=9;a++){
if(cuta&&a!=0)break;
for(int b=0;b<=9;b++){
if(cutb&&b!=0)break;
int tmp=a+b+carry;
int c=tmp/10,new_carry=tmp/10;
int new_sum=sum;
if(!cuta)new_sum-=cnt[a];
if(!cutb)new_sum-=cnt[b];
new_sum-=cnt[c];
if(new_sum<0)continue;
if(!cuta&&!cutb){//枚举A,B是否截断
dfs(k+1,new_carry,0,0,new_sum);
dfs(k+1,new_carry,0,1,new_sum);
dfs(k+1,new_carry,1,0,new_sum);
dfs(k+1,new_carry,1,1,new_sum);
}
else if(!cuta&&cutb){
dfs(k+1,new_carry,0,1,new_sum);
dfs(k+1,new_carry,1,1,new_sum);
}
else if(cuta&&!cutb){
dfs(k+1,new_carry,1,0,new_sum);
dfs(k+1,new_carry,1,1,new_sum);
}
else dfs(k+1,new_carry,1,1,new_sum);
}
}
}
但是在原题会 WA 一个点。
而且效率相当低,
还是因为前导
可以注意到,如果当前位被填了
int ans;
int cnt[]={6,2,5,5,4,5,6,3,7,6};
void dfs(int k,int carry,bool cuta,bool cutb,int sum){
if(cuta&&cutb){
if((!carry&&sum==0)||(carry&&sum==2))ans++;
return;
}
for(int a=0;a<=9;a++){
if(cuta&&a!=0)break;
for(int b=0;b<=9;b++){
if(cutb&&b!=0)break;
int tmp=a+b+carry;
int c=tmp%10,new_carry=tmp/10;
int new_sum=sum;
if(!cuta)new_sum-=cnt[a];
if(!cutb)new_sum-=cnt[b];
new_sum-=cnt[c];
if(new_sum<0)continue;
bool allowcuta=a!=0||k==1,allowcutb=b!=0||k==1;//只有当前位非0或在第一位才允许截断
if(!cuta&&!cutb){
dfs(k+1,new_carry,0,0,new_sum);
if(allowcuta)dfs(k+1,new_carry,0,1,new_sum);
if(allowcutb)dfs(k+1,new_carry,1,0,new_sum);
if(allowcuta&&allowcutb)dfs(k+1,new_carry,1,1,new_sum);
}
else if(!cuta&&cutb){
dfs(k+1,new_carry,0,1,new_sum);
if(allowcuta)dfs(k+1,new_carry,1,1,new_sum);
}
else if(cuta&&!cutb){
dfs(k+1,new_carry,1,0,new_sum);
if(allowcutb)dfs(k+1,new_carry,1,1,new_sum);
}
else dfs(k+1,new_carry,1,1,new_sum);
}
}
}
现在可以通过原题了,但跑
可以发现,目前我们的搜索向下递归的参数有且仅有函数的参数,没有任何额外信息(比如回溯需要的信息)。因此,可以使用记忆化搜索存储每个状态。
int cnt[]={6,2,5,5,4,5,6,3,7,6};
int f[N][2][2][2][N];//不难发现,每次进位最多为 1,所以 carry 这维仅需开到 2
int dfs(int k,int carry,bool cuta,bool cutb,int sum){
if(cuta&&cutb){
if((!carry&&sum==0)||(carry&&sum==2))return 1;
else return 0;
}
if(f[k][carry][cuta][cutb][sum]!=-1)return f[k][carry][cuta][cutb][sum];
int ret=0;
for(int a=0;a<=9;a++){
if(cuta&&a!=0)break;
for(int b=0;b<=9;b++){
if(cutb&&b!=0)break;
int tmp=a+b+carry;
int c=tmp%10,new_carry=tmp/10;
int new_sum=sum;
if(!cuta)new_sum-=cnt[a];
if(!cutb)new_sum-=cnt[b];
new_sum-=cnt[c];
if(new_sum<0)continue;
bool allowcuta=a!=0||k==1,allowcutb=b!=0||k==1;
if(!cuta&&!cutb){
(ret+=dfs(k+1,new_carry,0,0,new_sum))%=mod;
if(allowcuta)(ret+=dfs(k+1,new_carry,0,1,new_sum))%=mod;
if(allowcutb)(ret+=dfs(k+1,new_carry,1,0,new_sum))%=mod;
if(allowcuta&&allowcutb)(ret+=dfs(k+1,new_carry,1,1,new_sum))%=mod;
}
else if(!cuta&&cutb){
(ret+=dfs(k+1,new_carry,0,1,new_sum))%=mod;
if(allowcuta)(ret+=dfs(k+1,new_carry,1,1,new_sum))%=mod;
}
else if(cuta&&!cutb){
(ret+=dfs(k+1,new_carry,1,0,new_sum))%=mod;
if(allowcutb)(ret+=dfs(k+1,new_carry,1,1,new_sum))%=mod;
}
else (ret+=dfs(k+1,new_carry,1,1,new_sum))%=mod;
}
}
return f[k][carry][cuta][cutb][sum]=ret;
}
程序效率得到了质的飞跃,
还能更进一步吗?比如
乍一看有点难:总的状态数已经是
仔细看:
事实上,我们仅需知道
int cnt[]={6,2,5,5,4,5,6,3,7,6};
int f[2][2][2][2][N];
int dfs(bool k,bool carry,bool cuta,bool cutb,int sum){
if(cuta&&cutb){
if((!carry&&sum==0)||(carry&&sum==2))return 1;
else return 0;
}
if(f[k][carry][cuta][cutb][sum]!=-1)return f[k][carry][cuta][cutb][sum];
int ret=0;
for(int a=0;a<=9;a++){
if(cuta&&a!=0)break;
for(int b=0;b<=9;b++){
if(cutb&&b!=0)break;
int tmp=a+b+carry;
int c=tmp%10,new_carry=tmp/10;
int new_sum=sum;
if(!cuta)new_sum-=cnt[a];
if(!cutb)new_sum-=cnt[b];
new_sum-=cnt[c];
if(new_sum<0)continue;
bool allowcuta=a!=0||k==1,allowcutb=b!=0||k==1;
if(!cuta&&!cutb){
(ret+=dfs(0,new_carry,0,0,new_sum))%=mod;
if(allowcuta)(ret+=dfs(0,new_carry,0,1,new_sum))%=mod;
if(allowcutb)(ret+=dfs(0,new_carry,1,0,new_sum))%=mod;
if(allowcuta&&allowcutb)(ret+=dfs(0,new_carry,1,1,new_sum))%=mod;
}
else if(!cuta&&cutb){
(ret+=dfs(0,new_carry,0,1,new_sum))%=mod;
if(allowcuta)(ret+=dfs(0,new_carry,1,1,new_sum))%=mod;
}
else if(cuta&&!cutb){
(ret+=dfs(0,new_carry,1,0,new_sum))%=mod;
if(allowcutb)(ret+=dfs(0,new_carry,1,1,new_sum))%=mod;
}
else (ret+=dfs(0,new_carry,1,1,new_sum))%=mod;
}
}
return f[k][carry][cuta][cutb][sum]=ret;
}
现在程序效率大大提高(大常数的
正文部分结束了,接下来是吹水。
要不要把数据范围再开大一点,比如说
首先,虽然看起来比较抽象,但原来的记搜是可以改写成一般的递推形式的(确信)。
很抽象,但是可以看出来,这确实是一个 1D/0D 的动态规划,只是常数有一点大而已(迫真)。
进一步地,每一位最坏的情况就是
换句话说,每个
有没有发现什么?
可以将
稍微把时限放开点(或者机子比较快),跑
有点感慨,经过一系列优化,我们将原题
不能说这是算法竞赛的全部,只能说不断优化算法,就是算法竞赛最大的乐趣之一吧。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】