阅读视图

发现新文章,点击刷新页面。
🔲 ☆

【2020HDU多校】第二场1005(HDU6767)New Equipments——费用流

题目链接

题目大意

给出 $n$ 个工人和 $m$ 件装备,装备的编号为 $1, 2, 3 … m$。
对于工人 $i$ ,他有三个参数 $a_i, b_i, c_i$,当为这个工人装备了第 $j$ 个装备时,需要花费 $a_i j ^ 2+ b_i j + c_i$ 的费用。
当为 $k$ 个工人装备上装备时,最小花费是多少。
对所有的 $k$ 的情况均需要输出

分析

费用流
将每个员工与源点链接,取每个员工的二次函数曲线的最小值附近的 $n$ 个点,与员工相连,所有的在二次函数曲线上的点与汇点相连,员工连接到二次函数的线需要设定费用,其他线费用均为 $0$,所有线的流量均为 $1$

输出每次 spfa 过程中得到的费用即可

AC code

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <bits/stdc++.h>

using namespace std;

#define ll long long

const ll maxn = 3000;
const ll INF = 0x3f3f3f3f3f3f3f3f;

bool flag;

struct Edge {
ll from, to, cap, flow, cost;

Edge(ll u, ll v, ll c, ll f, ll cc)
: from(u), to(v), cap(c), flow(f), cost(cc) {}
};

struct MCMF {
ll n, m;
vector<Edge> edges;
vector<ll> G[maxn];
ll inq[maxn]; //是否在队列中
ll d[maxn]; //bellmanford
ll p[maxn]; //上一条弧
ll a[maxn]; //可改进量
void init(ll nn) {
this->n = nn;
for (ll i = 0; i <= n; ++i) G[i].clear();
edges.clear();
}

void addEdge(ll from, ll to, ll cap, ll cost) {
edges.emplace_back(Edge(from, to, cap, 0, cost));
edges.emplace_back(Edge(to, from, 0, 0, -cost));
m = (ll) (edges.size());
G[from].emplace_back(m - 2);
G[to].emplace_back(m - 1);
}

bool spfa(ll s, ll t, ll &flow, ll &cost) {
for (ll i = 1; i <= n; ++i) d[i] = INF;
memset(inq, 0, sizeof(inq));
d[s] = 0;
inq[s] = 1;
p[s] = 0;
queue<ll> q;
a[s] = INF;
q.push(s);
while (!q.empty()) {
ll u = q.front();
q.pop();
inq[u] = 0;
for (ll i = 0; i < (ll) (G[u].size()); ++i) {
Edge &e = edges[G[u][i]];
if (e.cap > e.flow && d[e.to] > d[u] + e.cost) {
d[e.to] = d[u] + e.cost;
p[e.to] = G[u][i];
a[e.to] = min(a[u], e.cap - e.flow);
if (!inq[e.to]) {
q.push(e.to);
inq[e.to] = 1;
}
}
}
}
if (d[t] == INF) return false;
flow += a[t];
cost += (ll) d[t] * (ll) a[t];
cout << (flag ? " " : "") << cost;
flag = true;
for (ll u = t; u != s; u = edges[p[u]].from) {
edges[p[u]].flow += a[t];
edges[p[u] ^ 1].flow -= a[t];
}
return true;
}

ll MincostMaxflow(ll s, ll t, ll &cost) {
ll flow = 0;
cost = 0;
while (spfa(s, t, flow, cost));
return flow;
}
} mcmf;

struct Node {
ll a, b, c;
ll l, r;

ll cal(ll x) {
return a * x * x + b * x + c;
}

void make(ll n, ll m) {
ll mid = -b / 2 / a;
l = mid - n / 2 - 1;
r = mid + n / 2 + 1;
l = max(1ll, l);
r = min(m, r);
if (r == m) {
l = r - n - 2;
} else if (l == 1) {
r = l + n + 2;
}
}
} node[60];

void solve() {
ll T;
cin >> T;
for (ll ts = 0; ts < T; ++ts) {
flag = false;
unordered_map<ll, ll> trans;
ll n, m;
cin >> n >> m;
ll ind = 51;
for (ll i = 1; i <= n; ++i) {
cin >> node[i].a >> node[i].b >> node[i].c;
node[i].make(n, m);
ll l = node[i].l;
ll r = node[i].r;
assert(r - l < 60);
for (ll j = l; j <= r; ++j) {
if (trans.count(j)) continue;
trans.insert({j, ind++});
}
}
ll source = ind + 10, target = ind + 11;
mcmf.init(target + 10);
#ifdef ACM_LOCAL
cerr << target + 10 << endl;
#endif
for (auto &item : trans)
mcmf.addEdge(item.second, target, 1, 0);
for (ll i = 1; i <= n; ++i) {
mcmf.addEdge(source, i, 1, 0);
for (ll j = node[i].l; j <= node[i].r; ++j) {
mcmf.addEdge(i, trans[j], 1, node[i].cal(j));
}
}
ll cost;
mcmf.MincostMaxflow(source, target, cost);
cout << endl;
}
}

signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
signed test_index_for_debug = 1;
char acm_local_for_debug;
while (cin >> acm_local_for_debug) {
if (acm_local_for_debug == '$') exit(0);
cin.putback(acm_local_for_debug);
if (test_index_for_debug > 20) {
throw runtime_error("Check the stdin!!!");
}
auto start_clock_for_debug = clock();
solve();
auto end_clock_for_debug = clock();
cout << "Test " << test_index_for_debug << " successful" << endl;
cerr << "Test " << test_index_for_debug++ << " Run Time: "
<< double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
}
#else
solve();
#endif
return 0;
}

🔲 ☆

【2019多校第一场补题 / HDU6578】2019多校第一场A题1001Blank——dp

HDU6578链接

题意

有一串字符串,仅由 ${0, 1, 2, 3}$ 组成,长度为 $n$,同时满足 $m$ 个条件。每个条件由三个整数组成:$l、r、x$ 表示在这个字符串的 $[l, r]$ 这个区间内,有且仅有 $x$ 个不同的字符,求问可能的组合有多少种(mod 998244353)

分析题意

因为前几天刚刚写了牛客暑期多校第二场,其中有一道题:ABBA(我的题解)感觉有点接近,所以第一想法就是dp了。但是这道题的字符多,不能像ABBA一样压缩至一维。所以只能想想看当时牛客官方给的题解的方法了。
考虑到之后需要判断条件是否满足,所以我第一感觉就是得定义一个超多维度的dp数组:

1
2
const int MAXN = 110;
long long dp[MAXN][MAXN][MAXN][MAXN][MAXN];

各个维度的定义如下:

对于 dp[a][b][c][d][t]
表示整个字符串长度为 t ,最后一次出现 0 的位置为 a,最后一次出现 1 的位置为 b ,最后一次出现 2 的位置为 c ,最后一次出现 3 的位置为d

然后可以得到状态转移方程

1
2
3
4
dp[t + 1][b][c][d][t + 1] += dp[a][b][c][d][t];
dp[a][t + 1][c][d][t + 1] += dp[a][b][c][d][t];
dp[a][b][t + 1][d][t + 1] += dp[a][b][c][d][t];
dp[a][b][c][t + 1][t + 1] += dp[a][b][c][d][t];

当然这个数组肯定是没法开的,所以把 t 压缩了,变成滚动dp
1
2
const int MAXN = 110;
long long dp[MAXN][MAXN][MAXN][MAXN][2];

然后通过滚动的方式来实现。
但是这并不是卡死这种方法的原因。
根据上面这些,可以写出整个dp过程,大概就是这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for (int t = 1; t <= n; t++) {
for (int a = 0; a <= t; a++)
for (int b = 0; b <= t; b++)
for (int c = 0; c <= t; c++)
for (int d = 0; d <= t; d++)
dp[a][b][c][d][t & 1] = 0;
for (int a = 0; a <= t; a++)
for (int b = 0; b <= t; b++)
for (int c = 0; c <= t; c++)
for (int d = 0; d <= t; d++) {
dp[t + 1][b][c][d][t & 1] += dp[a][b][c][d][t ^ 1];
dp[a][t + 1][c][d][t & 1] += dp[a][b][c][d][t ^ 1];
dp[a][b][t + 1][d][t & 1] += dp[a][b][c][d][t ^ 1];
dp[a][b][c][t + 1][t & 1] += dp[a][b][c][d][t ^ 1];
}
}

这个复杂的……这还没加上判断是否满足条件……
无论是时间是还是空间上,估计都悬(时间上应该是一定过不了了)
然后只能继续压缩。
考虑到最后无论哪种状态下,$a, b, c, d$ 四个变量中,必定有一个且仅有一个变量的值为 t 。而且在这道题中,字符 ${0, 1, 2, 3}$ 完全等价。所以我们再压缩一维
1
2
const int MAXN = 110;
long long dp[MAXN][MAXN][MAXN][2];

此时的意义如下:

对于变量 dp[x][y][z]
表示字符 ${0, 1, 2, 3}$ 中,其中一个最后出现的位置为当前的字符最后(由于这个条件恒成立,所以并未被记录在数组中),剩下的三个字符分别出现在$x, y, z$处,且保证 $i < x \leq y \leq z (i 为当前字符长度)$ (仅当 $x = y = 0$ 时满足前面一个等于号,后面的等于号同理。而字符串长度至少为1,且此时$x、y、z$均为0,所以不存在 $i = x$ 的情况。)而后面的长度为 2 的维度指代当前状态和 上一个状态(滚动dp)

可以得到状态转移方程:

1
2
3
4
5
// i 为当前字符串长度,cur 为当前状态,last 为上一个状态
dp[x][y][z][cur] += dp[x][y][z][last];// 加入的字符与上一个加入的字符相同
dp[i - 1][y][z][cur] += dp[x][y][z][last];
dp[i - 1][x][z][cur] += dp[x][y][z][last];
dp[i - 1][x][y][cur] += dp[x][y][z][last];

得到整个 dp 过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dp[0][0][0][0] = 1;
int cur = 1;
int last = 0;
for (int i = 1; i <= n; i++)
for (int x = 0; x <= i; x++)
for (int y = 0; y <= x; y++)
for (int z = 0; z <= y; z++)
dp[x][y][z][cur] = 0;
for (int x = 0; x < i; x++)
for (int y = 0; y <= x; y++)
for (int z = 0; z <= y; z++) {
dp[x][y][z][cur] += dp[x][y][z][last];
dp[i - 1][y][z][cur] += dp[x][y][z][last];
dp[i - 1][x][z][cur] += dp[x][y][z][last];
dp[i - 1][x][y][cur] += dp[x][y][z][last];

dp[x][y][z][cur] %= mod; // 别忘了 mod
dp[i - 1][y][z][cur] %= mod;
dp[i - 1][x][z][cur] %= mod;
dp[i - 1][x][y][cur] %= mod;
}
swap(cur, last);

考虑条件
需要判断一个区间内是否满足有多个不同的字符
我们可以根据区间右端为基准,当前dp的字符串长度到达一个条件的右端的时候,通过 $x、y、z$ 的值来判断是否到达了要求,如果没有则将此 dp 的赋值为0。如果懒得思考可以直接分类讨论一下就行了。虽然代码会比较长。

AC代码

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <bits/stdc++.h>

using namespace std;

const int MAXN = 110;
const int mod = 998244353;

long long dp[MAXN][MAXN][MAXN][2];
// dp[i][j][k] 表示上一次出现不同数字的位置分别是 i、j、k、当前位置

struct Conditions {
int l, x;

Conditions(int ll, int xx) : l(ll), x(xx) {}
};

vector <Conditions> conditions[MAXN]; // 用来保存要求

int main() {
#ifdef ACM_LOCAL
freopen("./in.txt", "r", stdin);
freopen("./out.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
int a, b, c;
for (int i = 0; i < MAXN; i++) {
conditions[i].clear();
}
for (int i = 0; i < m; i++) {
cin >> a >> b >> c;
conditions[b].push_back(Conditions(a, c)); // 要求按照 r 的不同来保存
}
dp[0][0][0][0] = 1;
int cur = 1;
int last = 0;
for (int i = 1; i <= n; i++) {
for (int x = 0; x <= i; x++) {
for (int y = 0; y <= x; y++) {
for (int z = 0; z <= y; z++) {
dp[x][y][z][cur] = 0;
}
}
}
for (int x = 0; x < i; x++) {
for (int y = 0; y <= x; y++) {
for (int z = 0; z <= y; z++) {
dp[x][y][z][cur] += dp[x][y][z][last];
dp[i - 1][y][z][cur] += dp[x][y][z][last];
dp[i - 1][x][z][cur] += dp[x][y][z][last];
dp[i - 1][x][y][cur] += dp[x][y][z][last];

dp[x][y][z][cur] %= mod;
dp[i - 1][y][z][cur] %= mod;
dp[i - 1][x][z][cur] %= mod;
dp[i - 1][x][y][cur] %= mod;
}
}
}
for (int s = 0; s < conditions[i].size(); s++) {
for (int x = 0; x < i; x++) {
for (int y = 0; y <= x; y++) {
for (int z = 0; z <= y; z++) {
int cnt = 1 + (x >= conditions[i][s].l ? 1 : 0) + (y >= conditions[i][s].l ? 1 : 0) +
(z >= conditions[i][s].l ? 1 : 0); // 判断剩下三个位置是否满足条件
if (cnt != conditions[i][s].x)
dp[x][y][z][cur] = 0;
}
}
}
}
swap(cur, last);
}
long long ans = 0; // 求算最终答案。需要把所有的情况都加起来
for (int x = 0; x < n; x++) {
for (int y = 0; y <= x; y++) {
for (int z = 0; z <= y; z++) {
ans += dp[x][y][z][last];
ans %= mod;
}
}
}
cout << ans << endl;
}
return 0;
}
🔲 ☆

HDU2883 kebab——最大流

题目链接

把“时间粒子”作为最大流的计算结果

设置超级源点为 0
顾客点范围为 1 - 204
时间点 205 - 610
超级汇点 615
超级源点与所有顾客连线,容量为需求的烤肉数 * 需求的每块烤肉的时间(即此顾客需要占用的总时间粒子)
顾客与时间点进行连线,仅当此时间点在顾客等待的时间段内,容量为INF
每个时间点与汇点连线,容量为 m

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <bits/stdc++.h>

using namespace std;

/*
* 最大流 SAP 算法,用 GAP 优化后
* 先把流量限制赋值到 maps 数组
* 然后调用 SAP 函数求解
* 可选:导出路径
*/

#define MAXN 620

int maps[MAXN][MAXN]; // 存图
int pre[MAXN]; // 记录当前点的前驱
int level[MAXN]; // 记录距离标号
int gap[MAXN]; // gap常数优化

// vector<int> roads[MAXN]; // 导出的路径(逆序)
// int curRoads; // 导出的路径数

// 入口参数vs源点,vt汇点
int SAP(int vs, int vt) {
memset(pre, -1, sizeof(pre));
memset(level, 0, sizeof(level));
memset(gap, 0, sizeof(gap));
gap[0] = vt;
int v, u = pre[vs] = vs, maxflow = 0, aug = INT_MAX;
// curRoads = 0;
while (level[vs] < vt) {
// 寻找可行弧
for (v = 1; v <= vt; v++) {
if (maps[u][v] > 0 && level[u] == level[v] + 1) {
break;
}
}
if (v <= vt) {
pre[v] = u;
u = v;
if (v == vt) {
// int neck = 0; // Dnic 多路增广优化,下次增广时,从瓶颈边(后面)开始
aug = INT_MAX;
// 寻找当前找到的一条路径上的最大流 (瓶颈边)
for (int i = v; i != vs; i = pre[i]) {
// roads[curRoads].push_back(i); // 导出路径——可选
if (aug > maps[pre[i]][i]) {
aug = maps[pre[i]][i];
// neck = i; // Dnic 多路增广优化,下次增广时,从瓶颈边(后面)开始
}
}
// roads[curRoads++].push_back(vs); // 导出路径——可选
maxflow += aug;
// 更新残留网络
for (int i = v; i != vs; i = pre[i]) {
maps[pre[i]][i] -= aug;
maps[i][pre[i]] += aug;
}
// 从源点开始继续搜
u = vs;
// u = neck; // Dnic 多路增广优化,下次增广时,从瓶颈边(后面)开始
}
} else {
// 找不到可行弧
int minlevel = vt;
// 寻找与当前点相连接的点中最小的距离标号
for (v = 1; v <= vt; v++) {
if (maps[u][v] > 0 && minlevel > level[v]) {
minlevel = level[v];
}
}
gap[level[u]]--; // (更新gap数组)当前标号的数目减1;
if (gap[level[u]] == 0)
break; // 出现断层
level[u] = minlevel + 1;
gap[level[u]]++;
u = pre[u];
}
}
return maxflow;
}

// 超级源点 0
// 顾客点 1 - 204
// 时间点 205 - 610
// 超级汇点 615
int n, m;
const int MaxPeople = 210;
const int Ed = 615;
struct costman {
int b, e, need, time;
};
set<int> timelist;
costman costmanlist[MaxPeople];

void init() {
memset(maps, 0, sizeof(maps));
set<int>::iterator iterl = timelist.begin();
set<int>::iterator iterr = timelist.begin();
iterr++;
int curiter = 0;
for (size_t i = 0; i < n; i++) {
maps[0][i + 1] = costmanlist[i].need * costmanlist[i].time;
}
while (iterr != timelist.end()) {
maps[205 + curiter][Ed] = ((*iterr) - (*iterl)) * m;
for (size_t i = 0; i < n; i++) {
if (costmanlist[i].b <= *iterl && costmanlist[i].e >= *iterr) {
maps[i + 1][curiter + 205] = INT_MAX;
}
}
iterl++;
iterr++;
curiter++;
}
}

int main() {
#ifdef ACM_LOCAL
freopen("./in.txt", "r", stdin);
freopen("./out.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
while (cin >> n >> m) {
timelist.clear();
long long sum = 0;
for (size_t i = 0; i < n; i++) {
cin >> costmanlist[i].b >> costmanlist[i].need >> costmanlist[i].e >> costmanlist[i].time;
sum += costmanlist[i].need * costmanlist[i].time;
timelist.insert(costmanlist[i].b);
timelist.insert(costmanlist[i].e);
}
init();
cout << (sum == SAP(0, Ed) ? "Yes" : "No") << endl;
}
return 0;
}
❌