• luogu1120 小木棍【数据加强版】 暴力剪枝


    题目大意

    乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

    总体思路

    令totLen为所有小木棍总长度,枚举每一个原始木棍的可能长度eachLen(sumLen%eachLen==0),判断那些原始木棍能否全部由小木棍凑成。

    判断过程的朴素算法为:枚举原始木棍的每一种排列方式,看看这样能否凑成totLen/eachLen个原始木棍。


    一些定义

    • 到当前木棍的排列:Dfs到当前木棍时,将Dfs访问过的木棍按访问的顺序排成一行,这一行木棍的排列 叫做到当前木棍的排列。
    • 某一木棍的长度前缀和:当前木棍排列中的第一个木棍到“某一”木棍之间(包括这两个木棍)木棍的总长度。
    • 前n个木棍能凑成:在到当前木棍的排列中,对于每个k∈[1, N]∩Z,都能在到当前木棍排列中找到一个木棍,使得该木棍的长度前缀和=k*eachLen。
    • 组合成的木棍组:在上述每个k∈[1,N]∩Z,k对应的木棍和k-1对应的木棍之间的(不包括k-1对应木棍,包括k对应木棍)木棍们。
    • 排序变换:将每个组合成的木棍组内的木棍从大到小排列,然后将木棍组按照木棍组中的第一个木棍的长度从大到小排列。

    隐式图中,每个节点的值为:

    1. 这是前几个木棍
    2. 到当前木棍的排列。

    每条边的值为新增的木棍的长度。

    但是此朴素算法效率很低。

    整体算法的改变

    当Dfs到当前木棍时,如果前(当前木棍的长度前缀和/eachLen)个原始木棍不能凑成,则Dfs下去的结果肯定不成立。所以Dfs到当前节点时,应当保证前(当前木棍的长度前缀和/eachLen)个原始木棍能凑成(条件*)。

    怎么表示当前木棍的排列

    因为保证了前(当前木棍的长度前缀和/eachLen)个原始木棍能凑成,所以我们不用记录组合成的木棍组的具体排列,只要记录哪些木棍访问过了即可。而对于第(当前木棍的长度前缀和/eachLen+1)个原始木棍(以后简称当前原始木棍),我们要达到的,是使得以后在处理后一个原始木棍时,仍然满足条件*。换句话说,就是把当前处理的原始木棍拼满。所以我们要记录一个rest,表示要凑成当前原始木棍,还需要的小木棍的长度为多少。这样,每个节点的值就变成了:

    1. 当前是前几个木棍(cnt)。
    2. rest.
    3. 每个木棍是否访问过的集合。

    剪枝

    排序

    假设:对于每个能凑成totLen/eachLen个原始木棍的排列,对其进行排序变换。对于每种排列,变换过后的结果与原来是等价的,但是多个排列经过变换后变成了同一种排列。这样,我们规定每一种可行排列都满足每个木棍组内的木棍长度递减,木棍组的第一个木棍的长度递减,这样就缩小了解空间。具体操作为:运算前先将每个木棍从大到小排序。Dfs当前木棍时记录当前从数组中哪个下标处开始,备选下一个木棍就从当前位置枚举。

    为什么排序从大到小

    因为如果一个木棍的排列不能凑成凑成totLen/eachLen个原始木棍,长度长的木棍在先可以较早地判断出该排列不满足要求。

    排除显然不成立的情况

    位于当前木棍扩展时,当前备选木棍与上一次的备选木棍长度相等

    遇到本情况显然跳过,因为Dfs到上一次所选节点时,其备用木棍组合包括了Dfs到当前备选木棍时的木棍组合。前者都不成立,后者当然不成立。

    (当前木棍的长度前缀和%eachLen)==0时,第一个备选木棍便无法(通过放入第(当前木棍的长度前缀和/eachLen+1)个原始木棍)来凑成所有原始木棍。

    本情况也要跳过。假设不跳过,则这第一个备选木棍就要在后后个原始木棍中再次出现。对以后的组合成的木棍组进行排序变换,则那个木棍又出现在了第(当前木棍的长度前缀和/eachLen+1)原始木棍中,这与当前情况矛盾。

    当前木棍的长度=eachLen,还不能凑成所有原始木棍

    跳过,证明与上同理。

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cassert>
    using namespace std;
    
    const int MAX_STICK = 70;
    bool Vis[MAX_STICK];
    int Len[MAX_STICK];
    int Tot;
    #define LOOP(i, n) for(int i=1; i<=n; i++)
    #define LoopFrom(i, m, n) for(int i=m; i<=n; i++)
    
    bool cmp(int a, int b)
    {
    	return a > b;
    }
    
    bool Dfs(int cnt, int rest, int eachLen, int begin)
    {
    	if (cnt == Tot)
    		return rest == 0;
    	if (rest == 0)
    	{
    		rest = eachLen;
    		begin = 1;
    	}
    	int prevLen = -1;
    	LoopFrom(next, begin, Tot)
    	{
    		if (Len[next] == prevLen || Vis[next] || rest-Len[next]<0)
    			continue;
    		prevLen = Len[next];
    		Vis[next] = true;
    		if (Dfs(cnt + 1, rest - Len[next], eachLen, next+1))
    			return true;
    		else if (rest == eachLen || rest-Len[next]==0)
    		{
    			Vis[next] = false;
    			return false;
    		}
    		else
    		{
    			Vis[next] = false;
    			continue;
    		}
    	}
    	return false;
    }
    
    int main()
    {
    #ifdef _DEBUG
    	freopen("c:\noi\source\input.txt", "r", stdin);
    #endif
    	Tot = 0;
    	int tempTot;
    	scanf("%d", &tempTot);
    	int maxLen = 0, sumLen = 0, stickLen;
    	while(tempTot--)
    	{
    		scanf("%d", &stickLen);
    		if (stickLen <= 50)
    		{
    			Len[++Tot] = stickLen;
    			maxLen = max(maxLen, stickLen);
    			sumLen += stickLen;
    		}
    	}
    	sort(Len + 1, Len + Tot + 1, cmp);
    	LoopFrom(eachLen, maxLen, sumLen)
    	{
    		if (sumLen%eachLen)
    			continue;
    		memset(Vis, false, sizeof(Vis));
    		if (Dfs(0, 0, eachLen, 1))
    		{
    			printf("%d
    ", eachLen);
    			break;
    		}
    	}
    	return 0;
    }
    

      

     

  • 相关阅读:
    JS设置Cookie过期时间
    linq to xml
    ToDictionary的用法
    为程序使用内存缓存(MemoryCache)
    NuGet的几个小技巧
    IIS 的几个小技巧
    在Visual Studio中使用NuGet管理项目库
    在ASP.NET MVC中,使用Bundle来打包压缩js和css
    在C#中使用WMI查询进程的用户信息
    WMI测试器
  • 原文地址:https://www.cnblogs.com/headboy2002/p/8614831.html
Copyright © 2020-2023  润新知