[NOI2008]志愿者招募 题解
题目地址:洛谷:【P3980】[NOI2008]志愿者招募 – 洛谷、BZOJ …
May all the beauty be blessed.
题目地址:洛谷:【P4142】洞穴遇险 – 洛谷
前方有一片沼泽地.
方便地, 我们用一个n × n 的网格图来描述它, 每一个格子代表着沼泽地的一小片区域. 其中(1, 1) 代表网格图的左上角, (n, n) 代表网格图的右下角. 若用X 表示行数, Y 表示列数, 那么X + Y 为奇数的格子有一个危险度VX, Y , X + Y 为偶数的格子的危险度为0.
为了保障人们的安全, 你有m 个长相怪异的大石头, 你可以选一些石头放在网格图上的某些格子上, 石头可以看成一个‘L’ 形的块, 并且占三个格子, 它通过旋转有四种方式供放置, 仅会使得在拐角处的那个格子危险度减为0.
网格图中还有k 个位置是“禁止位置”, 石头的任何部位都不能位于这些格子上, 且这些位置的危险度一定为0.
现在你需要知道放置一些石头后最小的危险度之和是多少. (石头可以不放完)
输入格式:
从文件marshland.in 中读入数据。
第一行三个整数n; m; k.
接下来n 行每行n 个整数, 表示每个格子的危险度, 保证X + Y 为偶数的格子和禁止位置的格子的危险度为0.
接下来k 行每行2 个整数X; Y , 表示禁止位置的坐标, 注意有可能会出现重复的禁止位置.
输出格式:
输出到文件marshland.out 中
输出一行一个整数代表最小的危险度之和.
输入样例#1:
3 3 1 0 1 0 2 0 1 0 1 0 1 3
输出样例#1:
3
输入样例#2:
3 3 4 0 2 0 0 0 4 0 3 0 1 3 2 1 2 2 3 1
输出样例#2:
9
对于10%的数据,满足n ≤ 4,
对于30%的数据,满足n ≤ 10,
对于100%的数据,满足n ≤ 50,
对于所有的数据,满足0 ≤ m ≤ n^2/3 ; 0 ≤ k ≤ n2; 0 ≤ VX;Y ≤ 10^6.
雅礼集训Day 5 T1,网络流好题。
我们想用对每个有危险度的格子确定一种安排方案,使得有与它有公共边的两个相邻非对位格子被覆盖或者选择不覆盖这个格子,而且数据范围也很网络流,所以可以考虑网络流。
但是无论是用有危险度的格子去匹配方案,还是用方案去匹配有危险度的的格子,都存在一个问题:流量控制在相邻的格子上,然而存在两个需要流量控制的点,不得不把匹配的流量加到2,但是存在可能无法跑满流的情况,这是无法解决的。
我们考虑,假如对于这样一个情况
---- | 1| ------- |DG| 2| -------
其中,DG是有危险度的格子,我们让流量从1流入,流经DG后从2流出,就无需考虑流量2无法满流的情况了。对于石头数m的限制,直接进行m次增广就可以解决这个问题了。需要注意的是,这里要求的是最大费用可行流,需要实时取min。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
typedef long long LL;
const int MAXN = 5005, INF = 1e9;
struct Edge {
int to, cap, cost, nxt;
} gra[MAXN << 4];
int head[MAXN], tot;
inline void addedge(int u, int v, int cap, int cost) {
gra[tot] = (Edge) {v, cap, cost, head[u]}; head[u] = tot++;
gra[tot] = (Edge) {u, 0, -cost, head[v]}; head[v] = tot++;
}
int f[MAXN], pre[MAXN], pree[MAXN];
LL dis[MAXN];
bool inque[MAXN];
std::queue<int> que;
inline bool spfa(int s, int t) {
memset(f, 0, sizeof(f));
memset(dis, 0x3f, sizeof(dis));
que.push(s); f[s] = INF; dis[s] = 0; inque[s] = true;
while(!que.empty()) {
int u = que.front(); que.pop();
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to; inque[v] = false;
if(gra[i].cap && dis[v] > dis[u] + gra[i].cost) {
dis[v] = dis[u] + gra[i].cost;
f[v] = std::min(f[u], gra[i].cap);
pre[v] = u; pree[v] = i;
if(!inque[v]) {
inque[v] = true; que.push(v);
}
}
}
}
return f[t];
}
LL flow, cost;
LL ans;
inline void mcmf(int s, int t) {
while(spfa(s, t)) {
flow += f[t]; cost += 1ll * f[t] * dis[t];
for(int i = t; i != s; i = pre[i]) {
gra[pree[i]].cap -= f[t]; gra[pree[i] ^ 1].cap += f[t];
}
ans = std::min(cost, ans);
}
}
int n, m, k, a[55][55], S, S1, T;
bool forbid[55][55];
inline int num(int x, int y) {
return (x - 1) * n + y;
}
int main() {
freopen("marshland.in", "r", stdin);
freopen("marshland.out", "w", stdout);
memset(head, -1, sizeof(head));
scanf("%d%d%d", &n, &m, &k);
S = 5000; S1 = 5001; T = 5002;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
scanf("%d", &a[i][j]);
cost += a[i][j];
}
}
ans = cost;
for(int i = 1, x, y; i <= k; i++) {
scanf("%d%d", &x, &y);
forbid[x][y] = true;
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(((i + j) & 1) && !forbid[i][j]) {
addedge(num(i, j), n * n + num(i, j), 1, -a[i][j]);
}
}
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(!((i + j) & 1) && !forbid[i][j]) {
if(i & 1) {
addedge(S1, num(i, j), 1, 0);
if(i - 1 >= 1) addedge(num(i, j), num(i - 1, j), 1, 0);
if(i + 1 <= n) addedge(num(i, j), num(i + 1, j), 1, 0);
if(j - 1 >= 1) addedge(num(i, j), num(i, j - 1), 1, 0);
if(j + 1 <= n) addedge(num(i, j), num(i, j + 1), 1, 0);
} else {
addedge(num(i, j), T, 1, 0);
if(i - 1 >= 1) addedge(n * n + num(i - 1, j), num(i, j), 1, 0);
if(i + 1 <= n) addedge(n * n + num(i + 1, j), num(i, j), 1, 0);
if(j - 1 >= 1) addedge(n * n + num(i, j - 1), num(i, j), 1, 0);
if(j + 1 <= n) addedge(n * n + num(i, j + 1), num(i, j), 1, 0);
}
}
}
}
addedge(S, S1, m, 0);
mcmf(S, T);
printf("%lld", ans);
return 0;
}
题目地址:BZOJ:Problem 3545. — [ONTAK2010]Peaks
在Bytemountains有N座山峰,每座山峰有他的高度h_i。有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1。
有一个图,点和边都有权值。回答若干询问,每个询问表示只保留图中边权不大于x的边,v所在的连通块中,点权k大
输入格式:
第一行三个数N,M,Q。
第二行N个数,第i个数为h_i
接下来M行,每行3个数a b c,表示从a到b有一条困难值为c的双向路径。
接下来Q行,每行三个数v x k,表示一组询问。
输出格式:
对于每组询问,输出一个整数表示答案。
输入样例#1:
10 11 4 1 2 3 4 5 6 7 8 9 10 1 4 4 2 5 3 9 8 2 7 8 10 7 1 4 6 7 1 6 4 8 2 1 5 10 8 10 3 4 7 3 4 6 1 5 2 1 5 6 1 5 8 8 9 2
输出样例#1:
6 1 -1 8
【数据范围】
N<=10^5, M,Q<=5*10^5,h_i,c,x<=10^9。
首先,连通性可以使用并查集维护。我们可以先对点权离散化,考虑每个连通块维护一个权值线段树,使用动态开点写法,在并查集的代表元处记下根下标。
我们考虑离线解决这个问题,对询问和边都排序,从小到大加边,并启发式合并连通块的线段树。由于线段树规模倍增,启发式合并的复杂度是期望O(\log n)的。因此总复杂度是O(n \log n)的,可以接受。
谁来告诉我为什么我的线段树常数看上去很优秀?这好像已经不是第一次了
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <vector>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 500005;
int n, m, q;
int fa[MAXN], siz[MAXN];
inline int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
struct Node {
int lch, rch, val;
} tr[MAXN << 4];
int rt[MAXN], tot;
inline void insert(int &o, int l, int r, int x) {
if(!o) o = ++tot;
tr[o].val++;
if(l == r) return;
int mid = (l + r) >> 1;
if(x <= mid) insert(tr[o].lch, l, mid, x);
else insert(tr[o].rch, mid + 1, r, x);
}
inline int query(int o, int l, int r, int k) {
if(l == r) return l;
int mid = (l + r) >> 1, rsiz = tr[tr[o].rch].val;
if(k <= rsiz) return query(tr[o].rch, mid + 1, r, k);
else return query(tr[o].lch, l, mid, k - rsiz);
}
inline int merge(int o1, int o2) {
if(!o1) return o2;
if(!o2) return o1;
if(!tr[o1].lch && !tr[o1].rch) {
tr[o1].val += tr[o2].val;
return o1;
}
tr[o1].lch = merge(tr[o1].lch, tr[o2].lch);
tr[o1].rch = merge(tr[o1].rch, tr[o2].rch);
tr[o1].val = tr[tr[o1].lch].val + tr[tr[o1].rch].val;
return o1;
}
int w[MAXN];
std::vector<int> tmp;
struct Edge {
int u, v, w;
} edges[MAXN];
inline bool cmpE(Edge a, Edge b) {
return a.w < b.w;
}
struct Query {
int v, x, k, id;
} qur[MAXN];
inline bool cmpQ(Query a, Query b) {
return a.x < b.x;
}
int ans[MAXN];
int main() {
n = readint(); m = readint(); q = readint();
for(int i = 1; i <= n; i++) {
fa[i] = i; siz[i] = 1;
}
tmp.push_back(-1);
for(int i = 1; i <= n; i++) {
w[i] = readint();
tmp.push_back(w[i]);
}
std::sort(tmp.begin(), tmp.end());
tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
for(int i = 1; i <= n; i++) {
w[i] = std::lower_bound(tmp.begin(), tmp.end(), w[i]) - tmp.begin();
insert(rt[i], 1, n, w[i]);
}
for(int i = 1; i <= m; i++) {
edges[i].u = readint(); edges[i].v = readint(); edges[i].w = readint();
}
std::sort(edges + 1, edges + m + 1, cmpE);
for(int i = 1; i <= q; i++) {
qur[i].v = readint(); qur[i].x = readint(); qur[i].k = readint(); qur[i].id = i;
}
std::sort(qur + 1, qur + q + 1, cmpQ);
int ne = 1;
for(int i = 1; i <= q; i++) {
for(; ne <= m && edges[ne].w <= qur[i].x; ne++) {
int u = edges[ne].u, v = edges[ne].v, fu = find(u), fv = find(v);
if(fu == fv) continue;
if(siz[fu] > siz[fv]) {
std::swap(u, v); std::swap(fu, fv);
}
rt[fv] = merge(rt[fu], rt[fv]);
fa[fu] = fv; siz[fv] += siz[fu];
}
int fv = find(qur[i].v);
if(tr[rt[fv]].val < qur[i].k) ans[qur[i].id] = -1;
else ans[qur[i].id] = tmp[query(rt[fv], 1, n, qur[i].k)];
}
for(int i = 1; i <= q; i++) {
printf("%d\n", ans[i]);
}
return 0;
}
题目地址:洛谷:【P2151】[SDOI2009]HH去散步 – 洛谷、BZOJ:Problem 1875. — [SDOI2009]HH去散步
HH有个一成不变的习惯,喜欢饭后百步走。所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离。 但是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回。 又因为HH是个喜欢变化的人,所以他每天走过的路径都不完全一样,他想知道他究竟有多 少种散步的方法。
现在给你学校的地图(假设每条路的长度都是一样的都是1),问长度为t,从给定地 点A走到给定地点B共有多少条符合条件的路径
给你一个无向图,求从a走到b且长度为t的路径总数,每条边经过次数不限,但是不能连续双向经过同一条边。
输入格式:
第一行:五个整数N,M,t,A,B。其中N表示学校里的路口的个数,M表示学校里的 路的条数,t表示HH想要散步的距离,A表示散步的出发点,而B则表示散步的终点。
接下来M行,每行一组Ai,Bi,表示从路口Ai到路口Bi有一条路。数据保证Ai != Bi,但 不保证任意两个路口之间至多只有一条路相连接。 路口编号从0到N − 1。 同一行内所有数据均由一个空格隔开,行首行尾没有多余空格。没有多余空行。 答案模45989。
输出格式:
一行,表示答案。
输入样例#1:
4 5 3 0 0 0 1 0 2 0 3 2 1 3 2
输出样例#1:
4
对于30%的数据,N ≤ 4,M ≤ 10,t ≤ 10。
对于100%的数据,N ≤ 50,M ≤ 60,t ≤ 2^30,0 ≤ A,B
参考资料:题解 P2151 【[SDOI2009]HH去散步】 – roufaen 的博客 – 洛谷博客
可以考虑用边DP,dp[i][j]表示已经走出距离i,且最后走过的一条边是j的方案数。显然这里需要把无向边拆成两条有向边考虑。转移也很显然,对于一条边i(u, v)及对应的状态dp[t][i],可以从j(w, u)对应的dp[t-1][j]转移过来,不过要判断i和j在原来的无向图中是否为同一条边。
我们观察到t的范围很大,而上述转移在两个距离之间显然是固定的线性变换,可以考虑使用矩阵快速幂加速转移。优化后的复杂度为O(4m^2 \log t)。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 205, MO = 45989;
int n, m;
struct Matrix {
int a[MAXN][MAXN];
Matrix() {
memset(a, 0, sizeof(a));
}
inline Matrix operator*(const Matrix &rhs) const {
Matrix res;
for(int i = 0; i < m << 1; i++) {
for(int j = 0; j < m << 1; j++) {
for(int k = 0; k < m << 1; k++) {
res.a[i][j] = (res.a[i][j] + a[i][k] * rhs.a[k][j]) % MO;
}
}
}
return res;
}
inline Matrix& operator*=(const Matrix &x) {
return *this = *this * x;
}
};
inline Matrix fpow(Matrix x, LL k) {
Matrix res;
for(int i = 0; i < m << 1; i++) {
res.a[i][i] = 1;
}
while(k) {
if(k & 1) res *= x;
x *= x;
k >>= 1;
}
return res;
}
struct Edge {
int u, v;
} edges[MAXN];
LL t;
int a, b;
Matrix def, trans;
int main() {
n = readint(); m = readint(); t = readint(); a = readint(); b = readint();
for(int i = 0, u, v; i < m; i++) {
u = readint(); v = readint();
edges[i << 1] = Edge {u, v};
edges[i << 1 | 1] = Edge {v, u};
}
for(int i = 0; i < m << 1; i++) {
def.a[i][0] = (edges[i].u == a);
}
for(int i = 0; i < m << 1; i++) {
for(int j = 0; j < m << 1; j++) {
trans.a[j][i] = (edges[i].v == edges[j].u && i != (j ^ 1));
}
}
trans = fpow(trans, t - 1);
def = trans * def;
int ans = 0;
for(int i = 0; i < m << 1; i++) {
if(edges[i].v == b) ans = (ans + def.a[i][0]) % MO;
}
printf("%d", ans);
return 0;
}