【题解】洛谷P1120 小木棍(搜索+剪枝+卡常)

洛谷P1120:https://www.luogu.org/problemnew/show/P1120

思路

明显是搜索题嘛 

但是这数据增强不是一星半点呐

我们需要N多的剪枝

PS:需要先删去超出50的木棍

首先我们可以想到枚举每个小木棍的长度来搜索

但是直接枚举肯定会超时的 所以我们想到优化剪枝

因为要组成木棍肯定要从被砍开的木棍中的最大值开始枚举到所有木棍总和长(只有一根木棍被砍开)

然而这样却还不是最优的剪枝 因为每根原始小木棍的长度一样 所以枚举长度的时候可以判断是否被总和整除

而且我们只需要枚举到总和的一半即可 因为如果分成两根或以下行不通的话 到最后只需要输出总长就行(只有一根木棍)

因为短的可以组合得比长的更灵活 因此我们可以把木棍从大到小排序之后再选择

扫描二维码关注公众号,回复: 3400245 查看本文章

进入DFS后我们需要判断如果当前枚举的这根木棍还需要凑的长度为0 且已经满足:凑出的根数已经足够 (凑出长棍的根数=所有木棍的长度之和/原始长度)

当DFS失败时 我们需要退出并换另外一根长度不同的木棍 因为一样长度的可能有很多 所以时间浪费了 我们需要在进入DFS之前预处理出所有木棍下一根不同长度的编号

可以根据木棍长度的单调性来二分找出第一个木棍长度不大于未拼长度rest

参考洛谷某dalao的难想却特别特别重要剪枝:

如果当前长棍剩余的未拼长度等于当前木棍的长度或原始长度,继续拼下去时却失败了,就直接回溯并改之前拼的木棍

解释:

当前长棍剩余的未拼长度等于当前木棍的长度时,当前木棍明显只能自组一根长棍,但继续拼下去却失败,说明这根木棍不能自组?!这根木棍不自组就没法用上了,所以不用搜更短的木棍了,直接回溯,改之前的木棍;

 当前长棍剩余的未拼长度等于原始长度时,说明这根原来的长棍还一点没拼,现在正在放入一根木棍。很明显,这根木棍还没有跟其它棍子拼接,如果现在拼下去能成功话,它肯定是能用上的,即自组或与其它还没用的木棍拼接。但继续拼下去却失败,说明现在这根木棍不能用上,无法完成拼接,所以直接回溯,改之前的木棍。

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define maxn 70
int n,k,minn,sum,c,num,len;
int mood[maxn],nex[maxn];//nex是预处理编号 
bool vis[maxn],flag;
int cinn() 
{
    int x=0,f=0;
    char ch=0;
    while(!isdigit(ch))
        f|=(ch=='-'),ch=getchar();
    while(isdigit(ch))
        x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return f?-x:x;
}
void write(int x) 
{
    if(x<0)putchar('-'),x=-x;
    if(x>=10)write(x/10);
    putchar(x%10+'0');
}
bool cmp(int a,int b)
{
    return a>b;
}
void dfs(int p,int last,int rest)//p是当前已经拼倒第几根 last是上一根用的编号 rest是还要多长 
{
    int i;
    if(!rest)//还需要0长度的木棍 
    {
        if(p==num)//如果已经凑齐了所有木棍 
        {
            flag=1;
            return;
        }
        for(i=1;i<=n;i++)//如果还没凑齐 找一根还没用过的当第一根要用的 
        if(!vis[i]) break;
        vis[i]=1;
        dfs(p+1,i,len-mood[i]);//搜索下一根 
        vis[i]=0;
        if(flag) return;
    }
    int l=last+1,r=n,mid;
    while(l<r)//二分找编号 
    {
        mid=(l+r)>>1;
        if(mood[mid]<=rest) r=mid;
        else l=mid+1;
    }
    for(i=l;i<=n;i++)//注意从第一根不同的编号开始而不是从1开始 
    {
        if(!vis[i])
        {
            vis[i]=1;
            dfs(p,i,rest-mood[i]);
             vis[i]=0;
             if(flag) return;
             if(rest==mood[i]||rest==len) return;//最后一个剪枝 
             i=nex[i];//更换小木棍 
             if(i==n) return;
        }
    }
}
void read()
{
    n=cinn();
    while(n)
    {
        int x;
        x=cinn();
        if(x<=50)//删去大于50的木棍 
        {
            sum+=x;
            mood[++k]=x;
        }
        n--;
    }
    n=k;
    sort(1+mood,1+mood+n,cmp);//从大到小排序 
    minn=mood[1];//木棍中的最大值是枚举长度的最小值 
    nex[n]=n;
    for(int i=n-1;i>0;i--)//预处理编号 
    {
        if(mood[i]==mood[i+1]) nex[i]=nex[i+1];
        else nex[i]=i;
    }
}
int main()
{
    read();
    for(len=minn;len<=sum/2;len++)
    {
        if(sum%len==0)//整除 
        {
            num=sum/len;//小木棍根数 
            flag=0;
            vis[1]=1;
            dfs(1,1,len-minn);//取了第一根 
            vis[1]=0;
            if(flag)
            {
                write(len);//如果满足输出 
                return 0;
            }
        }
    }
    write(sum);//只有一根木棍 
} 
View Code

 

猜你喜欢

转载自www.cnblogs.com/BrokenString/p/9721233.html
今日推荐