4.7考试总结
A 大逃杀
题面:51nod 2982
题解:本来大概是一道网络流题?
结果变成了一道模拟题(因为方案已经给你了,直接算就行)
顺便说一下,样例有问题
#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
x = 0; char c = getchar(); int f = 1;
while (!isdigit(c)) { if (c == '-') f = -1; c = getchar(); }
while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
x *= f;
}
#define maxn 105
int x[maxn], y[maxn], X[maxn], Y[maxn];
inline int absdec(int x, int y) { return x > y ? x - y : y - x; }
int main()
{
int n, m, ans = 0;
read(n), read(m);
for (int i = 1; i <= n; ++i) read(x[i]), read(y[i]);
for (int i = 1, tp; i <= m; ++i) read(X[i]), read(Y[i]), read(tp);
for (int i = 1,num; i <= n; ++i)
for (int j = 1; j <= m; ++j)
{
read(num);
ans += num * (absdec(x[i], X[j]) + absdec(y[i], Y[j]) + 1);
}
printf("%d\n", ans);
return 0;
}
B 土地划分
题面:51nod 2935
题解:最小割
这个模型还是比较套路吧
分成两遍的话直接上最小割,一边连S一边连T
考虑路的收益:
先假设和S联通的是属于A国的,和T联通的属于B国
则如果我们要使某一条路连结的都属于A国,就意味着要舍弃掉属于B国的部分收益
所以把这条路的两端点向T连边,边权为\(b_i/2\),表示如果都属于A就舍弃了属于\(B\)的这部分收益
如果两端点不属于一个国家怎么办?
此时必定是两个端点都只割了一条边(因为由上面的分析,每条边对应的两个端点都连了两条边)
所以此时已经扣掉了\(a_i/2+b_i/2\),而总的惩罚是\(a_i+b_i+c_i\)
所以两个端点再互联边权为\(a_i/2+b_i/2+c+i\),表示至少要割掉一条边
实现上可以先全部乘2
#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
x = 0; char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxm 800005
#define maxn 20010
#define inf 0x3f3f3f3f
struct Edge
{
int fr, to, val;
}eg[maxm];
int edgenum = 1, maxflow, head[maxn], cur[maxn];
inline void add(int fr, int to, int val)
{
eg[++edgenum] = { head[fr],to,val };
head[fr] = edgenum;
}
int deep[maxn], vis[maxn], S, T;
#define to eg[i].to
#define Add(fr,to,val) add(fr,to,val),add(to,fr,0)
bool bfs()
{
for (register int i = 0; i <= T; ++i) deep[i] = inf, vis[i] = 0, cur[i] = head[i];
deep[S] = 0;
queue<int> q;
q.push(S);
while (!q.empty())
{
int tmp = q.front();
q.pop();
vis[tmp] = 0;
for (int i = head[tmp]; i; i = eg[i].fr)
if (deep[to] > deep[tmp] + 1 && eg[i].val)
{
deep[to] = deep[tmp] + 1;
if (!vis[to]) vis[to] = 1, q.push(to);
}
}
if (deep[T] != inf) return 1;
return 0;
}
int dfs(int now, int flow)
{
if (now == T)
{
maxflow += flow;
return flow;
}
int tmpflow = 0, used = 0;
for (int& i = cur[now]; i; i = eg[i].fr)
if (deep[to] == deep[now] + 1 && eg[i].val)
if (tmpflow = dfs(to, min(flow - used, eg[i].val)))
{
used += tmpflow;
eg[i].val -= tmpflow;
eg[i ^ 1].val += tmpflow;
if (used == flow) break;
}
return used;
}
inline void dinic() { while (bfs()) dfs(S, inf); }
long long sum;
int main()
{
int n, m;
read(n), read(m);
S = n + 1, T = n + 2;
for (int i = 2, va; i < n; ++i) read(va), Add(S, i, va << 1), sum += va;
for (int i = 2, vb; i < n; ++i) read(vb), Add(i, T, vb << 1), sum += vb;
Add(S, 1, inf), Add(1, T, 0);
Add(S, n, 0), Add(n, T, inf);
for (int i = 1, x, y, a, b, c; i <= m; ++i)
{
read(x), read(y), read(a), read(b), read(c);
Add(S, x, a), Add(S, y, a);
Add(x, T, b), Add(y, T, b);
Add(x, y, a + b + 2 * c), Add(y, x, a + b + 2 * c);
sum += a + b;
}
dinic();
printf("%lld\n", sum - (maxflow >> 1));
return 0;
}
C 城市
题面:51nod 2983
题解:最短路图+DAG支配树
说在前面:\(S\color{red}{angber},yyds!\)
考场上自行推出支配树,这是什么神仙?
再来一遍,\(S\color{red}{angber},yyds!\)
首先考虑建出最短路图
对于一条边,我们考虑化边为点
也就是如果最短路图上有一条边\((x,y)\),新建一个点\(z\),连\((x,z),(z,y)\),这样就转化为了统计必须要经过\(z\)的数量
然后由于最短路图是一个\(DAG\),所以接下来的部分可以参看[ZJOI2012]灾难
一些变量名我会在代码里注释
#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
x = 0; char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 400005
struct Edge { int fr, to, val; };
struct Graph
{
Edge eg[maxn << 1];
int head[maxn], edgenum;
inline void add(int fr, int to, int val = 0)
{
eg[++edgenum] = { head[fr],to,val };
head[fr] = edgenum;
}
inline void Add(int fr, int to, int val) { add(fr, to, val), add(to, fr, val); }
}Origin, DAG, Tree;//原图,最短路图(稍微改了一下),支配树
int n, m, s;
int dis[maxn];
#define eg Origin.eg
#define head Origin.head
void dijskra()//求最短路
{
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>>q;
memset(dis, 0x3f, sizeof(dis));
q.push(make_pair(dis[s] = 0, s));
while (!q.empty())
{
int tp = q.top().second, dist = q.top().first;
q.pop();
if (dis[tp] != dist) continue;
for (int i = head[tp]; i; i = eg[i].fr)
if (dis[eg[i].to] > dis[tp] + eg[i].val)
q.push(make_pair(dis[eg[i].to] = dis[tp] + eg[i].val, eg[i].to));
}
}
int dad[maxn], deg[maxn], fa[maxn][20], dep[maxn];//支配树相关
int tot, id[maxn], sum[maxn], siz[maxn], ans[maxn];//统计答案相关
int Fr[maxn], To[maxn], Val[maxn];//一开始的边
void build_DAG()//建最短路图
{
tot = n;
for (int i = 1; i <= m; ++i)
{
if (dis[Fr[i]] + Val[i] == dis[To[i]])
{
id[++tot] = i;
DAG.add(Fr[i], tot), ++deg[tot];
DAG.add(tot, To[i]), ++deg[To[i]];
}
if (dis[To[i]] + Val[i] == dis[Fr[i]])
{
id[++tot] = i;
DAG.add(To[i], tot), ++deg[tot];
DAG.add(tot, Fr[i]), ++deg[Fr[i]];
}
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y]) swap(x, y);
int dist = dep[x] - dep[y];
for (int i = 0; i <= 17; ++i)
if ((1 << i) & dist) x = fa[x][i];
if (x == y) return x;
for (int i = 17; i >= 0; --i)
if (fa[x][i] != fa[y][i])
x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
#undef eg
#undef head
#define eg DAG.eg
#define head DAG.head
#define to eg[i].to
void toposort()//建支配树
{
memset(dad, -1, sizeof(dad));
queue<int> q;
for (int i = 1; i <= n; ++i)
if (!deg[i]) q.push(i), dad[i] = 0;
while (!q.empty())
{
int tp = q.front(); q.pop();
Tree.add(dad[tp], tp); dep[tp] = dep[dad[tp]] + 1;
fa[tp][0] = dad[tp];
for (int i = 1; i <= 17; ++i) fa[tp][i] = fa[fa[tp][i - 1]][i - 1];
for (int i = head[tp]; i; i = eg[i].fr)
{
if (dad[to] == -1) dad[to] = tp;
else dad[to] = lca(dad[to], tp);
if (!--deg[to]) q.push(to);
}
}
}
#undef eg
#undef head
#undef to
void dfs(int rt)//统计答案,注意新建的点不要算进答案
{
siz[rt] = (rt <= n);
for (int i = Tree.head[rt]; i; i = Tree.eg[i].fr)
dfs(Tree.eg[i].to), siz[rt] += siz[Tree.eg[i].to];
sum[id[rt]] = siz[rt];//如果是原来就有的点,id[rt]=0,对答案没有影响,否则就代表的是第id[rt]条边
}
int main()
{
read(n), read(m);
for (int i = 1; i <= m; ++i)
read(Fr[i]), read(To[i]), read(Val[i]), Origin.Add(Fr[i], To[i], Val[i]);
read(s);
dijskra(); build_DAG();
toposort(); dfs(0);
for (int i = 1; i <= m; ++i) ans[Fr[i]] += sum[i], ans[To[i]] += sum[i];
for (int i = 1; i <= n; ++i) printf("%d\n", ans[i]);
return 0;
}
一切伟大的行动和思想,都有一个微不足道的开始。
There is a negligible beginning in all great action and thought.
There is a negligible beginning in all great action and thought.