P1248 加工生产调度&P2123 皇后游戏
Johnson 法则早就该会了……
一般地,设 \(c_i\) 表示完成第 \(i\) 个后的时间,得
在调整法之前,有一个显然的结论:若 \(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 <\)”表示不满足此运算符),若满足以下四个条件,则称其是满足严格弱序的:
- \(x\not< x\)(非自反性)
- 若 \(x<y\) ,则 \(y\not <x\)(非对称性)
- 若 \(x<y,y<z\),则 \(x<z\)(传递性)
- 若 \(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]);
}