卧薪尝胆,厚积薄发。
HNOI模拟 拔河
Date: Thu Sep 06 11:22:30 CST 2018 In Category: NoCategory

Description:

有 $ 2n $ 个人玩拔河,拔河的绳子由左右两段组成,每段绳子上有 $ n $ 个位置,第 $ i $ 个人可以在左边绳子的 $ l_i $ 位置处,也可以在右边绳子的 $ r_i $ 位置处。每个位置上有且仅有一个人。每个人有一个实力值 $ s_i $ ,问对于每一种合法方案两边实力值和之差的绝对值最小是多少。
$1\le n\le 30000,1\le s_i\le 15$

Solution:

把绳子左段看成左部点,右段看成右部点,那么一个人就是一条带权边,如果这个二分图中有点度数为 $1$ ,那么这个人的位置已经确定,否则如果合法,剩下的一定是一堆偶环,因为图是二分图,总共有 $n$ 个人,所以总共有 $2n$ 个度数,而又没有奇度点,且每个点都有连边,那么一定每个点度数都为 $2$ ,把这些偶环都找出来,并统计奇偶边,统计时可以把从左到右的边设为正,从右到左的边设为负,这样再用总数减两倍就可以得到在左边和右边的价值,这样可以 $DP$ ,枚举加到那边,但是这样很不好做,不妨把所有为正的值加起来,再把左右相减取绝对值,就可以变成选或不选,可以背包,但背包的复杂度是 $n\sum s_i$ 的,因为最多有 $O(n)$ 个物品,但我们如果设 $S$ 表示不同价值的物品数,那么最差情况是任意 $s$ 都不同,此时有 $\frac{S(S+1)}2\le 15\times 30000$ ,那么我们把相同权值的物品合起来暴力做多重背包,复杂度就被优化成了 $O(S\sum s)$ 。

Code:


#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n;
#define MAXN 60010
struct edge
{
int fr,to,nxt,v,id;
}e[MAXN << 1];
int edgenum = 0;
int lin[MAXN] = {0};
void add(int a,int b,int v,int id)
{
++edgenum;e[edgenum].fr = a;e[edgenum].to = b;e[edgenum].v = v;
e[edgenum].id = id;e[edgenum].nxt = lin[a];lin[a] = edgenum;
return;
}
int l[MAXN],r[MAXN],s[MAXN];
int maxsum;
int tot = 0,cnt,edg,sum;
int A[MAXN],B[MAXN],C[MAXN * 15];
bool f[MAXN * 15];
bool vis[MAXN];
void dfs(int k,int ine)
{
vis[k] = true;
for(int i = lin[k];i != 0;i = e[i].nxt)
{
if(e[i].id != ine)
{
if(vis[e[i].to] && e[i].id != e[edg].id)
{
++cnt;
edg = i;
}
else if(!vis[e[i].to])
{
s[e[i].to] = s[k] + e[i].v;
sum += e[i].v;
dfs(e[i].to,e[i].id);
}
}
}
return;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= 2 * n;++i)
{
scanf("%d%d%d",&l[i],&r[i],&s[i]);
add(l[i],r[i] + n,-s[i],i);
add(r[i] + n,l[i],s[i],i);
}
maxsum = 0;
int ans = 0,ansb = 0;
memset(s,0,sizeof(s));
for(int i = 1;i <= 2 * n;++i)
{
if(vis[i])continue;
cnt = edg = sum = 0;
dfs(i,0);
if(cnt != 1)
{
puts("-1");
return 0;
}
++tot;
A[tot] = sum - 2 * s[e[edg].fr] - e[edg].v;
B[tot] = sum - 2 * s[e[edg].to] + e[edg].v;
if(A[tot] > B[tot])swap(A[tot],B[tot]);
B[tot] -= A[tot];C[B[tot]]++;
maxsum += B[tot];ansb += A[tot];
}
ans = abs(ansb);
f[0] = true;
for(int i = 1;i <= maxsum;++i)
if(C[i])
for(int j = maxsum;j >= 0;--j)
if(f[j])
for(int k = 1;k <= C[i] && !f[i * k + j];++k)
f[i * k + j] = 1;
for(int i = 0;i <= maxsum;++i)
{
if(f[i])ans = min(ans,abs(ansb + i));
}
cout << ans << endl;
return 0;
}
In tag: DP-背包
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡