T1.「JOI 2015 Final」JOI 公园
题意分析
题意描述:选择一个自然数xx, 将和第一个广场的距离小于等于xx 的所有广场相互之间连接地下通道
所以,对于给出的每一条边,当max(dis[u], dis[v]) > xmax(dis[u],dis[v])>x时,这条边将额外对答案作出贡献
否则的话,该边所连接的广场均被记入地下通道计划中
所以,对于每个枚举出的xx,它所对应的修缮花费总和为 \sum_1^m∑1m didi(max(dis[u], dis[v]) > xmax(dis[u],dis[v])>x) + x * cx∗c
具体实现
1.dijkstradijkstra算法,计算每个广场到一号广场的最短路
2.记录下e[i].sh = max(dis[e[i].u], dis[e[i].v])e[i].sh=max(dis[e[i].u],dis[e[i].v])
3.对ee数组以shsh的大小,由小到大排序
4.暴力枚举每一个分割点,更新最小花费
代码实现
ans = sum;
// sum,最开始记录所有边权的总和
for (int i = 1; i <= m; i ++) {
e[i].sh = max(dis[e[i].u], dis[e[i].v]);
}
sort(e + 1, e + m + 1, cmp);
for (int i = 1; i <= m; i ++) {
sum -= e[i].w;
// 以第i个边的sh为分割(x),之前的所有边(sh)显然小于等于x
// 所以这些边的边权全部减去
// 剩余的sum(边权和)恰好是可以为答案做出贡献的值的总和(即不被纳入地下交通计划)
ans = min(ans, sum + (long long) e[i].sh *c );
}
printf("%lld\n", ans);
最后提醒:数据范围开long long
T2.「JOI 2017 Final」足球
题意分析
题目难点:如何建图
分析:因为有控球和不控球的状态差别,或者说是有球滚动和人移动的不同代价 所以我们需要拆点(建分层图)
分层
首先,因为人的移动本质上是一样的,所以我们不妨将人带着球的移动当成一层来看
接着,球滚动的过程也需考虑(因为踢球的代价不同)将球的滚动分为两层:横着滚动和竖着滚动(方向不同)
如何从滚动过程跳跃到运球过程
因为状态第一层的定义是人运球的过程,所以从移动过程的当前点(x, y)(x,y)转移至运球过程的当前点(x, y)(x,y)一定需要一名球员来接球
接球的球员一定是距离(x, y)(x,y)最近的那名球员
所以,关于接球球员的选择,我们需要在预处理时求得
另外
坐标是从0开始的,一张图实际共有(n + 1)* (m + 1)(n+1)∗(m+1)个点
以下是具体实现
关于代价
p[sp - 2]p[sp−2], 人的移动
p[sp - 3]p[sp−3],踢球时
p[sp - 4]p[sp−4],球的滚动
变成未被控球看作p[sp - 3]p[sp−3]的代价
滚动一米花费p[sp - 4]p[sp−4]的代价
人在移动,运球时花费p[sp - 2]p[sp−2]的代价
关于拆点
每个点我们拆出 33 个点表示控球(00)、未被控球左右滚动(11)、未被控球上下滚动(22)
关于连边
具体有以下几类边可以相连
(i, j, 0)(i,j,0)向四周的 00 连边权为 CC 的边,表示控球跑 11 米
(i, j, 1)(i,j,1)向左右的 11 连边权为 AA 的边,表示球向左右滚动 11 米
(i, j, 2)(i,j,2)向上下的 22 连边权为 AA 的边,表示球向上下滚动 11 米
(i, j, 0)(i,j,0)向(i, j, 1)(i,j,1)和(i, j, 2)(i,j,2)连边权为 BB 的边,表示把球踢出去
(i, j, 1)(i,j,1)向(i, j, 2)(i,j,2)和(i, j, 0)(i,j,0)连边权为 dis_ij * Cdisij∗C 的边,表示最近的球员跑过来控球
我的代码备注
可以看到, id(x, y)id(x,y)是在压缩每一个点的标识,将这张二维图压缩成一维,便于之后的dijkstradijkstra操作
另外,在建边与连边时,参考学习并查集时的扩展域思想
有id(x, y) + (n + 1) * (m + 1)id(x,y)+(n+1)∗(m+1) 和 id(x, y) + 2 * (n + 1) * (m + 1)id(x,y)+2∗(n+1)∗(m+1)的操作
代码实现
以下是完整代码
#include <cstdio>
#include <queue>
#include <map>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
#define sc ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define pus make_pair
#define pa pair<long long, int>
const int N = 1e5 + 5, M = 505, INF = 0x3f3f3f3f, sp = 5, S = M * M * 30;
const int dx[sp] = { 0, 0, 1, -1 };
const int dy[sp] = { 1, -1, 0, 0 };
int m, n, s, cnt;
long long p[sp];
int head[S / 10], ver[S], nextw[S];
bool v[M][M], vis[S / 10];
long long dis[S / 10], dist[M][M], edgew[S];
struct node {
int x, y;
};
node st[N];
struct edge {
long long val;
int id;
edge(long long x, int y) {
val = x;
id = y;
}
};
priority_queue<edge> q;
bool operator<(edge x, edge y) { return x.val > y.val; }
inline int id(int x, int y) {
return x * (m + 1) + y;
}
void add_edge(int x, int y, long long z) {
ver[++cnt] = y;
edgew[cnt] = (long long) z;
nextw[cnt] = head[x];
head[x] = cnt;
}
void bfs() {
queue<pa> q;
for (int i = 1; i <= s; i++) {
q.push(pus(st[i].x, st[i].y));
dist[st[i].x][st[i].y] = 0;
}
while (q.size()) {
int x = q.front().first, y = q.front().second;
q.pop();
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 0 || ny < 0 || nx > n || ny > m || v[nx][ny] || dist[nx][ny]) {
continue;
}
dist[nx][ny] = dist[x][y] + 1;
q.push(pus(nx, ny));
}
}
}
void dijkstra(int s) {
memset(dis, 0x3f, sizeof dis);
dis[s] = 0;
q.push(edge(0, s));
while (!q.empty()) {
int x = q.top().id;
q.pop();
if (vis[x])
continue;
vis[x] = 1;
for (int i = head[x]; i; i = nextw[i]) {
int y = ver[i];
long long z = edgew[i];
if (dis[y] > dis[x] + z) {
dis[y] = dis[x] + z;
q.push(edge(dis[y], y));
}
}
}
}
int main() {
sc;
cin >> n >> m;
for (int i = 1; i < 4; i++) {
cin >> p[i];
}
cin >> s;
for (int i = 1; i <= s; i++) {
cin >> st[i].x >> st[i].y;
v[st[i].x][st[i].y] = true;
}
bfs();
for (int x = 0; x <= n; x++) {
for (int y = 0; y <= m; y++) {
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 0 || ny < 0 || nx > n || ny > m) {
continue;
}
// 向四周的 0 连边权为 C 的边,表示控球跑 1 米
add_edge(id(x, y), id(nx, ny), p[sp - 2]);
}
// 向(i,j,1)和(i,j,2)连边权为 B 的边,表示把球踢出去
add_edge(id(x, y), id(x, y) + (n + 1) * (m + 1), p[sp - 3]);
add_edge(id(x, y), id(x, y) + 2 * (n + 1) * (m + 1), p[sp - 3]);
}
}
for (int x = 0; x <= n; x++) {
for (int y = 0; y <= m; y++) {
for (int i = 0; i < 2; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 0 || ny < 0 || nx > n || ny > m) {
continue;
}
// 向左右的 1 连边权为 A 的边,表示球向左右滚动 1 米
add_edge(id(x, y) + (n + 1) * (m + 1), id(nx, ny) + (n + 1) * (m + 1), p[sp - 4]);
}
// 向(i,j,2)和(i,j,0)连边权为 dis_ij * C的边,表示最近的球员跑过来控球
add_edge(id(x, y) + (n + 1) * (m + 1), id(x, y), dist[x][y] * p[sp - 2]);
}
}
for (int x = 0; x <= n; x++) {
for (int y = 0; y <= m; y++) {
for (int i = 2; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 0 || ny < 0 || nx > n || ny > m) {
continue;
}
// 向上下的 2 连边权为 A 的边,表示球向上下滚动 1 米
add_edge(id(x, y) + 2 * (n + 1) * (m + 1), id(nx, ny) + 2 * (n + 1) * (m + 1), p[sp - 4]);
}
// 向(i,j,2)和(i,j,0)连边权为 dis_ij * C的边,表示最近的球员跑过来控球
add_edge(id(x, y) + 2 * (n + 1) * (m + 1), id(x, y), dist[x][y] * p[sp - 2]);
}
}
dijkstra(id(st[1].x, st[1].y));
printf("%lld", dis[id(st[s].x, st[s].y)]);
return 0;
}
草稿——
T3.「JOI 2013 Final」 现代豪宅
题意分析
具体实现
代码实现
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
#define sc ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define int long long
const int N = 3000005, M = 40000005, INF = 0x7f7f7f7f7f7f, sp = 5;
int m, n, s, cnt, sum, x, y, p;
int head[N], ver[M], nextw[M], edgew[M];
int dis[N];
bool vis[N];
struct node {
int x, y;
};
vector<node> h[N], w[N];
bool cmp(node x, node y) {
return x.x < y.x;
}
struct edge {
int val, id;
edge(int x, int y) {
val = x;
id = y;
}
};
priority_queue<edge> q;
bool operator <(edge x, edge y) {
return x.val > y.val;
}
void add(int x, int y, int z) {
ver[++sum] = y;
edgew[sum] = z;
nextw[sum] = head[x];
head[x] = sum;
}
void dijkstra() {
for (int i = 0; i <= p; i ++) {
dis[i] = INF;
}
dis[0] = 0;
q.push(edge(0, 0));
while (q.size()) {
edge u = q.top();
q.pop();
// 因为在处理dijkstra时,我们是先更改值,再将对应的标识压入队列
// 所以,当u.val > dis[u.id]时,代表u.id已被访问过,确定最短路
if (u.val > dis[u.id]) {
continue;
}
for (int i = head[u.id]; i; i = nextw[i]) {
int y = ver[i];
long long z = edgew[i];
if (dis[y] > dis[u.id] + z) {
dis[y] = dis[u.id] + z;
q.push(edge(dis[y], y));
}
}
}
}
signed main() {
sc;
cin >> m >> n >> s;
p = s << 1 | 1;
for (int i = 1; i <= s; i ++) {
cin >> x >> y;
++ cnt;
w[x].push_back((node) {y, cnt});
h[y].push_back((node) {x, cnt + s});
}
for (int i = 1; i <= n; i ++) {
sort(h[i].begin(), h[i].end(), cmp);
int len = h[i].size();
for (int j = 1; j < len; j ++) {
add(h[i][j - 1].y, h[i][j].y, h[i][j].x - h[i][j - 1].x);
add(h[i][j].y, h[i][j - 1].y, h[i][j].x - h[i][j - 1].x);
}
}
if(h[n].size()) {
int len = h[n].size() - 1;
add(p, h[n][len].y, m - h[n][len].x);
add(h[n][len].y, p, m - h[n][len].x);
}
for (int i = 1; i <= m; i ++) {
sort(w[i].begin(), w[i].end(), cmp);
int len = w[i].size();
for (int j = 1; j < len; j ++) {
add(w[i][j - 1].y, w[i][j].y, w[i][j].x - w[i][j - 1].x);
add(w[i][j].y, w[i][j - 1].y, w[i][j].x - w[i][j - 1].x);
}
}
if(w[m].size()) {
int len = w[m].size() - 1;
add(p, w[m][len].y, n - w[m][len].x);
add(w[m][len].y, p, n - w[m][len].x);
}
if(w[1].size()) {
add(0, w[1][0].y, w[1][0].x - 1);
add(w[1][0].y, 0, w[1][0].x - 1);
}
for (int i = 1; i <= s; i ++) {
add(i, i + s, 1);
add(i + s, i, 1);
}
dijkstra();
if (dis[p] > INF - 1) {
puts("-1");
return 0;
}
printf("%lld", dis[p]);
return 0;
}
T4.「JOISC 2014 Day1」有趣的家庭菜园
题意分析
具体实现
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
#define sc ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 3e5 + 5, INF = 0x3f3f3f3f, sp = 5;
int n;
long long ans;
int s[N], d[N], BIT[N][2];
int lowbit(int x) { return x & -x; }
void updata(int x, int k, int fl) {
for (int i = x; i <= n; i += lowbit(i)) {
BIT[i][fl] += k;
}
}
int sum(int x, int fl) {
int nsum = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
nsum += BIT[i][fl];
}
return nsum;
}
void sf() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &s[i]);
d[i] = s[i];
}
}
void discrete() {
sort(s + 1, s + n + 1);
int cnt = unique(s + 1, s + n + 1) - s - 1;
for (int i = 1; i <= n; i++) {
d[i] = lower_bound(s + 1, s + cnt + 1, d[i]) - s;
updata(d[i], 1, 1);
}
}
int main() {
sf();
discrete();
for (int i = 1; i <= n; i++) {
int u, v;
updata(d[i], -1, 1);
u = n - i - sum(d[i], 1);
updata(d[i], 1, 0);
v = i - sum(d[i], 0);
ans += min(u, v);
}
printf("%lld", ans);
return 0;
}