深搜专题

经过六天左右的时间终于写完了,在写完之前有好几个大佬都写完了,orz一下。

把名字涂掉了


T1.邮票面值设计

题意

可以贴n张邮票,有m种邮票。求能表示到的最大邮票值mx最大(就是1~mx之间的值都可以表示的出来),并且求出能表示到mx的方案中字典序最大的一个。

分析

这题是不是初赛考过啊…其实一开始有点冲动把试卷翻出来然后照着敲一遍但是仔细一想,初赛怎么可能给你跑得快的解啊xxx

首先思路就是深搜,把邮票都枚举出来最后求再判,这样会感觉有点慢,而且是会出现重复计算好多东西,比如说前面选择的邮票种类一样,只有最后的邮票不一样,这样的话重复算一次就很慢了。

用dp[i]表示贴到值i至少要用多少种邮票(对于当前的情况而言)
然后每新加进来一种新的邮票就更新一下dp然后进入下一层。这时候可以用一个past数组来记录一下当前的dp,回溯的时候重新赋值回来一遍就好了。(据说memcpy很快)
这样就可以做到一边枚举一边求答案了。
以上是对于状态答案求解的优化。

每枚举一种新的邮票的值x,这个x有什么特点吗?
首先这个x一定是大于上一个邮票的值的。同时,如果当前的最大能表示的值为mx,x必然<=mx+1。证明的话很容易因为如果x>mx+1,那么mx+1就肯定无法表示了。
以上是对于枚举值的优化。

#include<bits/stdc++.h>
#define M 17
#define N 2000
using namespace std;

void read(int &x){
    x=0; char c=getchar();
    for (; c<'0'; c=getchar());
    for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0');  
}
int n,m,a[M],ans[M],dp[N],c[M][N];
int maxv;

void pt(){
    int i;
    for (i=1; i<=n; i++)printf("%d ",ans[i]); putchar('\n');
    printf("MAX=%d\n",maxv);    
}

void debug(int n,int r){
    int i;
    for (i=1; i<=n; i++)printf("%d ",a[i]);  printf("\n");
    for (i=0; i<=r; i++)printf("%d ",dp[i]); printf("\n");
    printf("r=%d\n",r);
}

void Min(int &x,int y){if (x>y)x=y;}

void dfs(int k,int r){
    int i;
    for (i=0; i<=a[k]*m; i++)if (dp[i]<m)Min(dp[i+a[k]],dp[i]+1);
    for (; dp[r]<=m; r++);
    if (k==n){
        if (r-1>=maxv){
            maxv=r-1;
            for (i=1; i<=n; i++)ans[i]=a[i]; 
        }
        return;     
    }
    memcpy(c[k],dp,sizeof(c[k]));
    for (i=a[k]+1; i<=r; i++){
        a[k+1]=i;
        dfs(k+1,r); 
        a[k+1]=0;
        memcpy(dp,c[k],sizeof(dp));
    }   
}

int main(){
    read(m); read(n);
    memset(dp,63,sizeof(dp));
    a[1]=1; dp[0]=0;
    dfs(1,0);
    pt();
    return 0;
}

T2.木棍

题意

一开始有个人有一堆长度相等的木棍,他突然把所有的木棍砍到成几部分,每一部分长度不大于50。砍完之后,有n根木棍,每根长度为a[i],现在这个人他又突然想把这堆木棍拼回去,但是他又不记得原来长度和原来有多少根了。现在求原来木棍最短能是多短。
n<=65
数据有多组

分析

其实问题就是把这一堆木棍分成几堆,每一堆加起来的值都相等,并且求出这个相等的最大值。

然后思路还是搜索,但是一看这n这么大感觉人都不好了。
首先表示一下搜索记录的东西。

bool dfs(int hp,int last,int x,int now,int sum,int len)
因为有多组数据所以不能一找到答案就exit掉,所以要用bool来…(好麻烦)
hp表示还剩几根没有凑出来
last表示还剩下几个奇数长度的木棍
x表示上一个木棍是哪一根
now表示当前正在凑的这一根木棍长度是多长
sum表示剩余还没凑的木棍的长度总和
len表示目标要凑的长度是多长。

  1. 首先假设所有木棍长度为sum,你枚举答案的长度为len,那么必须满足sum%len==0
  2. len从大到小枚举
  3. 将木棍长度从大到小排序,这样的话情况应该会少很多。
  4. 如果sum==len||sum+now==len 就肯定可以 return 1;
  5. 枚举用哪一根木棍的时候要从上一根的后一根开始。注意如果凑满一整根之后又要从最开始继续。
  6. 如果now==0那么就把最大的一根凑进去,如果不成功就直接退出。
  7. 对于一个now,如果说已经尝试过一个长度的木棍了,那么和它长度一样的木棍就不用再试了。
  8. 如果now+a[i]==len,a[i]即为当前被选中的木棍,也可以直接return
  9. 用dp[i]表示用所有的木棍能不能表示出i,如果!dp[len-now]也可以直接return
  10. 如果len是奇数,那么剩余的奇数长度木棍个数一定要大于还需要凑出来的木棍个数。如果len是偶数,其实怎么样都好啦——

    分个类。
    可行:1,4,6,8,9,10
    最优:2
    枚举范围:5,7
    杂七杂八的玄学剪枝:3(其实并不很玄学只是有点难解释)

#include<bits/stdc++.h>
#define M 75
using namespace std;

void read(int &x){
    x=0; char c=getchar();
    for (; c<'0'; c=getchar());
    for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0');
}

int a[M],n,S[M],cnt[2];
bool t[M];
bitset<M*M>dp;

bool dfs(int hp,int last,int x,int now,int sum,int len){
    if (now>len)return 0;
    if (now==len)now=0,x=1,hp--;
    if (sum+now==len)return 1;
    if (sum+now<len)return 0;
    if (now+S[x+1]<len)return 0;
    if ((len-now)&1){if (last<hp-1)return 0; }

    if ((len-now)&1){
        for (register int i=x+1; i<=n; i++)if (!t[i]&&(a[i]&1)&&now+a[i]<=len&&(a[i]!=a[i-1]||t[i-1])){
            t[i]=1; 
            if (dp[len-now-a[i]])if (dfs(hp,last-1,1,now+a[i],sum-a[i],len))return 1;
            t[i]=0;
            if (now==0||now+a[i]==len)return 0;
        }   
    }
    else for (register int i=x+1; i<=n; i++)if (!t[i]&&now+a[i]<=len&&(a[i]!=a[i-1]||t[i-1])){
        t[i]=1; 
        if (dp[len-now-a[i]])if (dfs(hp,last-(a[i]&1),i,now+a[i],sum-a[i],len))return 1;
        t[i]=0;
        if (now==0||now+a[i]==len)return 0;
    }
    return 0;
}

bool cmp(int x,int y){return x>y;} 
int main(){
    for (; scanf("%d",&n)!=EOF;){
        if (n==0)return 0;
        int sum=0; cnt[0]=cnt[1]=0;
        memset(S,0,sizeof(S));
        for (register int i=1; i<=n; i++)read(a[i]),sum+=a[i],cnt[a[i]&1]++;
        sort(a+1,a+n+1,cmp);
        for (register int i=n; i>=1; i--)S[i]=S[i+1]+a[i];
        dp.reset();
        dp[0]=1; 
        memset(t,0,sizeof(t)); t[1]=1;
        for (int i=1; i<=n; i++)dp|=dp<<a[i];
        bool fl=1;
        for (int i=a[1]; fl&&i*2<=sum; i++)if (sum%i==0){
            int num=sum/i;
            if (i&1){
                if ((num<=cnt[1])&&((cnt[1]-num)%2==0))
                    if(dfs(num,cnt[1]-(a[1]&1),1,a[1],sum-a[1],i))fl=0;
            }
            else {
                if (dfs(num,cnt[1]-(a[1]&1),1,a[1],sum-a[1],i))fl=0;
            }
            if (!fl)printf("%d\n",i);
        }
        if (fl)printf("%d\n",sum);
    }
    return 0;   
}

吐槽

其实题面为了方便起见我稍作了一下修改结果有这样一种既视感


T3.生日蛋糕

题意

现在要做一个体积为nπ,共m层的蛋糕。需要满足每一层的半径r[i] < r[i-1],高度h[i] < h[i-1] (i>=1)
怎么做能让表面积最小。求这个最小值。做不出来输出0

分析

嗯反正肯定还是深搜。
然后其实不需要把每一层的r,h都记下来,只需要记上一层的就好了。
void dfs(int last,int r,int h,int S,int V)
last 还剩多少层
r上一层的半径
h上一层的高度
S表面积
V剩余的体积

然后是剪枝
1. 如果当前的V-最小的last层<0 return
2. 如果当前的V-最大的last层(取不到的)>0 return
3. 如果sum+V/S>=ans return
4. 对于每一层的r,从上一层的r-1枚举到last为止,h同理。

分类:
可行:1,2
最优:3
枚举:4

#include<bits/stdc++.h>
#define Mx 1000000000
using namespace std;

int s[25];

void ch(int n,int m){
    /*
        S[i] 造i层至少有多少体积 
    */
    int r;
    for (r=1; r<=m; r++){
        s[r]=s[r-1]+r*r*r;
    }   
    if (s[m]>n){printf("0\n"); exit(0);}
}
int n,m,ans=Mx;

void dfs(int last,int r,int h,int S,int V){
    /*
        last 还剩多少层
        r上一层的半径
        h上一层的高度
        S表面积 
        V剩余的体积  
    */
    if (S+V/r>=ans)return;
    if (V-s[last]<0)return;
    if (V-(r-1)*(r-1)*(h-1)*last>0)return;
    if (last==0){
        if (V==0)ans=S;
        return;
    }
    register int i,tmp=last-1,R=r-1; h--;
    for (r=min(R,(int)(sqrt(V))+1); r>tmp; r--)for (i=min(h,V/r/r); i>tmp; i--)dfs(tmp,r,i,S+2*r*i,V-r*r*i);
}
int main(){
    scanf("%d%d",&n,&m);
    ch(n,m);
    int i,j;
    for (i=m; i*i*m<=n; i++){
        for (j=n/i/i; j>=m; j--){
            dfs(m-1,i,j,i*i+2*i*j,n-i*i*j);
        }
    }   
    if (ans!=Mx)printf("%d\n",ans);else printf("0\n");
    return 0;
}

T4.汽车问题

题意

有个人在观察一个小时内公交汽车的进站情况。已知同一班汽车每隔一段相同的时间会进站一次。求问最少有几班车。注意的是 结束观察的时间是观察到最后一辆车的时间而不是59。
汽车数量n<=300,0<=t[i]<=59,最多k班车,1<=k<=13

分析

这个n好像 稍微 有点 大诶?
但是线路数量挺少的。

一开始的想法是,找第一辆车,枚举第二辆车。然后再加剪枝。
虽然过不了不过还是简单讲一下这种方法怎么剪枝吧。
1. 如果当前位置的车的数量大于后面的车的数量,退出来。
2. 如果当前位置>=最晚时间/2,那么k=k+剩下的车数量。判完退。
3. 如果当前k>=ans,退。
4. 预处理某个位置的某个长度是否可行
但是这样只能水到80分。

正解直接换了一个枚举的方法。就是判断当前这一辆车是开头的一辆还是第二辆。
这种方法不用加什么剪枝就能过了。而且跑的飞快。

#include<bits/stdc++.h>
using namespace std;

void read(int &x){
    x=0; char c=getchar();
    for (; c<'0'; c=getchar());
    for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0');
}
int ans=17;
int cnt[70],M,a[20];
bool t[20];
void dfs(int x,int sum,int last){
    /*
        x 当前处理到最后一辆车的位置
        sum 当前的k
        last 还剩下几辆车
    */
    if (sum>=ans)return;
    if (last==0){
        ans=sum; 
        return;
    }
    for (; !cnt[x]; x++);
    register int i,tmp=last-1,len,j;
    cnt[x]--;
    a[++sum]=x;
    dfs(x,sum,tmp);
    sum--;
    for (i=1; i<=sum; i++){
        if (x==a[i]||t[i])continue;
        len=x-a[i]; bool flag=1;
        for (j=x+len; flag&&j<=M; j+=len)if (!cnt[j])flag=0;
        if (flag){
            t[i]=1;
            for (j=x+len; j<=M; j+=len)cnt[j]--,tmp--;   
            dfs(x,sum,tmp);
            for (j=x+len; j<=M; j+=len)cnt[j]++,tmp++;
            t[i]=0;
        }       
    }
    cnt[x]++;
}

int main(){
    int n,i,x;
    read(n);
    for (i=1; i<=n; i++)read(x),cnt[x]++,M=max(M,x);
    dfs(0,0,n);
    printf("%d\n",ans); 
    return 0;
}

参考资料

由“汽车问题”浅谈深度搜索的一个方面 搜索对象与策略的重要性(国家集训队2001 骆骥)
看这个↑

总结

一般来说之前写的深搜的优化都是减小枚举的范围啊,可行性,答案最优还有上下界这样的剪枝。但是遇见直接改变枚举的东西还是第一次。觉得很神奇…因为如果是枚举第二辆车的话就最多有60,而如果是枚举是第一辆还是第二辆的话,就最多只有17。通过改变枚举的东西来缩小操作量(上面的论文是这么叫它的)。
这种改变搜索策略的方法应该还是很有用的,如果以后写到搜索题的话也可以往这个上面想一想。


T5. Betsy’s Tour 漫游小镇

题意

村庄是一个n*n大小的矩阵,求问从(1,1)走到(1,n)且经过所有的点的路径有多少种。
例如n=3 有以下两种走法
123 145
874 236
965 987

分析

打表大法好!
当然打表也是一种不可或缺的重要方法不过暂时不考虑它…
发现搜索时间特别长的原因是经常进入死路但是又查不出来。其实非要查的话是可以n^2查的能不能到终点的。但是这个没什么用而且时间很长。
怎么判断当前状态能不能走遍所有点又到达终点呢?
首先我们是从(1,1)开始走,直到(1,n)结束。
有以下几种情况。

这里写图片描述
遇到这个情况是向左走还是向右走呢?

答案是不用走了直接退回来吧。
原因是走到这个局面的话一定是绕了一圈回来了。
如果是从右边绕回来的话向右走会被绕进圈子里,然后向左走的话右边就走不到。如果是从左边来的话是同理的。
然后如果把这个图横过来也是同理的。

加上这一步剪枝,就跑的很快了。

#include<bits/stdc++.h>
using namespace std;
int n;
int ans;
bool vis[10][10];
#define ch(x,y) (!vis[x][y])

void dfs(register int x,register int y,register int len) {
    if (x==1&&y==n&&len)return;
    if (x==1&&y==n) {ans++; return; }
    if (vis[x+1][y]&&vis[x-1][y])if (!vis[x][y+1]&&!vis[x][y-1])return;
    if (!vis[x+1][y]&&!vis[x-1][y])if (vis[x][y+1]&&vis[x][y-1])return;
    len--;
    vis[x][y]=1;
    if (ch(x+1,y))dfs(x+1,y,len);
    if (ch(x-1,y))dfs(x-1,y,len);
    if (ch(x,y+1))dfs(x,y+1,len);
    if (ch(x,y-1))dfs(x,y-1,len);
    vis[x][y]=0;
}

int main() {
    scanf("%d",&n);
    ans=0;
    register int i;
    for (i=1; i<=n; i++)vis[n+1][i]=vis[i][n+1]=vis[i][0]=vis[0][i]=1;
    dfs(1,1,n*n-1);
    printf("%d",ans);
    return 0;
}

以及

#include<bits/stdc++.h>
int n,ans[9];
int main(){
    scanf("%d",&n);
    ans[1]=1;
    ans[2]=1;
    ans[3]=2;
    ans[4]=8;
    ans[5]=86;
    ans[6]=1770;
    ans[7]=88418;   
    printf("%d\n",ans[n]);
    return 0;
}

总结

一开始想到的就是打表,不过其实这类走来走去并且要经过整个图的题目其实也不少吧。(后面就还有一道)
然后这种可行性剪枝是需要深入分析当前状态的。总之就是要多想想才能想出来的剪枝。现在对于可行性剪枝方法怎么找大概就是这样的想法:
找找有没有什么状态是肯定不可行的。然后找这一种状态的特征,然后看这个特征好不好判断(当然就算不好判断也要稍微努力一下的),然后加入剪枝。
当然有个很重要的地方!就是不能把可行的也给剪掉。(虽然是一句废话但是很重要)

T6. 三角关

题意

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

分析

嗯,数学题
其实找找规律就能知道其实每一个数字的贡献就是杨辉三角第n层那一位的系数*这个数字的大小即可。

void dfs(int x,int Sum,int S)
表示还剩下x个位置需要填。
距离t还有Sum
已经选了的数字集S

好了来剪枝吧!

  • 因为字典序最小而且左右其实是等价的。就是说 对称。所以说一个点开始枚举的值是从a[n-i+1]+1开始的
  • 把已经选了的数字映射成一个二进制数S,预处理处mx[S]和mn[S],即S情况下能达到的最大和最小值。上下界剪枝
  • 只剩一个的时候直接判
#include<bits/stdc++.h>
#define M 25
#define N 1050000
using namespace std;
int a[M],n,dp[M],mn[N],mx[N],f[M][M];
bool use[M];
int base[M];

void dfs(int x,int Sum,int S){
    register int i;
    if (x==1){
        if (Sum<=n&&!use[Sum]){for (i=n; i>=2; i--)printf("%d ",a[i]); printf("%d",Sum); exit(0);}
        return;
    }

    if (Sum<mn[S])return;
    if (Sum>mx[S])return;
    //register int tmp=dp[x]*(a[n-x+1]+1);
    for (i=a[n-x+1]+1; i<=n&&i*dp[x]<Sum; i++)if (!use[i]){
        use[i]=1; a[x]=i;
        dfs(x-1,Sum-i*dp[x],S|base[i-1]); 
        use[i]=0; a[x]=0;
    }
}
void ycl(){
    register int i,j,cnt,id;
    base[0]=1;
    for (i=1; i<=n; i++)base[i]=base[i-1]*2;
    f[1][1]=1;
    for (i=2; i<=n; i++)for (j=1; j<=i; j++)f[i][j]=f[i-1][j-1]+f[i-1][j];
    for (i=1; i<=n; i++)dp[i]=f[n][i];
    for (i=1; i<n;  i++)
        for (j=1; j<=i; j++)f[i][j]=f[n][j];
    for (i=1; i<=n; i++)sort(f[i]+1,f[i]+i+1);


    for (i=0; i<base[n]; i++){
        cnt=0;
        for (j=0; j<n; j++)if (!(i&base[j]))cnt++;
        id=cnt; //printf("---%d %d---\n",i,cnt);
//      cnt=0;
        for (j=0; j<n; j++)if (!(i&base[j]))mn[i]+=(j+1)*(f[id][cnt--]);
        cnt=id;
        for (j=n-1; j>=0; j--)if (!(i&base[j]))mx[i]+=(j+1)*(f[id][cnt--]); 
//      printf("%d:%d    mx:%d  mn:%d\n",i,cnt,mx[i],mn[i]);
    }
}
int main(){
//  freopen("F.in","r",stdin);
    int t;
    scanf("%d%d",&n,&t);
    if (n==1){printf("1\n"); return 0;}
    ycl();
    dfs(n,t,0);
    return 0;
}

总结

非要说的话就是状压来搞上下界吧。

T7.佳佳的魔法阵

题意

(不大好概括,上原题)
魔法阵是一个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…}的最小值。
读入n,m,k1,k2

分析

其实非要说的话就只有两个剪枝。

  • 可行性剪枝,见T5。方法类似
  • 最优性剪枝,如果当前的最大值大于等于ans直接 return;
#include<bits/stdc++.h>
#define A first
#define B second
#define pii pair<int,int>
#define mp make_pair
using namespace std;

#define ch(x,y) (!vis[x][y])
pii a[5005];
int n,m,tmp,k1,k2,res=1000000000;
bool vis[55][55];

void dfs(int x,int y,int last,int Mx){
    if (last<=tmp){Mx=max(Mx,k1*abs(a[last].A-x)+k2*abs(a[last].B-y));}
    else {a[last-tmp]=mp(x,y);}
    last--;
    if (Mx>=res)return;
    if (!last){res=Mx; return ;}
    if (!vis[x+1][y]&&!vis[x-1][y])if (vis[x][y-1]&&vis[x][y+1])return ;
    if (vis[x+1][y]&&vis[x-1][y])if (!vis[x][y-1]&&!vis[x][y+1])return ;
    vis[x][y]=1;

    if (ch(x+1,y))dfs(x+1,y,last,Mx);
    if (ch(x-1,y))dfs(x-1,y,last,Mx);
    if (ch(x,y+1))dfs(x,y+1,last,Mx);
    if (ch(x,y-1))dfs(x,y-1,last,Mx);

    vis[x][y]=0;
}


int main(){
    scanf("%d%d%d%d",&n,&m,&k1,&k2); tmp=n*m/2;
    int i;
    for (i=1; i<=n; i++)vis[i][m+1]=vis[i][0]=1;
    for (i=1; i<=m; i++)vis[n+1][i]=vis[0][i]=1;
    dfs(1,1,n*m,0);
    printf("%d",res);
    return 0;
}

T8.彩票

题意

有m个数,在里面找n个数,分别为a[1],a[2],a[3]…a[n],是1/a[1]+1/a[2]+1/a[3]…+1/a[n]=x/y的方案共有多少种。

分析

虽然听说有一种取模的方法但是并不能看懂。
比较水,总之上下界剪枝就足够了。而且还可以直接用实型。

直接上code吧。里面mx,mn就分别是最大最小值的情况。

#include<bits/stdc++.h>
#define db double
#define EPS 1e-10
using namespace std;

int n,m,xx,yy,res;
db z,mn[15],mx[15][55];

void dfs(int i,db now,int last){
    i++;
//  printf("%.3f\n",now);
    if (now+mx[last][i]+EPS<z)return ;
    if (now+mn[last]-EPS>z)return ;
    if (last==0){if (fabs(now-z)<EPS)res++; return;}
    last--;
    for (; i+last<=m; i++)dfs(i,now+1.0/i,last);
}

void ycl(){
    int i,j,k;
    for (i=1; i<=n; i++){mn[i]=mn[i-1]+1.0/(m-i+1.0);}

    for (i=1; i<=n; i++){
        for (j=1; j+i<=m+1; j++){
            for (k=j; k<i+j; k++)mx[i][j]+=1.0/k;
        }
    }
}

int main(){
    scanf("%d%d%d%d",&n,&m,&xx,&yy); z=1.0*xx/yy;
    ycl();
    dfs(0,0.0,n);
    printf("%d\n",res);
    return (0-0);
}

T9.智破连环阵

题面

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应当尽量小。

输入
第一行包含三个整数:M、n和k(1<=M, n<=100,1<=k<=1000),分别表示B国连环阵由M个武器组成,A国有n个炸弹可以使用,炸弹攻击范围为k。以下M行,每行由一对整数xi,yi(0<=xi,yi<=10000)组成,表示第i(1<=i<=M)号武器的平面坐标。再接下来n行,每行由一对整数ui,vi(0<=ui,vi<=10000)组成,表示第i(1<=i<=n)号炸弹的平面坐标。输入数据保证随机、无误、并且必然有解。

分析

题面好长Σ

总之就是说一个炸弹可以炸掉范围内的武器,但是武器i能被炸必须要满足武器j(j

#include<bits/stdc++.h>
#define M 105
using namespace std;

void read(int &x){
    x=0; char c=getchar();
    for (; c<'0'; c=getchar());
    for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0');
}

struct AC{int x,y;}a[M],b[M];

int n,m,res,mat[M],c[M],dp[M];
bool vis[M],use[M],can[M][M][M];

bool find(int x,int t){
    int i;
    for (i=1; i<=t; i++)if (!vis[i]){
        if (!can[x][c[i-1]+1][c[i]])continue;
        vis[i]=1;
        if (!mat[i]||find(mat[i],t)){
            mat[i]=x;
            return 1;           
        }   
    }
    return 0;
}

bool ch(int t){
    int i;
    mat[t]=0;
    memset(use,0,sizeof(use));
    for (i=1; i<t; i++)use[mat[i]]=1;
    for (i=1; i<=n; i++)if (!use[i]){
        memset(vis,0,sizeof(vis));
        if (find(i,t))return 1;     
    }
    return 0;
}

void dfs(int len,int sum){
    if (sum+dp[len+1]-1>=res)return;
//  printf("%d %d\n",len,sum);
    if (len==m){
        c[sum]=m;
        if (!ch(sum))return;
        res=sum; return;
    }
    dfs(len+1,sum);
    c[sum]=len;
    if (ch(sum))dfs(len+1,sum+1);
}


int main(){
//  freopen("1158.in","r",stdin);
//  freopen("1158.out","w",stdout);
    int i,j,k;
    read(m); read(n); read(k); k=k*k;
    res=n;
    for (i=1; i<=m; i++)read(b[i].x),read(b[i].y);
    for (i=1; i<=n; i++)read(a[i].x),read(a[i].y);

    for (i=1; i<=n; i++){
        for (j=1; j<=m; j++){
            int tx=a[i].x-b[j].x,ty=a[i].y-b[j].y;
            if (tx*tx+ty*ty<=k)can[i][j][j]=1;
//          printf("%d",can[i][j][j]);
        }   
//      printf("\n");
    }

    for (i=1; i<=n; i++){
        for (j=1; j<=m; j++){
            for (k=j+1; k<=m; k++){
                can[i][j][k]=can[i][j][k-1]&&can[i][k][k];
//              printf("%d  %d-%d   %d\n",i,j,k,can[i][j][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 (can[j][i][k-1])dp[i]=min(dp[i],dp[k]+1);
        }
//      printf("dp[%d]=%d\n",i,dp[i]);
    }
    c[0]=0;
    dfs(1,1);
    printf("%d",res);
    return 0;
}

参考资料

《浅谈部分搜索+高效算法在搜索问题中的应用 》(浙江省杭州第十四中学 楼天城 )

总结

总之就是这么 刷完了。
深搜真神奇
大概能够给剪枝归类一下。
最优性剪枝:基本所有都有
可行性剪枝:T5,T7(其实也是基本所有都有,但是这两题比较有特点)
枚举范围:T3,T6
枚举策略:T4
使用到算法维护答案或中间过程:T1(DP),T9(二分图)

总之感觉学到好多东西了…
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在MATLAB中,NURBS(非均匀有理B样条)是一种强大的数学工具,用于表示和处理复杂的曲线和曲面。NURBS在计算机图形学、CAD(计算机辅助设计)、CAM(计算机辅助制造)等领域有着广泛的应用。下面将详细探讨MATLAB中NURBS的绘制方法以及相关知识点。 我们需要理解NURBS的基本概念。NURBS是B样条(B-Spline)的一种扩展,其特殊之处在于引入了权重因子,使得曲线和曲面可以在不均匀的参数空间中进行平滑插值。这种灵活性使得NURBS在处理非均匀数据时尤为有效。 在MATLAB中,可以使用`nurbs`函数创建NURBS对象,它接受控制点、权值、 knot向量等参数。控制点定义了NURBS曲线的基本形状,而knot向量决定了曲线的平滑度和分布。权值则影响曲线通过控制点的方式,大的权值会使曲线更靠近该点。 例如,我们可以使用以下代码创建一个简单的NURBS曲线: ```matlab % 定义控制点 controlPoints = [1 1; 2 2; 3 1; 4 2]; % 定义knot向量 knotVector = [0 0 0 1 1 1]; % 定义权值(默认为1,如果未指定) weights = ones(size(controlPoints,1),1); % 创建NURBS对象 nurbsObj = nurbs(controlPoints, weights, knotVector); ``` 然后,我们可以用`plot`函数来绘制NURBS曲线: ```matlab plot(nurbsObj); grid on; ``` `data_example.mat`可能包含了一个示例的NURBS数据集,其中可能包含了控制点坐标、权值和knot向量。我们可以通过加载这个数据文件来进一步研究NURBS的绘制: ```matlab load('data_example.mat'); % 加载数据 nurbsData = struct2cell(data_example); % 转换为cell数组 % 解析数据 controlPoints = nurbsData{1}; weights = nurbsData{2}; knotVector = nurbsData{3}; % 创建并绘制NURBS曲线 nurbsObj = nurbs(controlPoints, weights, knotVector); plot(nurbsObj); grid on; ``` MATLAB还提供了其他与NURBS相关的函数,如`evalnurbs`用于评估NURBS曲线上的点,`isoparm`用于生成NURBS曲面上的等参线,以及`isocurve`用于在NURBS曲面上提取特定参数值的曲线。这些工具对于分析和操作NURBS对象非常有用。 MATLAB中的NURBS功能允许用户方便地创建、编辑和可视化复杂的曲线和曲面。通过对控制点、knot向量和权值的调整,可以精确地控制NURBS的形状和行为,从而满足各种工程和设计需求。通过入理解和熟练掌握这些工具,可以在MATLAB环境中实现高效的NURBS建模和分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值