upd:这篇是17年写的博客,结果后来18年出成月赛于是把这篇删了,现在还原又变成最新发布时间了。。。。
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5937
一开始认为是网络流,1边是1-9,另一边是1+1=2 1+2=3,但如何限制3个流量满流到右边才能对答案贡献为1?完全想不出,以为是个神建模。之前认为D是搜索,然后队友说不可能这个也是搜索,1次比赛出2个搜索的概率太小了,我就觉得嘛,与其挂机不如暴力。于是写了dfs,然后队友一测100 100 ..100,果然超时,队友尝试另一个题的时候,我想了想搜索东西,可行性剪枝,最优性剪枝,迭代加深,a*函数。。。。卧槽好像可以如果后面的全部选都达不到当前已记录的答案就return,最基本的最优性剪枝啊。。然后队友尝试完D打出GG,我立马加上这个剪枝,哈哈100 100 ..100能过辣,激动地交一发,队友一口奶,WA了,然后查错发现2+7=6了。。然后改掉,交一发,没奶,A了,哇这个搜索题A的人这么少嘛?很多人都不相信这是搜索吧。
#include<cstdio>
#include<cstring>
#define maxl 1001
int ans;
int a[10],cnt[21];
int t[21][3]=
{{0,0,0},{1,1,2},{1,2,3},{1,3,4},{1,4,5},{1,5,6},{1,6,7},{1,7,8},{1,8,9}
,{2,2,4},{2,3,5},{2,4,6},{2,5,7},{2,6,8},{2,7,9}
,{3,3,6},{3,4,7},{3,5,8},{3,6,9}
,{4,4,8},{4,5,9}
};
void prework()
{
for(int i=1;i<=9;i++)
scanf("%d",&a[i]);
for(int i=1;i<=20;i++)
if(t[i][0]==t[i][1])
cnt[i]=cnt[i-1]+1;
else
cnt[i]=cnt[i-1]+2;
}
inline bool jug(int k,int i)
{
if(t[k][0]==t[k][1] && i>1)
return false;
if(t[k][0]!=t[k][1] && i>2)
return false;
if(t[k][0]==t[k][1] && a[t[k][0]]<2*i)
return false;
if(a[t[k][0]]<i || a[t[k][1]]<i || a[t[k][2]]<i)
return false;
return true;
}
void dfs(int k,int sum)
{
int i=0;
if(cnt[20]-cnt[k-1]+sum<=ans)
return;
while(jug(k,i))
{
a[t[k][0]]-=i;a[t[k][1]]-=i;a[t[k][2]]-=i;sum+=i;
if(k==20)
ans=sum>ans? sum:ans;
else
dfs(k+1,sum);
a[t[k][0]]+=i;a[t[k][1]]+=i;a[t[k][2]]+=i;sum-=i;
i++;
}
}
void mainwork()
{
ans=0;
dfs(1,0);
}
void print(int cas)
{
printf("Case #%d: %d\n",cas,ans);
}
int main()
{
int t;
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
prework();
mainwork();
print(i);
}
return 0;
}
转载某神犇更加全面的剪枝,我还是太菜辣。。
- 对于遍历次数的优化
仔细跟着思路模拟一下,会发现,对于a+b=c
和b+a=c
的情况,他们属于可以把a+b=c
选择两次的情况
而直接遍历 36 种可能显然把a+b=c
只选一次的情况 遍历了两次
因此,这里可以先标记下然后再进行判断,如果a+b=c
不在 DFS 递归的栈里(已经遍历过只选一次的分支),那么不再遍历b+a=c
- 剪枝
- 当深度大于等于 36 的时候,显然已经找到答案了,可以直接返回
- 如果把现在所有的数字除以 3 (每个式子有 3 个数字) 再加上已经遍历的深度都不可能比已有答案大,显然可以直接返回
- 如果把当前已经遍历的深度(已选择的式子数目),和剩下能遍历的深度加起来都没有已有答案大,可以直接返回
- 预处理
预处理到每个深度,其以后所有式子需要的每个数字的个数.当发现数字足够时,直接返回返回答案