洛谷P1282 多米诺骨牌

P1282 多米诺骨牌
题目描述
多米诺骨牌有上下2个方块组成,每个方块中有1~6个点。现有排成行的

上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|。例如在图8-1中,S1=6+1+1+1=9,S2=1+5+3+2=11,|S1-S2|=2。每个多米诺骨牌可以旋转180°,使得上下两个方块互换位置。 编程用最少的旋转次数使多米诺骨牌上下2行点数之差达到最小。

对于图中的例子,只要将最后一个多米诺骨牌旋转180°,可使上下2行点数之差为0。

输入输出格式
输入格式:
输入文件的第一行是一个正整数n(1≤n≤1000),表示多米诺骨牌数。接下来的n行表示n个多米诺骨牌的点数。每行有两个用空格隔开的正整数,表示多米诺骨牌上下方块中的点数a和b,且1≤a,b≤6。

输出格式:
输出文件仅一行,包含一个整数。表示求得的最小旋转次数。

输入输出样例
输入样例:
4
6 1
1 5
1 3
1 2
输出样例:
1

由于这题有负数,所以我们可以取一个较大的下标作为数组的基准值(也就是设x为0)
然后通过DP求解

先将所有骨牌的初始状态先求出来

cin>>n;
    int x,y,z;
    for(int i=1;i<=n;i++){
        cin>>x>>y;
        a[i]=x-y;//这里的a[i]就代表了多米诺骨牌的上下点数之差,因为翻转以后点数差会取负所以直接存这个差值就可以
        z+=a[i];//z求出来的即是初始的上下点数总差
    }

然后在这里我随便选了个20000作为基准数,只要保证在接下来的计算过程中下标不会变负即可
除了初始状态标记翻转次数为0以外其他值就用一个很大的数来表示无法达到

for(int i=0;i<40000;i++)s[i]=INF;
s[20000-z]=0;

接下来就是主要的DP过程了,而点数之差的正负也又要进入我们的考虑范围,相信大家在学01背包的时候都思考过下面这个循环:

for(int i=1;i<=n;i++){
    for(int j=m;j>=v[i];j--){//m为背包的容量
        s[j]=max(s[j],s[j-v[i]]+w[i]);//v[i]:物品的体积,w[i]:物品的价值
    }
}

当时一直不是很明白为什么j是从大到小循环,后来才知道如果是正着循环的话,当前循环中更新的s[j]会在接下来成为新的s[j’-v[i]],这就影响到了s[j’]的结果,而反过来循环就不会,还不懂的童鞋可以自己去试着推一下。
同理我们再来看这道题也是如此,略有不同的是多米诺骨牌翻转后对于整体来讲是改变了两倍的a[i]:

s[j]=min(s[j],s[j-2*a[i]]+1);

当a[i]为正的情况下,j-2*a[i]<j,我们要保证更新完s[j]后不会影响到本次循环其他的更新,因此我们从后往前循环,这里我的10000和30000都是随意取的,只是保证不越界。

if(a[i]<0){
    for(int j=10000;j<=30000;j++){
        s[j]=min(s[j],s[j-2*a[i]]+1);
    }
}

a[i]为负则反之

else if(a[i]>0){
    for(int j=30000;j>=10000;j--){
        s[j]=min(s[j],s[j-2*a[i]]+1);
    }
}

最后我们以之前选的基准数为起点,同时往前后推,若是遇见可以翻转到的点数,就输出次数并结束循环

for(int i=0;i<=6000;i++){
    if(s[20000+i]<1000||s[20000-i]<1000){
        cout<<min(s[20000+i],s[20000-i]);//这里还需判断一下正负点数哪种次数比较小
        return 0;
    }
}

完整代码:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <vector>
#include <string.h>
#include <string>
#include <queue>
#include <stack>
#include <map>
using namespace std;
#define FI first
#define SE second
#define PB push_back
#define PII pair<int,int>
#define LL long long int
#define DEBUG cout<<1<<endl;
const int INF=1e8;
int a[10101],s[40011];
int n;
int main()
{
    for(int i=0;i<40000;i++)s[i]=INF;
    cin>>n;
    int x,y,z;
    for(int i=1;i<=n;i++){
        cin>>x>>y;
        a[i]=x-y;
        z+=a[i];
    }
    s[20000-z]=0;
    for(int i=1;i<=n;i++){
        if(a[i]<0){
            for(int j=10000;j<=30000;j++){
                s[j]=min(s[j],s[j-2*a[i]]+1);
            }
        }
        else if(a[i]>0){
            for(int j=30000;j>=10000;j--){
                s[j]=min(s[j],s[j-2*a[i]]+1);
            }
        }
    }
    for(int i=0;i<=6000;i++){
        if(s[20000+i]<1000||s[20000-i]<1000){
            cout<<min(s[20000+i],s[20000-i]);
            return 0;
        }
    }
    return 0;
}

全部评论

相关推荐

不愿透露姓名的神秘牛友
2025-12-17 16:48
今天九点半到公司,我跟往常一样先扫了眼电脑,屁活儿没有。寻思着没事干,就去蹲了个厕所,回来摸出手机刷了会儿。结果老板刚好路过,拍了我一下说上班别玩手机,我吓得赶紧揣兜里。也就过了四十分钟吧,我的直属领导把我叫到小隔间,上来就给我一句:“你玩手机这事儿把老板惹毛了,说白了,你可以重新找工作了,等下&nbsp;HR&nbsp;会来跟你谈。”&nbsp;我当时脑子直接宕机,一句话都没憋出来。后面&nbsp;HR&nbsp;找我谈话,直属领导也在旁边。HR&nbsp;说我这毛病不是一次两次了,属于屡教不改,不光上班玩手机,还用公司电脑看论文、弄学校的事儿。我当时人都傻了,上班摸鱼是不对,可我都是闲得发慌的时候才摸啊!而且玩手机这事儿,从来没人跟我说过后果这么严重,更没人告诉我在公司学个习也算犯错!连一次口头提醒都没有,哪儿来的屡教不改啊?更让我膈应的是,昨天部门刚开了会,说四个实习生里留一个转正,让大家好好表现。结果今天我就因为玩手机被开了。但搞笑的是,开会前直属领导就把我叫去小会议室,明明白白告诉我:“转正这事儿你就别想了,你的学历达不到我们部门要求,当初招你进来也没打算给你这个机会。”合着我没入贵厂的眼是吧?可我都已经被排除在转正名单外了,摸个鱼至于直接把我开了吗?真的太离谱了!
rush$0522:转正名单没进,大概率本来就没打算留你
摸鱼被leader发现了...
点赞 评论 收藏
分享
稽鱼:简历好丑啊,换个模板,别用红色字体
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务