0%

[ABC332G] Not Too Many Balls

又是我没见过的新 trick?

应该也有别的做法吧。

题目链接

题意

现在有 种球和 个盒子,第 种球有 个,第 个箱子里最多放 个球。

除此之外,第 个箱子里最多放 个第 种球。

求最多能放多少个球。

题解

这个问题看上去就可以流,但是显然会 T。

我们不妨把这个图建出来。

这张图共有 个点,分别为源点 ,汇点 ,代表第 种小球的点 ,以及代表第 个箱子的点

边也分为三种:

  1. ,流量为
  2. ,流量为
  3. ,流量为

显然,直接跑是不行的。

众所周知,最大流与最小割相等,那么存不存在什么快速的方法,求出这张图的最小割呢?

为方便使用公式,我们首先定义 为左部点的集合, 为右部点的集合。

考虑枚举 中与源点 联通的点集 ,枚举 中与汇点 联通的点集 ,那么对于上述的三种边:

  1. 对于边 ,若 ,则需要割掉这条边,价值为
  2. 对于边 ,若 ,则需要割掉这条边,价值为
  3. 对于边 ,若 $x_iP
  4. y_jQ$,则需要割掉这条边,价值为

那么我们就可以将最小割的值用一个式子表示出来! 不太好算,考虑枚举 的值。 考虑枚举 的值,那么前面关于 的部分可以使用 dp 计算。

后面的值,对于每一个 来说都是一个分段函数,也是容易计算的。

时间复杂度

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
using namespace std;
using ll=long long;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline ll lread()
{
ll s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
ll prek[150001];
ll preb[150001];
ll dp[150001];
ll b[500001];
ll a[501];
int n,m,Lim;
int main()
{
n=read(),m=read(),Lim=(ll)n*(n+1)/2;
memset(dp,0x3f,sizeof(dp)),dp[Lim]=0;
for(int i=1;i<=n;i++)
{
a[i]=lread();
for(int j=0;j+i<=Lim;j++) dp[j]=min(dp[j],dp[j+i]+a[i]);
}
for(int j=1;j<=m;j++)
{
b[j]=lread();ll s=b[j]/j;
prek[1]+=j;
if(s<Lim) prek[s+1]-=j,preb[s+1]+=b[j];
}
ll ans=inf;
for(int i=0;i<=Lim;i++)
{
prek[i]+=prek[i-1],preb[i]+=preb[i-1];
ans=min(ans,dp[i]+prek[i]*i+preb[i]);
}
printf("%lld\n",ans);
return 0;
}

感谢观看!