UVa 1297 - The Minimum Number of Rooks

23 篇文章 0 订阅

这确实是一道可以被放在训练指南advanced分类里的dp题


提示:

1. 强烈建议自己尝试去手动找答案 , 这能帮你理解"车"攻击的特点

( 想象一下, 把整个图横向剖分成数行 , 然后把每一行当成一个列的区间 )

2. 我构造的dp方程是基于两点贪心:

每一行至多一个车

可以构造这样的最优答案 , 行数越大的车列数也越大 , 即所有车呈现一个斜向右下的一个形状 , 可以反证说明这一点 , 注意 , 并不是所有最优解都满足这一贪心性质 , 但是一定可以构造这样的最优解(考虑去交换两个不符合这个贪心性质的车的列号)

3. 再进一步 , 对于一行方格 , 要么这一行有一个车 ,要么没有车 ,如果这一行没有车我称其为"空行",对于任意一个空行 , 这一行中所包含的所有列上肯定有一个车(想清楚再继续),也可以说前面或者后面的车可以涵盖这一行中所有的格子(仔细看看样例图 , 自行脑补) ,


4. dp 方程的定义比较奇妙

dp [ i ] [ q1 ] [ q2 ] [ k ] , 首先其中i是行数 , 表示处理到前i行    (但完全可以用滚动数组) , k就是一个标志系数仅为0或者1


为了进一步解释 q1 q2 k 的含义 , 简单讲一下dp的过程 , 在处理任意一行的时候会有两个状态 , 是否“欠债” , “欠债”就是前i 行中有一些行是空行 , 但是却没有被完全覆盖 , 这样我就需要知道哪些列没有被覆盖 , 于是我就在状态中引入了一个区间 [ q1 , q2 ] ,在 k == 0的情况下表示目前需要被覆盖的列号 , 但为什么只有一个区间呢?


因为第二个贪心性质: 想象你现在正在处理第二行 , 而第一行是空行 , 这时如果你选择去覆盖第一行中的格子那么你一定只能选择第二行第一列  (看不懂回到第二点提示)

于是可以用这种思路去证明至多有一个没有覆盖的区间

(之所以把选择加粗 , 是因为你还可以把第二行也空着)


k==1的时候表示目前的车覆盖了[ q1 , q2 ] 中的所有列 , 同理考虑多个区间是没有意义的 (因为所覆盖的列只有连续才有意义)


dp 数值上都表示符合这个状态下最小的车的数目

dp还要记录一个from数组 , 这个数组在转移方程中会提到


5. dp 的转移( 这个dp向前刷表 )

我写不出一个明了的转移方程 , 因为有很多特例 , 讲几个最典型的转移 ,其它的见代码。

用s [ i ] , t [ i ] 表示第i行的左端点和右端点


d [ i ] [ q1 ] [ q2 ] [ 0 ] 在考虑i+1行时 , 可以把 i+1 行也空下来 , 即更新 d [ i+1 ] [ q1 ] [ t[i+1] ] [ 0 ] 

当然还可以把车放在第i+1行, 第q1列  (提示4解释了为什么) , 如果此时q1 == q2 那么显然 “债还清” 了 , 然而这个时候出现问题了 , 我现在到底该更新谁呢? 注意 , 在还债的途中其实你已经覆盖了一个区间了 , 这个区间的大小至少是上一个空行的大小 , 如果我知道这个连续的区间从哪里开始的 , 我就可以更新 d[ i+1 ] [ 上一波开始还债的位置 ]  [ q2 ] [ 1 ] , 于是我就用一个from数组来记录还债开始的时候第一个空白行的左端点值

当k == 1的时候情形是类似的


//
//  main.cpp
//  UVa1297
//
//  Created by Fuxey on 15/10/21.
//  Copyright © 2015年 corn.crimsonresearch. All rights reserved.
//

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <vector>
#include <deque>
#include <set>
#include <map>
#include <algorithm>

using namespace std;
const int maxn = 120;


int s[maxn] , t[maxn];
int d[maxn][maxn][maxn][2];
int from[maxn][maxn][maxn][2];

bool  update(int& a , int b)
{
    if(a==-1 || a>b) { a = b; return true; }
    return false;
}

int main(int argc, const char * argv[]) {
    
    vector<pair<int, int> > res;
    
    int a , b , Case=0;
    while(cin>>a>>b && a+b)
    {
        res.clear();
        res.push_back(make_pair(a , b));
        while(cin>>a>>b && a+b) res.push_back(make_pair(a, b));
        
        memset(s, -1, sizeof(s));
        memset(t, -1, sizeof(t));
        
        for(int i=1;i<res.size();i+=2)
            if(res[i].first>res[i+1].first) break;
            else
                for(int j=res[i].first;j<res[i+1].first;j++)
                    t[j] = res[i].second-1;
        for(int i=(int)res.size()-2;i>=0;i-=2)
            if(res[i].first>res[i-1].first) break;
            else
                for(int j=res[i].first;j<res[i-1].first;j++)
                    s[j] = res[i].second;
        for(int j=res[0].first;j<res.back().first;j++)
            s[j] = res[0].second;
        
        int start;
        for(start=1;start<=100 && s[start]==-1;start++);
        
        memset(d, -1, sizeof(d));
        for(int i=s[start];i<=t[start];i++) d[start][i][i][1] = 1 , from[start][i][i][1] = i;
        d[start][s[start]][t[start]][0] = 0;
        
        for(int i=start;i<=100 && s[i+1]!=-1;i++) for(int q1=1;q1<=t[i];q1++) for(int q2=q1;q2<=t[i];q2++) for(int k=0;k<2;k++)
            if(d[i][q1][q2][k]!=-1)  // now lets decide i+1
            {
                int snow = s[i+1] , tnow = t[i+1] , f = from[i][q1][q2][k] , now = d[i][q1][q2][k];
                if(k==0)
                {
                    // fill the q1+1
                    if(q1>=snow)
                    {
                        if(q1==q2) { if(update(d[i+1][f][q1][1] , now+1)) from[i+1][f][q1][1] = f; }
                        else { if(update(d[i+1][q1+1][q2][0] , now+1)) from[i+1][q1+1][q2][0] = f; }
                    }
                    else continue;
                    // continue to stay empty
                    int Right = max(q2, tnow);
                    if(update(d[i+1][q1][Right][0] , now)) from[i+1][q1][Right][0] = f;
                }
                else
                {
                    // fill q2 or q2+1
                    if(update(d[i+1][q1][q2][1] , now+1)) from[i+1][q1][q2][1] = f;
                    if(q2+1<=t[i+1] && update(d[i+1][q1][q2+1][1] , now+1)) from[i+1][q1][q2+1][1] = f;
                    
                    // or Lets simply do it again (refill it)
                    for(int nq = q2+2;nq<=t[i+1];nq++) if(update(d[i+1][nq][nq][1] , now+1)) from[i+1][nq][nq][1] = nq;
                    
                    // start to empty
                    if(snow>=q1)
                    {
                        if(q2>=tnow) { if(update(d[i+1][q1][q2][1] , now)) from[i+1][q1][q2][1] = f; }
                        else if(update(d[i+1][max(snow,q2+1)][tnow][0] , now)) from[i+1][max(snow,q2+1)][tnow][0] = f;
                    }
                }
            }
        while(s[start+1]!=-1) start++;
        int ans = 1<<29;
        for(int i=1;i<=100;i++) for(int j=1;j<=100;j++) if(d[start][i][j][1]!=-1)
            ans = min(ans , d[start][i][j][1]);
        cout<<++Case<<" "<<ans<<endl;
    }
    
    
    
    return 0;
}

注意细节

1 1 1 4 5 4 5 7 7 7 7 10 9 10 9 6 6 6 6 3 4 3 4 1 0 0 

答案是5 , 不是6


给出一组你可能出bug的数据: 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值