【复杂枚举】 library

传送门

题意

给定一个框架的长宽\(XN、YN\),旧书长宽分别为\(XT、YT\),给定\(n\)行,代表\(n\)个木板,每行有5个数指明每个木板及其上的栓子

  • \(y_{i}\)表示第\(i\)个木板距离框架底部的距离
  • \(x_{i}\)表示当前木板距离框架左边的距离
  • \(l_{i}\)当前木板的长度
  • \(x_{1i}\)当前木板上的左侧钉子距离当前木板左侧的距离
  • \(x_{2i}\)当前木板上右侧钉子距离木板左侧的距离

其中任意两个木板都位于不同的水平线上,木板必须是处于稳定的状态,即木板的中心在两个钉子减或者中心恰好位于一个钉子上方。
给定的旧书竖直摆放,且其上方不能触及边壁以及碰到其他木板,对于木板可以进行下面操作

  1. 无操作
  2. 木板左移或右移
  3. 锯掉一段木板,左移或右移
  4. 钉子移动到同一水平线另一位置,木板左移或右移
  5. 木板锯掉一段,移动某一钉子到同一水平线的另一位置,木板左移或右移
  6. 木板和钉子一并去掉

木板厚度和钉子直径皆忽略不计,要通过以上的操作找到钉子移动最少次数、木板长度变化最小的方案下,
钉子移动次数最小次数及木板变化数

数据范围

\(1\leq XN,YN,XT,YT \leq 1000\)
\(1\leq N\leq 100\)
\(0< y_{i}<YN\)
\(0\leq x_{i}<XN\)
\(0 <li \leq XN-x_{i}\)
\(0\leq x_{1i}\leq \frac{li}{2}\)
\(\frac{li}{2}\leq x_{2i} \leq li , x_{1i}<x_{2i}\)

题解

  • 木板有高度和宽度,且同一水平线上只会有一块木板,所以可以先将木板按照高度从小到大排序
  • 每个木板的钉子直接得到其距离框架左端的距离

然后按照以下枚举方式进行搜索

  1. 首先枚举书放置的位置

    • 自下而上枚举大书放置的水平线
    • 由于书必定是放在某一块木板上的,考虑当前情况下的代价,因为据下木板没有收益,所以不需要考虑据下木板的情况,
    • 考虑当前大书可能放置的位置,如果当前的位置加上木板长度后不能保证在钉子上,就需要移动两个钉子
    • 如果中心不在两钉子之间只需要移动一个即可
  2. 用最优代价为大书腾出空间

    • 每个木板一定要满足稳定性所以通过枚举两个钉子和放书木板的关系来进行移动钉子和裁剪木板
    • 枚举当前木板上方的所有木板,看是否会挡住大书
    • 判断所有钉子与放书木板的位置关系选取最大的能保留长度,用总的减去即需要去掉的
      • 可以直接用钉子作中心的时候就用钉子做中心,这样的话最优

Code

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string>
#include<set>
#include<map>
using namespace std;


#define close ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define rep(i,a,n) for(int i=a;i<n;i++)
#define per(i,a,n) for(int i=n-1;i>=a;i--)
#define fi first
#define se second 
#define ll long long
#define pb push_back


typedef pair<long long,long long> pll;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<long long> vll;
typedef double db;


const ll mod=1e9+7;
const db pi=acos(-1.0); // 圆周率
const int INF  = 0x3f3f3f3f;


ll powmod(ll a,ll b,ll p){ll res=1;a%=p;while(b){if(b&1) res=res*a%p;a=a*a%p;b>>=1;}return res;}
ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;}


struct node
{
    int x,y,len,x1,x2;
    bool operator <(const node &a) const // 通过高度将木板区分
    {
        return y<a.y;
    }
}p[110];

int XN,YN,XT,YT;
int n;
int minmove=INF,mincut=INF;
void remove(int k,int x,int &move,int &cut) // x表示当前书的起始位置
{
    rep(i,k+1,n+1) // 枚举当前放上书的第k的木板上方的木板是否会挡住当前木板
    {
        int len; // 保留当前木板的最大长度
        if(p[i].y >= p[k].y+YT) break; // 剪枝,书够不到上面的都不会挡住
        if(p[i].x+p[i].len <= x || p[i].x > x+ XT) continue; //当前的水平位置上不会挡住
        if(p[i].x2 <= x) // 当前木板和书有交集,且此木板右钉子在起始点左端
        {
            if(x <= 2*p[i].x1)  // 考虑到中心的问题,满足中心的最大长度
                len = 2*(x-p[i].x1); 
            else len = x;
            if(len < p[i].len) cut += p[i].len - len; // 当前长度小于当前木板的长度的时候
        }
        else if(p[i].x1 >= x+XT) //左钉子在终点的右边
        {
            if(p[i].x2-x-XT <= XN - p[i].x2)
                len = 2*(p[i].x2-x-XT);// 右钉子正好在中心保证了长度最长,移动最少
            else len = XN - x - XT;
            if(len < p[i].len) cut += p[i].len-len;
        }
        else if(p[i].x1 <= x && p[i].x2 > x && p[i].x2 < x+XT) 
        {// 起始点在钉子之间且终点在有右钉子右边
            if(x==0) move +=2 ,cut+=p[i].len; //起始点为0,只能删掉整条边
            else 
            {
                move ++; 
                if(p[i].len > x) cut += p[i].len -x; // 去掉木板
            }
        }   
        else if(p[i].x1 > x && p[i].x1 < x+XT &&p[i].x2 >= x+XT) 
        {// 左钉子在起始点与终点之间并且右钉子在终点的右端
            if(x+XT == XN) // 当前书的终点到了最右端,只能删一整条边
            {
                move += 2;
                cut += p[i].len;
            }
            else
            {
                move ++;
                if(p[i].len > XN - x - XT)
                    cut +=p[i].len-XN+x+XT; // 木板最小截取长度
            }   
        }
        else if(p[i].x1<=x && p[i].x2 >= x+XT) // 两钉子在起点终点的两端之外
        {
            if(x==0 && XT==XN) // 当前书占整个长度,只能全部拆掉
                move+=2;
            else
                move++;

            len = max(x,XN-x-XT);
            if(p[i].len > len)
                cut+=p[i].len-len;
        }
        else if(p[i].x1 > x && p[i].x2 < x+XT) // 被书整个包含
        {
            move += 2;
            cut += p[i].len;
        }

    }
}
void solve(int k) // 枚举水平线
{
    rep(i,0,XN - XT+1) // 枚举书放置的起始位置
    {
        int move = 0,cut=0;
        if(i+p[k].len < p[k].x1) continue; // 木板不在钉子上
        else if(2*(p[k].x1 - i) > p[k].len || 2*(i+XT -p[k].x2)>p[k].len) 
            move++;//中心不在两个钉子之间,移动钉子
        else if(p[k].x2 - i>p[k].len || i+XT-p[k].x1 > p[k].len) 
            move++; //木板的长度不够,左端点到右钉子超过木板的长度、右端点到左钉子超过
        remove(k,i,move,cut);

        if(move < minmove || (move == minmove && cut < mincut))
            minmove=move,mincut=cut;
    }
}
int main()
{
    close
    cin>>XN>>YN>>XT>>YT;
    cin>>n;
    rep(i,1,n+1)
    {
        cin>>p[i].y>>p[i].x>>p[i].len>>p[i].x1>>p[i].x2;
        p[i].x1+=p[i].x;
        p[i].x2+=p[i].x;
    }
    sort(p+1,p+n+1);
    
    rep(i,1,n+1)
    {
        if(p[i].y + YT >YN) break; // 只能水平移动,高度不符合的直接减枝即可
        if(p[i].len >= XT) // 当前木板能够放下
            solve(i);
    }
    cout<<minmove<<' '<<mincut<<endl;
    return 0;
}
posted @ 2020-09-09 12:23  Hyx'  阅读(175)  评论(0编辑  收藏  举报