深搜专题(DFS大法好!)

来源:互联网 发布:quick report软件下载 编辑:程序博客网 时间:2024/06/05 06:59

纪念一下

蒟蒻的成绩

断断续续地做了三天 终于肝完了9题

前排提示:本篇题解涉及大量无脑乱搞剪枝以及各种卡常高超技巧 有些地方可能会多余甚至导致程序变慢

Problem A 邮票面值设计

NOIP2007初赛完善程序第二题 NOIP1999 T4

给定一个信封,最多只允许粘贴N张邮票,计算在给定M(N+M<=10)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大max ,使得1~max之间的每一个邮资值都能得到。
例如,N=3,M=2,如果面值分别为1分、4分,则在l分~6分之间的每一个邮资值都能得到(当然还有8分、9分和12分):如果面值分别为1分、3分,则在1分~7分之间的每一个邮资值都能得到。可以验证当N=3,M=2时,7分就是可以得到连续的邮资最大值,所以MAX=7,面值分别为l分、3分。

用一个背包来找到最大可以满足的面值 然后可想而知若当前最大为r 那么下一张邮票最大只能为r+1 否则中间会空
对于每一层保存一下当前dp数组即可(memcpy常数真心小..我在自己的程序中使用memcpy是直接枚举赋值的1/10左右)

#include<bits/stdc++.h>using namespace std;int a[20],ans[20],ANS,n,m,dp[2000],cop[20][2000];void dfs(int l,int r){    int i,j;    for (i=0;i<=a[l]*(m-1);i++)        if (dp[i]<m)             dp[i+a[l]]=min(dp[i+a[l]],dp[i]+1);    while (dp[r+1]<=m) r++;    if (l==n){        if (r>=ANS){            ANS=r;            for (i=1;i<=n;i++) ans[i]=a[i];        }        return;    }    memcpy(cop[l],dp,sizeof(dp));    for (i=a[l]+1;i<=r+1;i++){        a[l+1]=i;        dfs(l+1,r);        memcpy(dp,cop[l],sizeof(dp));    }}int main(){    memset(dp,63,sizeof(dp));    int i;    scanf("%d%d",&m,&n);    a[1]=1;    dp[0]=0;    dfs(1,0);    for (i=1;i<=n;i++) printf("%d ",ans[i]);    printf("\nMAX=%d\n",ANS);    return 0;}

Problem B 木棒

POJ1011

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

看到n的范围就不像个DFS题
肯定要先确定答案 然后判断是否可行
似乎有9种剪枝方法 我东拼西凑出了7个
首先从大到小排序 因为如果先确定小的会很容易使大的木棒匹配不上
外层枚举答案:
剪枝1:可行的答案显然应该整除木棒的长度和
剪枝2:根据奇偶性 如果当前答案是奇数 木棒中为奇数的条数必须大于需要的条数 而且多出来的部分必须是偶数
DFS中:
剪枝3:找每一组第一根木棒的时候 如果找不到答案 就应该直接退出 因为这一根是必须要取的
剪枝4:有很多木棒是重复的 如果第一个找不到答案 之后重复的那些也不会找到答案 直接跳过即可
剪枝5:上下界剪枝 如果加上最小的还比要求大或者加上最大的还比要求小 那么一定无解
剪枝6:类似剪枝2 如果要求为奇数 而当前剩下的比需要的少 一定无解
剪枝7:如果加上当前这根正好能凑成一根 却找不到答案 那么也应当退出 因为后面都是小的木棒 比当前木棒“灵活”一些 用等长的一组小木棒代替这根木棒只会更难匹配完成

#include<bits/stdc++.h>using namespace std;#define N 70int tar,n,Mx,a[N],s[N];bool ok,b[N];void dfs(int l,int sum,int cnt,int las){//l表示当前匹配到第l根 sum表示当前已经匹配的长度 cnt表示剩下的奇数长度 las表示还要匹配的根数     if (las<=1){        ok=1;        return;    }    if (sum==tar){        dfs(1,0,cnt,las-1);        return;    }    if (sum+a[n]>tar) return;//剪枝5     if (sum+s[l]<tar) return;    if ((tar&1) && cnt<las-1) return;//剪枝6 las是当前的一根 加进去判断会有些问题     int i;    for (i=l;i<=n;i++)        if (!b[i]){            b[i]=1;            dfs(i+1,sum+a[i],cnt-(a[i]&1),las);            b[i]=0;            if (ok) return;            if (!sum) return;//剪枝3             if (sum+a[i]==tar) return;//剪枝7             while (i<=n && a[i]==a[i+1]) i++;//剪枝4         }}bool cmp(int _,int __) {return _>__;}int main(){    while (scanf("%d",&n),n){        int tot=0,i;        ok=0;        Mx=0;        int cnt=0;        for (i=1;i<=n;i++){            scanf("%d",&a[i]);            if (a[i]&1) cnt++;            tot+=a[i];            Mx=max(Mx,a[i]);        }        sort(a+1,a+1+n,cmp);        for (i=n;i;i--) s[i]=s[i+1]+a[i];        for (i=Mx;i<=tot;i++){            if (tot%i) continue;//剪枝1             int ned=tot/i;            if ((i&1) && (cnt<ned || (cnt-ned)&1)) continue;//剪枝2             tar=i;            dfs(1,0,cnt,ned);            if (ok){                printf("%d\n",i);                break;            }        }    }    return 0;}

Problem C 生日蛋糕

NOI1999

7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为NπM层生日蛋糕,每层都是一个圆柱体。
设从下往上数第i(1iM)层蛋糕是半径为Ri, 高度为Hi的圆柱。当i<M时,要求Ri>Ri+1Hi>Hi+1
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。令Q=Sπ
请编程对给出的NM,找出蛋糕的制作方案(适当的RiHi的值),使S最小。
(除Q外,以上所有数据皆为正整数)
N10000M20

这题限制条件只有体积和上层蛋糕要比下层小
对于每一层蛋糕要枚举RiHi首先要满足不超过V
然后就是剪枝
使用上下界可行性剪枝以及答案最优性剪枝

#include<bits/stdc++.h>using namespace std;int ans=1e9,n,V;long long S[10010],S1[10010];void dfs(int t,int v,int s,int l1,int l2){    if (S[t]>v) return;//S为i层以上最小体积    if (1LL*l1*l1*l2*t<v) return;//最大体积(这种方法显然很不精确,但是如果枚举可能会导致复杂度爆炸)     if (s+S1[t]>=ans) return;//S1为i层以上最小面积    if (!t){        if (!v) ans=min(ans,s);        return;    }    int h,r;    for (r=l1-1;r>=t;r--)        for (h=min(v/r/r,l2-1);h>=t;h--)            dfs(t-1,v-h*r*r,s+2*r*h+(t==n?r*r:0),r,h);}int main(){    scanf("%d%d",&V,&n);    int i;    for (i=1;i<=n;i++) S[i]=S[i-1]+1LL*i*i*i,S1[i]=S1[i-1]+2LL*i*i;     dfs(n,V,0,V,V);    printf("%d\n",ans==1e9?0:ans);    return 0;}

Problem D 汽车问题

IOI1994

有一个人在某个公共汽车站上,从12:00到12:59观察公共汽车到达本站的情况,该站被多条公共汽车线路所公用,他依次记下公共汽车到达本站的时刻。
 在12:00-12:59期间,同一条线路上的公共汽车以相同的时间间隔到站。
 时间单位用“分”表示,从0 到59 。
 每条公共汽车线路至少有两辆车到达本站。
 公共汽车线路数K一定≤17,汽车数目N一定小于300。
 来自不同线路的公共汽车可能在同一时刻到达本站。
 不同公共汽车线路的车首次到站时间和到站的时间间隔都有可能相同。
请为公共汽车线路编一个调度表,目标是:公共汽车线路数目最少的情况下,使公共汽车到达本站的时刻满足输入数据的要求。

首先一条路线可以由两辆车的时间确定
第一种方法
每次已知第一辆车 枚举第二辆车的方法
这种方法可以进行答案最优性和判重等剪枝 不过我尝试多次仍然TLE80分
第二种方法
就是每辆车有两种决策 要么作为第一辆 要么作为第二辆
作为第一辆时就存下来 作为第二辆时枚举前面存下来的第一辆车 然后把后面同一路线的标记掉
经过测试 这样的方法不用加什么剪枝就可以通过 而且跑得贼快
大概是因为这样的方法(通过玄学)减少了枚举的次数 这也充分说明了搜索时枚举策略的重要性

#include<bits/stdc++.h>using namespace std;#define N 310int ans,n,p,a[N],L[N];  bool vis[N],ok[N];void dfs(int l,int route,int matched){    if (route>=ans) return;    if (l>n){        if (route==matched) ans=min(ans,route);        return;    }    if (vis[l]){        dfs(l+1,route,matched);        return;    }    int i,j;    vis[l]=1;    for (i=1;i<=route;i++)//作为第二辆         if (!ok[i]){            int delta=a[l]-L[i];            int tmp=a[l]+delta;            for (j=l+1;j<=n;j++)                if (!vis[j] && a[j]==tmp) tmp+=delta;            if (tmp<=p) continue;            tmp=a[l]+delta;            for (j=l+1;j<=n;j++)                if (!vis[j] && a[j]==tmp) vis[j]=1,tmp+=delta;            ok[i]=1;            dfs(l+1,route,matched+1);            ok[i]=0;            tmp=a[l]+delta;            for (j=l+1;j<=n;j++)                if (vis[j] && a[j]==tmp) vis[j]=0,tmp+=delta;        }    L[route+1]=a[l];//作为第一辆     ok[route+1]=0;    dfs(l+1,route+1,matched);    vis[l]=0;   }int main(){    scanf("%d",&n);    int i;    for (i=1;i<=n;i++){        scanf("%d",&a[i]);        p=max(p,a[i]);    }    ans=min(17,(n+1)/2);    dfs(1,0,0);     printf("%d\n",ans);    return 0;}

Problem E Betsy’s Tour 漫游小镇

USACO

一个正方形的镇区分为 N*N 个小方块(1N7)。农场位于方格的左上角,集市位于左下角。贝茜穿过小镇,从左上角走到左下角,刚好经过每个方格一次。
写一个程序,对于给出的 N 值,计算贝茜从农场走到集市有多少种唯一的路径。

这种问题算是比较经典了 虽然n比较小 但是搜不好就会进入死路 导致时间爆炸
从两个角度分析“进入死路”这一条件
1:除了起点和终点 所有点都必须和两个未被遍历到的格子相邻
2:不能走将会把联通块切成两半的路径 就像这样

Betsy(引用自齐鑫IOI99论文《搜索方法中的剪枝优化》)

由于确定了起点和终点 如果出现图中的情况 一定是绕了一圈之后回来 那么一旦走过去就会把联通块切成两半
然而如果每次O(n2)扫一遍显然效率太低了
对于第一种 Betsy每次移动只可能对周围的4个格子产生影响 每次更新一遍并且判断
如果周围有两格cnt(相邻的未遍历格子的个数)=1 就一定没有方案
如果有一格 一定要走那一格
如果没有 四格都可以走
对于第二种 记一下来的方向判断一下即可

#include<bits/stdc++.h>using namespace std;int X[4]={1,0,-1,0},Y[4]={0,1,0,-1};int n,ans,cnt[9][9];bool b[9][9];void dfs(int x,int y,int t,int fr){    if (x==n && y==1){        if (t==n*n-1) ans++;        return;    }    int a1=(fr+1)%4,a2=(fr+3)%4;//第二种     if (b[x+X[fr]][y+Y[fr]] && !b[x+X[a1]][y+Y[a1]] && !b[x+X[a2]][y+Y[a2]]) return;    if (b[x+X[fr]+X[a1]][y+Y[fr]+Y[a1]] && !b[x+X[fr]][y+Y[fr]] && !b[x+X[a1]][y+Y[a1]]) return;    if (b[x+X[fr]+X[a2]][y+Y[fr]+Y[a2]] && !b[x+X[fr]][y+Y[fr]] && !b[x+X[a2]][y+Y[a2]]) return;    register int i,j,k;    int must=0;    for (i=0;i<4;i++){//第一种         int px=x+X[i],py=y+Y[i];        if ((px==n && py==1) || !px || !py || px>n || py>n || b[px][py]) continue;        if (cnt[px][py]==1) must++;    }    if (must>=2) return;    for (i=0;i<4;i++){        int px=x+X[i],py=y+Y[i];        if (!px || !py || px>n || py>n || b[px][py]) continue;        if (must && cnt[px][py]!=1) continue;        b[px][py]=1;        for (j=0;j<4;j++){            int mx=px+X[j],my=py+Y[j];            cnt[mx][my]--;        }        dfs(px,py,t+1,i);        for (j=0;j<4;j++){            int mx=px+X[j],my=py+Y[j];            cnt[mx][my]++;        }        b[px][py]=0;    }}int main(){    scanf("%d",&n);    b[1][1]=1;    int i,j;    memset(cnt,63,sizeof(cnt));    for (i=1;i<=n;i++)//cnt初始化         for (j=1;j<=n;j++){            cnt[i][j]=4;            if (i==1 || i==n || j==1 || j==n) cnt[i][j]--;            if ((i==1 || i==n) && (j==1 || j==n)) cnt[i][j]--;        }    cnt[1][1]=0;    cnt[2][1]--;    cnt[1][2]--;    dfs(1,1,0,0);    printf("%d\n",ans);    return 0;}

Problem F 三角关

USACO2006Feb

杨辉三角,不过应该是倒过来的杨辉三角。若给出1~n的一个排列A,则将A1、A2相加,A2、A3相加……An-1、An相加,则得到一组n-1个元素的数列B;再将B1、B2相加,B2、B3相加,Bn-2、Bn-1相加,则得到一组n-2个元素的数列……如此往复,最终会得出一个数T。而Charles给sunnypig出的难题便是,给出n和T,再尽可能短的时间内,找到能通过上述操作得到T且字典序最小的1~n的排列(N20

不难发现每个点的贡献就是第n层的杨辉三角
可以枚举全排列然后判断
一个比较显然的剪枝就是上下界的可行性剪枝
如果每次都扫一遍找最大/最小值 时间不能接受
总方案有n!种 而最大/最小值只和取的数有关 和排列无关 于是可以用二进制压位预处理每种情况的最大/最小值
其实有一个很重要的剪枝
观察杨辉三角发现左右权值是对称的 那么由于要求相同答案下字典序最小的方案 一定让对应位置保持左小右大
这样再卡一下常就可以过了

#include<bits/stdc++.h>using namespace std;#define N 22int C[N],a[N],n,tar,Mn[1<<N],Mx[1<<N];void check(int S,int t,int &res1,int &res2){//其实直接sort对预处理的复杂度影响并不大= =     register int i,l,r;    if (t>=n/2+1){        r=t;        for (i=n;i;i--)            if (!(S&(1<<i-1))) res1+=C[r++]*i;        r=t;        for (i=1;i<=n;i++)            if (!(S&(1<<i-1))) res2+=C[r++]*i;    }else{        l=n/2,r=n/2+1;        for (i=n;i;i--)            if (!(S&(1<<i-1)))                if (l>=t && C[l]>=C[r]) res1+=C[l--]*i;                else res1+=C[r++]*i;        l=n/2,r=n/2+1;        for (i=1;i<=n;i++)            if (!(S&(1<<i-1)))                if (l>=t && C[l]>=C[r]) res2+=C[l--]*i;                else res2+=C[r++]*i;    }}void init(){    C[1]=1;    int i,j;    for (i=2;i<=n;i++)        for (j=i;j;j--)            C[j]+=C[j-1];    for (i=0;i<1<<n;i++)        check(i,__builtin_popcount(i)+1,Mx[i],Mn[i]);}void dfs(int t,int sum,int now){    register int i;    if (t>n){        if (sum==tar){            for (i=1;i<=n;i++) printf("%d ",a[i]);            printf("\n");            exit(0);        }        return;    }    if (sum+Mn[now]>tar || sum+Mx[now]<tar) return;    for (i=(t<<1)>n+1?a[n-t+1]+1:1;i<=n;i++)//保持左小右大         if (!(now&(1<<i-1))){            a[t]=i;            dfs(t+1,sum+i*C[t],now|(1<<(i-1)));        }}int main(){    scanf("%d%d",&n,&tar);    init();    dfs(1,0,0);    return 0;}

Problem G 佳佳的魔法阵

Vijos P1284

魔法阵是一个n*m的格子(高n,宽m),n*m为偶数。佳佳手中有n*m个宝石(以1~n*m编号)。佳佳从最右上角的格子开始走,从一个格子可以走到上、下、左、右4个相邻的格子,但不能走出边界。每个格子必须且仅能到过1次,这样佳佳一共走了n*m个格子停止(随便停哪里)。佳佳每进入一个格子,就在该格子里放入一颗宝石。他是按顺序放的,也就是说——第i个进入的格子放入i号宝石。
如果两颗宝石的编号对n*m/2取模的值相同,则认为这两颗宝石相互之间有微妙的影响。也就是说,我们按照宝石的编号对n*m/2取模的值,将宝石分成n*m/2对,其中每对都恰有两颗宝石。对于每一对宝石,设第一颗宝石在第a行第b列,另一颗宝石在第c行第d列,那么定义这2个宝石的魔力影响值为 k1*|a-c|+k2*|b-d|。
需要你求出的是,在所有合乎题意的宝石摆放方案中,所有成对的宝石间的最大魔力影响值的最小值为多少。换句话说,如果我们定义对n*m/2取模的值为i的一对宝石的魔力影响值为a[i]。你需要求出的就是max{a[i]|i=0,1,2…}的最小值。

其实直接套Problem E的剪枝即可 不再赘述
再加上一个答案最优性剪枝

#include<bits/stdc++.h>using namespace std;#define N 55int n,m,k1,k2,p,posx[N],posy[N],ans=1e9;int X[4]={1,0,-1,0},Y[4]={0,1,0,-1};bool b[N][N];void dfs(int x,int y,int t,int Mx,int fr){    if (Mx>=ans) return;    if (t==n*m-1){        ans=min(ans,max(Mx,k1*abs(x-posx[p-1])+k2*abs(y-posy[p-1])));        return;    }    int a1=(fr+1)%4,a2=(fr+3)%4;    if (b[x+X[fr]][y+Y[fr]] && !b[x+X[a1]][y+Y[a1]] && !b[x+X[a2]][y+Y[a2]]) return;    if (b[x+X[fr]+X[a1]][y+Y[fr]+Y[a1]] && !b[x+X[fr]][y+Y[fr]] && !b[x+X[a1]][y+Y[a1]]) return;    if (b[x+X[fr]+X[a2]][y+Y[fr]+Y[a2]] && !b[x+X[fr]][y+Y[fr]] && !b[x+X[a2]][y+Y[a2]]) return;    int i;    if (t>=p)        for (i=0;i<4;i++){            int px=x+X[i],py=y+Y[i];            if (b[px][py]) continue;            b[px][py]=1;            dfs(px,py,t+1,max(Mx,k1*abs(x-posx[t-p])+k2*abs(y-posy[t-p])),i);            b[px][py]=0;        }    else        for (i=0;i<4;i++){            int px=x+X[i],py=y+Y[i];            if (b[px][py]) continue;            b[px][py]=1;            posx[t]=x;            posy[t]=y;            dfs(px,py,t+1,0,i);            b[px][py]=0;        }}int main(){    scanf("%d%d%d%d",&n,&m,&k1,&k2);    int i;    for (i=1;i<=n;i++) b[i][0]=b[i][m+1]=1;    for (i=1;i<=m;i++) b[0][i]=b[n+1][i]=1;    p=n*m/2;    b[1][1]=1;    dfs(1,1,0,0,0);    printf("%d\n",ans);    return 0;}

Problem H 彩票

HNOI2002

某地发行一套彩票。彩票上写有1到M这M个自然数。彩民可以在这M个数中任意选取N个不同的数打圈。每个彩民只能买一张彩票,不同的彩民的彩票上的选择不同。
每次抽奖将抽出两个自然数X和Y。如果某人拿到的彩票上,所选N个自然数的倒数和,恰好等于X/Y,则他将获得一个纪念品。
已知抽奖结果X和Y。现在的问题是,必须准备多少纪念品,才能保证支付所有获奖者的奖品。

直接转化为小数运算
上下界剪枝很有效 直接加就可以卡过去
还有一种利用DP的剪枝
在一开始暴力通分 数字可能非常大 但是剪枝并不需要很精确 可以随便找一个质数%一下
dpi,j表示i到m中通分后加起来取模后为j所需最少数字
dpi,j=min(dpi+1,j,dpi+1,jVi+1) 其中Vi表示1i通分后的结果 即n!yi
那么在dfs中就可以利用到最终状态所需最少的数字剪枝了
还可以加一个最大的剪枝 不过好像效率不高

#include<bits/stdc++.h>using namespace std;#define EPS 1e-10#define mo 9973#define N 55int n,m,ans,dp[N][mo+10],V[N],T;double tar,S[N],P[N];void dfs(int l,int t,double sum,int sv){    if (sum+S[m-(n-t)+1]-EPS>tar) return;    if (sum+S[l]-S[l+(n-t)]+EPS<tar) return;    if (t+dp[l][(T-sv+mo)%mo]>n) return;    if (t==n){        if (fabs(sum-tar)<EPS) ans++;        return;    }    int i;    for (i=l;i<=m-(n-t)+1;i++)        dfs(i+1,t+1,sum+P[i],(sv+V[i])%mo);}int Pow(int a){    int res=1,b=mo-2;    while (b){        if (b&1) res=res*a%mo;        b>>=1;        a=a*a%mo;    }    return res;}int main(){    int x,y,i,j;    scanf("%d%d%d%d",&n,&m,&x,&y);    tar=1.0*x/y;    for (i=m;i;i--) S[i]=S[i+1]+(P[i]=1.0/i);    int tmp=1;    for (i=1;i<=m;i++) tmp=tmp*i%mo;    for (i=1;i<=m;i++) V[i]=tmp*y*Pow(i)%mo;    T=tmp*x%mo;    memset(dp,63,sizeof(dp));    dp[m+1][0]=0;    for (i=m;i;i--)        for (j=0;j<mo;j++){            int p=(j+V[i])%mo;            dp[i][j]=min(dp[i][j],dp[i+1][j]);            dp[i][p]=min(dp[i][p],dp[i+1][j]+1);        }    dfs(1,0,0,0);    printf("%d\n",ans);    return 0;}

Problem I 智破连环阵

NOI2003

B国在耗资百亿元之后终于研究出了新式武器——连环阵(Zenith Protected Linked Hybrid Zone)。传说中,连环阵是一种永不停滞的自发性智能武器。但经过A国间谍的侦察发现,连环阵其实是由M个编号为1,2,…,M的独立武器组成的。最初,1号武器发挥着攻击作用,其他武器都处在无敌自卫状态。以后,一旦第i(1<=i< M)号武器被消灭,1秒种以后第i+1号武器就自动从无敌自卫状态变成攻击状态。当第M号武器被消灭以后,这个造价昂贵的连环阵就被摧毁了。
为了彻底打击B国科学家,A国军事部长打算用最廉价的武器——炸弹来消灭连环阵。经过长时间的精密探测,A国科学家们掌握了连环阵中M个武器的平面坐标,然后确定了n个炸弹的平面坐标并且安放了炸弹。每个炸弹持续爆炸时间为5分钟。在引爆时间内,每枚炸弹都可以在瞬间消灭离它平面距离不超过k的、处在攻击状态的B国武器。和连环阵类似,最初a1号炸弹持续引爆5分钟时间,然后a2号炸弹持续引爆5分钟时间,接着a3号炸弹引爆……以此类推,直到连环阵被摧毁。
显然,不同的序列a1、a2、a3…消灭连环阵的效果也不同。好的序列可以在仅使用较少炸弹的情况下就将连环阵摧毁;坏的序列可能在使用完所有炸弹后仍无法将连环阵摧毁。现在,请你决定一个最优序列a1、a2、a3…使得在第ax号炸弹引爆的时间内连环阵被摧毁。这里的x应当尽量小。

这一题无论是dfs的方式还是剪枝都比较巧妙
首先炸弹炸5分钟其实只是说明了一个炸弹可以炸掉一段连续区间的武器
如果每次枚举用哪个炸弹炸 只能卡到40分
题解的方法是枚举武器区间的划分 然后跑二分图最大匹配判断
剪枝1:用dp算出武器i-m全部炸掉所需最少炸弹数 进行最优性剪枝
剪枝2:如果当前二分图无法匹配 直接退出
每次二分图匹配可以利用上一次匹配剩下的信息 只需要匹配最新的一段区间即可
题解的论文中是用区间匹配炸弹 我脑抽写炸弹匹配区间 每次还要多枚举一层 跑得贼慢= =
还可以用一个bfs求出当前可以满足的最大长度 具体可以参照楼天城《浅谈部分搜索+高效算法在搜索问题中的应用》

#include<bits/stdc++.h>using namespace std;#define N 105struct poi{    int x,y;}A[N],B[N];int ans,n,m,K,dis[N][N],mat[N],a[N],dp[N];bool b[N],ok[N][N][N],vis[N];void init(){    ans=n;    int i,j,k;    for (i=1;i<=m;i++)        for (j=1;j<=n;j++)            dis[i][j]=(A[i].x-B[j].x)*(A[i].x-B[j].x)+(A[i].y-B[j].y)*(A[i].y-B[j].y);    for (i=1;i<=n;i++)        for (j=1;j<=m;j++){            ok[i][j][j]=(dis[j][i]<=K);            for (k=j+1;k<=m;k++)                ok[i][j][k]=ok[i][j][k-1]&(dis[k][i]<=K);        }    memset(dp,63,sizeof(dp));    dp[m+1]=0;    for (i=m;i;i--)        for (j=1;j<=n;j++)            for (k=i+1;k<=m+1;k++)                if (ok[j][i][k-1])                    dp[i]=min(dp[i],dp[k]+1);}bool find(int x,int t){    int i;    for (i=1;i<=t;i++)        if (!vis[i] && ok[x][a[i-1]+1][a[i]]){            vis[i]=1;            if (!mat[i] || find(mat[i],t)){                mat[i]=x;                return 1;            }        }    return 0;}bool Vis[N];bool check(int t){    int i;    mat[t]=0;    memset(Vis,0,sizeof(Vis));    for (i=1;i<t;i++) Vis[mat[i]]=1;    for (i=1;i<=n;i++){        if (Vis[i]) continue;        memset(vis,0,sizeof(vis));        if (find(i,t)) return 1;    }    return 0;} void dfs(int l,int t){    if (t-1+dp[l+1]>=ans) return;//剪枝1     if (l==m){        a[t]=m;        if (check(t)) ans=t;        return;    }    dfs(l+1,t);    a[t]=l;    if (check(t)) dfs(l+1,t+1);//剪枝2 }int main(){    scanf("%d%d%d",&m,&n,&K);    K=K*K;    int i;    for (i=1;i<=m;i++) scanf("%d%d",&A[i].x,&A[i].y);    for (i=1;i<=n;i++) scanf("%d%d",&B[i].x,&B[i].y);    init();    dfs(1,1);    printf("%d\n",ans);    return 0;}

总结:DFS是一个很基本的算法 但它的精髓在于剪枝优化
剪枝大致分为可行性剪枝和最优性剪枝
对于不同的题目应当根据搜索树考虑剪枝方式
另外 dfs的策略也格外重要 枚举的方式很大程度上决定了运行时间的快慢

Date:2017/10/9
By CalvinJin

原创粉丝点击