学习小组 洛谷P4209
这是一道比较优秀的技巧类题目。
我们通过观察数据范围,发现很小,首先排除贪心。
由于题目限制多而复杂,我们考虑使用网络流来描述这个问题。
考虑建立一个网络流框架,大概是我们要求每个能选小组的学生都至少选一个小组,可以使用最大流描述,但是题目有要求支出最少,因此考虑使用费用流进一步描述。
先考虑如何描述参加了一些学生的小组的支出,我们可以发现由于支出增长是平方倍级别的,因此无法直接用同一的费用描述。我们发现由于 \(C,F\) 是正数,因此不难证明有以下式子:
\[C_i \times (a+1)^2 - F_i \times (a+1) - C_i \times a^2 + F_i \times a > C_i \times a^2 - F_i \times a - C_i \times (a-1)^2 + F_i \times (a-1)
\]
即相邻参加人数增加所带来的花费变化量是单调递增的,我们考虑把他的花费进行差分,拆成 \(n\) 条边与汇点相连,根据贪心想法,跑费用流时一定会优先选较小的边,因此正确性可以保证。
另外,考虑如何限制每个学生至少选择一个。根据贪心想法,由于询问最少支出,我们考虑在什么时候学生会选择不止一个学习小组。显然,当且仅当多选择小组会让花费更小时我们才会多选,为了保证学生至少选一个我们考虑使用满流的方式限制。因此我们可以从每个学生往汇点连接一条容量为 \(k - 1\) ,花费为 \(0\) 的边,就可以限制这个学生至少选择一个小组,同时想要多选则花费必须比 \(0\) 更小。
至此,我们的建图满足了题目的所有限制条件,代码如下:
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9') {if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=(x<<3)+(x<<1)+(s^'0');s=getchar();}
x *= f;
}
const int MAXN = 1e5 + 5;
const int MAXM = 1e5 + 5;
const LL inf = 1e15;
int head[MAXN] , to[MAXM << 1] , nxt[MAXM << 1] , cnt = 1;
LL edge[MAXM << 1] , val[MAXM << 1];
void add(int u , int v , LL c , LL w) {
nxt[++cnt] = head[u];head[u] = cnt;to[cnt] = v;edge[cnt] = c;val[cnt] = w;
nxt[++cnt] = head[v];head[v] = cnt;to[cnt] = u;edge[cnt] = 0;val[cnt] = -w;
}
int las[MAXN] , pre[MAXN] , num , s , t , vis[MAXN];
LL dis[MAXN] , flow[MAXN];
struct MinCostMaxFlow {
LL MaxFlow , MinCost;
bool bfs() {
for (int i = 1; i <= num; ++i) las[i] = 0 , dis[i] = inf , pre[i] = 0 , flow[i] = 0 , vis[i] = 0;
flow[s] = inf , dis[s] = 0;
queue <int> q;q.push(s);
int flag = 0;
while(!q.empty()) {
int x = q.front();
q.pop();
vis[x] = 0;
for (int i = head[x]; i; i = nxt[i]) {
if(!edge[i]) continue;
int v = to[i];
if(dis[v] > dis[x] + val[i]) {
dis[v] = dis[x] + val[i];
flow[v] = min(flow[x] , edge[i]);
pre[v] = x;
las[v] = i;
if(v == t) {
flag = 1;
continue;
}
if(!vis[v]) vis[v] = 1 , q.push(v);
}
}
}
return flag;
}
void MVMC() {
MaxFlow = 0 , MinCost = 0;
while(bfs()) {
int now = t;
MaxFlow += flow[t];
MinCost += dis[t] * flow[t];
while(now != s) {
edge[las[now]] -= flow[t];
edge[las[now] ^ 1] += flow[t];
now = pre[now];
}
}
}
}MIN;
int n , m , k , id[MAXN] , C[MAXN];
int per[MAXN];
int main() {
read(n),read(m),read(k);
s = 1 , t = 2 , num = 2;
for (int i = 1; i <= m; ++i) {
read(C[i]);
id[i] = ++num;
}
for (int i = 1; i <= m; ++i) {
int F;
read(F);
for (int j = 1; j <= n; ++j) add(id[i] , t , 1 , C[i] * (2 * j - 1) - F);
}
for (int i = 1; i <= n; ++i) {
per[i] = ++num;
char s[105];
scanf("%s" , s + 1);
for (int j = 1; j <= m; ++j) {
if(s[j] == '1') add(per[i] , id[j] , 1 , 0);
}
}
for (int i = 1; i <= n; ++i) {
add(s , per[i] , k , 0);
add(per[i] , t , k - 1 , 0);
}
MIN.MVMC();
printf("%lld" , MIN.MinCost);
return 0;
}