P1248 加工生产调度&P2123 皇后游戏

P1248 加工生产调度
P2123 皇后游戏

Johnson 法则早就该会了……

一般地,设 \(c_i\) 表示完成第 \(i\) 个后的时间,得

\[c_i=\begin{cases} a_1+b_1 &(i=1)\\ \max\left(c_{i-1},\sum\limits_{j=1}^i a_j\right)+b_i &(i>1) \end{cases}\]

在调整法之前,有一个显然的结论:若 \(c_i\) 变小,其后的 \(c\) 都会变小。

之后考虑调整。令 \(T\) 表示前面数的 \(a\) 之和,\(P\) 表示 \(c_{i-1}\)

\(i\) 在前:\(\max(\max(P,T+a_i)+b_i,T+a_i+a_j)+b_j\)
\(j\) 在前:\(\max(\max(P,T+a_j)+b_j,T+a_i+a_j)+b_i\)
等价于比较
\(\max\{T+a_i+b_i+b_j,T+a_i+a_j+b_j\}\)
\(\max\{T+a_j+b_i+b_j,T+a_i+a_j+b_i\}\)

两式同减 \(T+a_i+a_j+b_i+b_j\)\(\max(-a_j,-b_i)\)\(\max(-a_i,-b_j)\),即 \(-\min(a_j,b_i)\)\(-\min(a_i,b_j)\)

分析可知当 \(\min(a_i,b_j)\leq \min(a_j,b_i)\)\(i\)\(j\) 前面更优。

照着上述思路,可以写出一份能 AC P1248 的代码。

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
char buf[1<<14],*p1=buf,*p2=buf;
#define GetC() ((p1==p2)&&(p2=(p1=buf)+fread(buf,1,1<<14,stdin),p1==p2)?EOF:*p1++)
struct Ios{}io;
template <typename _tp>
Ios &operator >>(Ios &in,_tp &x){
	x=0;int w=0;char c=GetC();
	for(;!isdigit(c);w|=c=='-',c=GetC());
	for(;isdigit(c);x=x*10+(c^'0'),c=GetC());
	if(w) x=-x;
	return in;
}
const int N=1e5+5;
struct qwq{int a,b,id;}a[N];
bool operator <(qwq x,qwq y){return min(x.a,y.b)<min(y.a,x.b);}
int main(){
	int n;io>>n;
	for(int i=1;i<=n;++i) io>>a[i].a;
	for(int i=1;i<=n;++i) io>>a[i].b,a[i].id=i;
	sort(a+1,a+n+1);
	long long s1=a[1].a,s2=a[1].a+a[1].b;
	for(int i=2;i<=n;++i){
		s1+=a[i].a;
		s2=max(s2,s1)+a[i].b;
	}
	printf("%lld\n",s2);
	for(int i=1;i<=n;++i) printf("%d%c",a[i].id," \n"[i==n]);
	return 0;
}

这么做过不了P2123。

首先要说明的一点是,仅仅保证相邻两个之间满足 “\(\leq\)”关系是没用的,因为这只能保证一次交换后不优,无法保证全局。

注意到上述所说的任意相邻两个元素满足 \(\min(a_i,b_j)\leq \min(a_j,b_i)\) 只能保证一定存在一个最优解满足条件,而不是最优解的充分条件,因此需要强化限制,即 \(\forall i<j,\min(a_i,b_j)\leq \min(a_j,b_i)\),这样,我们才可以用类似插入排序的调整法证明产生的一定是最优解。

接下来的内容来自 @ouuan 浅谈邻项交换排序的应用以及需要注意的问题 一文。

hack 数据见此:

2
4
1 1
1 1
3 5
2 7
4
1 1
3 5
1 1
2 7

原因在于两次排序结果分别为

1 1
1 1
2 7
3 5

1 1
3 5
1 1
2 7

这说明直接 sort 只能保证相邻的满足“\(<\)”而不能任意两个都满足,原因在于 STL 要求比较函数满足“严格弱序”。

对于一个比较运算符(用“\(<\)”表示此运算符,用“\(\not <\)”表示不满足此运算符),若满足以下四个条件,则称其是满足严格弱序的:

  1. \(x\not< x\)(非自反性)
  2. \(x<y\) ,则 \(y\not <x\)(非对称性)
  3. \(x<y,y<z\),则 \(x<z\)(传递性)
  4. \(x\not<y,y\not<x,y\not < z,z\not<y\),则 \(x\not <z,z\not <x\)(不可比性的传递性)

可以证明,我们定义的 “\(<\)” 运算具有传递性,但不具有不可比性的传递性。

换句话说,可能会出现较大的数出现在前面的情况。然后就不是最优了。

所以,必须保证具有不可比性的传递性。
一个取巧的方法见下:

定义 \(d_i=\begin{cases}-1 & a_i<b_i\\0 &a_i=b_i\\ 1&a_i>b_i\\\end{cases}\)

先按 \(d\) 排。对于 \(d_i=-1\) 的元素,可以按照 \(a\) 从小往大排,对于等于 \(0\) 的元素,随便排,对于 \(d_i=1\) 的元素,按照 \(b\) 从大往小排。这玩意叫 Johnson 法则。

其实,有一个更简单的方法:当出现“不可比”的情况时,以 \(a\) 作为第二关键字升序排序。

如何检验正确性?注意到严格弱序的定义只与三个数有关,考虑到这只与大小有关,而任意情况离散化后的值最多 \(6\) 个,因此暴力枚举这 \(6\) 个数的 \(6\) 个值,逐一检查是否满足上述四条即可。

以下为原 blog 中的检验器:

#include <cstdio>
#include <algorithm>

using namespace std;

bool cmp(int i,int j);

int a[10],b[10];

int main()
{
    for (a[0]=1;a[0]<=6;++a[0])
    {
        for (b[0]=1;b[0]<=6;++b[0])
        {
            if (cmp(0,0))
            {
                printf("No irreflexivity:%d %d\n",a[0],b[0]);
            }
            for (a[1]=1;a[1]<=6;++a[1])
            {
                for (b[1]=1;b[1]<=6;++b[1])
                {
                    if (cmp(0,1)&&min(a[0],b[1])>min(a[1],b[0]))
                    {
                        printf("Not the best:%d %d %d %d\n",a[0],b[0],a[1],b[1]);
                    }
                    for (a[2]=1;a[2]<=6;++a[2])
                    {
                        for (b[2]=1;b[2]<=6;++b[2])
                        {
                            if (cmp(0,1)&&cmp(1,2)&&!cmp(0,2))
                            {
                                printf("No transitivity:%d %d %d %d %d %d\n",a[0],b[0],a[1],b[1],a[2],b[2]);
                            }
                            if (!cmp(0,1)&&!cmp(1,0)&&!cmp(1,2)&&!cmp(2,1)&&(cmp(0,2)||cmp(2,0)))
                            {
                                printf("No transitivity of incomparability:%d %d %d %d %d %d\n",a[0],b[0],a[1],b[1],a[2],b[2]);
                            }
                        }
                    }
                }
            }
        }
    }

    return 0;
}

bool cmp(int i,int j)
{
    return min(a[i],b[j])==min(a[j],b[i])?a[i]<a[j]:min(a[i],b[j])<min(a[j],b[i]);
}

posted @ 2022-12-10 18:50  pref_ctrl27  阅读(19)  评论(0编辑  收藏  举报