问题描述:
已知:给定一长度为3K(1≤K≤60)的整数序列S,将其划分为等长(各为K)的三个子序列S1、S2和S3,要求其中至少两个子序列各自的加和大于500*K
求:满足上述条件的一个划分(所给输入必有解)
输入:第1行为K的值;第2行至第3K+1行为序列S的元素值
输出:满足条件的序号划分
Sample Input:
2
510
500
500
500
670
400
310
Sample Output:
1
2
3
6
5
4
思路:
首先,只要S1、S2和S3中的两个其加和大于500*K即可,因此我们采用贪心策略,对输入序列S进行降序排序,将排序后的前2K个元素划分给S1和S2(假设最终满足条件的就是S1和S2),不再考虑S3
设S' = (S1, S2),现考虑采用随机化算法
第一种策略:循环地随机重排S‘中的所有元素,进行条件检测,若满足则退出循环输出结果;否则继续循环。这种策略会造成大量浪费,因为会出现如下情况:随机重排一次后,S1和S2各自包含的元素没有发生变化,导致此次重排没有意义。此外对整个S’进行重排也很耗时
第二种策略:每次分别在S1和S2中各随机选取一个元素进行互换,进行条件检测。这种策略能保证绝大部分的重排是有效的(除非本次随机选取的两个元素恰好是上次随机选取的那两个元素)
代码(第二种策略):
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <limits.h>
int a[200][2];
int k;
void sort(){
int n = 3*k;
for(int i = 0; i < n-1; i++){
int max = i;
for(int j = i+1; j < n; j++){
if(a[j][0] > a[max][0])
max = j;
}
if(max != i){
int tmp1 = a[max][0];
int tmp2 = a[max][1];
a[max][0] = a[i][0];
a[max][1] = a[i][1];
a[i][0] = tmp1;
a[i][1] = tmp2;
}
}
}
int main(){
scanf("%d", &k);
int floor = 500*k;
for(int i = 0; i < 3*k; i++){
scanf("%d", &a[i][0]);
a[i][1] = i;
}
sort();
int sum1 = 0;
int sum2 = 0;
for(int i = 0; i < k; i++){
sum1 += a[i][0];
sum2 += a[i+k][0];
}
srand((unsigned int)time(0));
while(!(sum1 > floor && sum2 > floor)){
int offset1 = rand()%k;
int offset2 = rand()%k;
sum1 = sum1-a[offset1][0]+a[k+offset2][0]; //注意先更新加和再互换元素
sum2 = sum2-a[k+offset2][0]+a[offset1][0];
int tmp1 = a[offset1][0];
int tmp2 = a[offset1][1];
a[offset1][0] = a[k+offset2][0];
a[offset1][1] = a[k+offset2][1];
a[k+offset2][0] = tmp1;
a[k+offset2][1] = tmp2;
}
for(int i = 0; i < 3*k; i++){
printf("%d\n", a[i][1]+1);
}
return 0;
}