初见安~这里是传送门:洛谷P1489 猫狗大战
题解
一个挺冷门的题啊……也蛮有意思的。
把n个数划分成两部分,让两部分的数的总和的差值尽量小。
其实一眼dp,但是要往正解的方向去想是有点点难度的——明显我们确定一个集合,另一个集合也就可以确定了。思考暴力一点的做法:如果我们可以枚举一个集合的所有可能性,是不是就可以解出这个题了?换言之,我们要处理出这n个数可以凑出哪些数值。同时,在题目的“一半人数”的要求下,我们dp的两维,一个是人数,一个数数值。这很明显就是一个01背包了。完结。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 205
using namespace std;
typedef long long ll;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, a[maxn], sum = 0;
bool dp[maxn][8005];
signed main() {
n = read(); dp[0][0] = true;
for(int i = 1; i <= n; i++) a[i] = read(), sum += a[i];
for(int i = 1; i <= n; i++)
for(int j = n / 2; j > 0; j--)
for(int k = sum; k >= a[i]; k--) dp[j][k] |= dp[j - 1][k - a[i]];
//01背包,状态从后往前推移
int ans = 99999999;
for(int i = 0; i <= sum; i++) if(dp[n >> 1][i]) {//在n/2的人数下得到i。
ans = min(ans, abs(i - sum + i));
}
printf("%d %d\n", sum - (sum + ans) / 2, (sum + ans) / 2);
return 0;
}
下面是解法二——
如果思路纠结于划分方案呢?似乎就算是在确定了两个集合人数的情况下我们主动枚举方案的复杂度也有点高。所以考虑化主动为被动——在确定了方案的情况下比较,如何?也就是随机化算法。我们用到一个随机函数random_shuffle(begin, end),作用是打乱一个数组内的元素,我们就不断打乱我们划分方案的数组,取最优解即可。
rand的次数建议是通过时间复杂度上限进行计算,最多1.5e5,其实5e5就够了:)
上代码——
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 205
using namespace std;
typedef long long ll;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, num[maxn], ans1 = 99999999, ans2 = -99999999;
bool pt[maxn];
signed main() {
n = read();
for(int i = 1; i <= n; i++) num[i] = read();
for(int i = 1; i <= (n >> 1); i++) pt[i] = true;//初始化n/2个人的集合为1,其余为0
for(int x = 1; x <= 50005; x++) {//rand
random_shuffle(pt + 1, pt + 1 + n);
int sum1 = 0, sum2 = 0;
for(int i = 1; i <= n; i++) if(pt[i]) sum1 += num[i]; else sum2 += num[i];
if(abs(sum1 - sum2) < abs(ans1 - ans2)) ans1 = sum1, ans2 = sum2;
}
printf("%d %d\n", min(ans1, ans2), max(ans1, ans2));
return 0;
}
题意挺经典的,但是解法很有意思~
迎评:)
——End——