T1:道路修建
【问题描述】
在 W 星球上有 n 个国家。为了各自国家的经济发展,他们决定在各个国家之间建设双向道路使得国家之间连通。但是每个国家的国王都很吝啬,他们只愿意修建恰好 n – 1 条双向道路。
每条道路的修建都要付出一定的费用,这个费用等于道路长度乘以道路两端的国家个数之差的绝对值。例如,在下图中,虚线所示道路两端分别有 2 个、4 个国家,如果该道路长度为 1,则费用为 1×|2 – 4|=2。图中圆圈里的数字表示国家的编号。
(图略)
由于国家的数量十分庞大,道路的建造方案有很多种,同时每种方案的修建费用难以用人工计算,国王们决定找人设计一个软件,对于给定的建造方案,计算出所需要的费用。请你帮助国王们设计一个这样的软件。
【输入格式】
从文件 road.in 中读入数据。
输入的第一行包含一个整数 n,表示 W 星球上的国家的数量,国家从 1 到 n编号。 接下来 n – 1 行描述道路建设情况,其中第 i 行包含三个整数 ai、bi 和 ci,表示第 i 条双向道路修建在 ai 与 bi 两个国家之间,长度为 ci。
【输出格式】
输出到文件 road.out 中。
输出一个整数,表示修建所有道路所需要的总费用。
【样例输入】
6
1 2 1
1 3 1
1 4 2
6 3 1
5 2 1
【样例输出】
20
【数据规模与约定】
所有测试数据的范围和特点如下表所示
测试点编号 |
n 的规模(注意是等于号) |
约定 |
1 |
n = 2 |
1≤ai, bi≤n 0 ≤ci≤ 106 |
2 |
n = 10 |
|
3 |
n = 100 |
|
4 |
n = 200 |
|
5 |
n = 500 |
|
6 |
n = 600 |
|
7 |
n = 800 |
|
8 |
n = 1000 |
|
9 |
n = 10,000 |
|
10 |
n = 20,000 |
|
11 |
n = 50,000 |
|
12 |
n = 60,000 |
|
13 |
n = 80,000 |
|
14 |
n = 100,000 |
|
15 |
n = 600,000 |
|
16 |
n = 700,000 |
|
17 |
n = 800,000 |
|
18 |
n = 900,000 |
|
19 |
n = 1,000,000 |
|
20 |
n = 1,000,000 |
解析:
开始时把题看错了以为要$LCT$,结果只需要把树建好,统计子树大小,每条边计算贡献即可,时间$O(n)$,(100分)
$bzoj$上$dfs$好像过不了,需要把$dfs$改成$bfs$,然后就可过了,这里放$dfs$版的代码
代码:
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn = 1000004; inline int read() { int ret, f=1; char c; while((c=getchar())&&(c<'0'||c>'9'))if(c=='-')f=-1; ret=c-'0'; while((c=getchar())&&(c>='0'&&c<='9'))ret = (ret<<3)+(ret<<1)+c-'0'; return ret*f; } int n, tot, head[maxn], siz[maxn]; ll ans; struct edge{ int nxt, to, d; }e[maxn<<1]; void Addedge(int x, int y, int z) { e[++tot] = (edge){head[x], y, z}; head[x] = tot; } void dfs(int x, int fa) { siz[x] = 1; for(int i = head[x]; i; i = e[i].nxt) { int id = e[i].to; if(id == fa) continue; dfs(id, x); ans += ((siz[id]<<1) > n? (ll)((siz[id]<<1) - n) * (ll)e[i].d: (ll)(n - (siz[id]<<1)) * (ll)e[i].d); siz[x] += siz[id]; } } int main() { freopen("road.in", "r", stdin); freopen("road.out", "w", stdout); n = read(); for(int i = 1; i < n; ++i) { int x = read(), y = read(), z = read(); Addedge(x, y, z); Addedge(y, x, z); } dfs(1, 0); printf("%lld ", ans); return 0; }
T2:NOI嘉年华
【问题描述】
NOI2011 在吉林大学开始啦!为了迎接来自全国各地最优秀的信息学选手,吉林大学决定举办两场盛大的 NOI 嘉年华活动,分在两个不同的地点举办。每个嘉年华可能包含很多个活动,而每个活动只能在一个嘉年华中举办。
现在嘉年华活动的组织者小安一共收到了 n 个活动的举办申请,其中第 i 个活动的起始时间为 Si,活动的持续时间为 Ti。这些活动都可以安排到任意一个嘉年华的会场,也可以不安排。 小安通过广泛的调查发现,如果某个时刻,两个嘉年华会场同时有活动在进行(不包括活动的开始瞬间和结束瞬间),那么有的选手就会纠结于到底去哪个会场,从而变得不开心。所以,为了避免这样不开心的事情发生,小安要求不能有两个活动在两个会场同时进行(同一会场内的活动可以任意进行)。
另外,可以想象,如果某一个嘉年华会场的活动太少,那么这个嘉年华的吸引力就会不足,容易导致场面冷清。所以小安希望通过合理的安排,使得活动相对较少的嘉年华的活动数量最大。 此外,有一些活动非常有意义,小安希望能举办,他希望知道,如果第 i 个活动必须举办(可以安排在两场嘉年华中的任何一个),活动相对较少的嘉年华的活动数量的最大值。
【输入格式】
从文件 show.in 中读入数据。
输入的第一行包含一个整数 n,表示申请的活动个数。
接下来 n 行描述所有活动,其中第 i 行包含两个整数 Si、Ti,表示第 i 个活
动从时刻 Si 开始,持续 Ti 的时间。
【输出格式】
输出到文件 show.out 中。
输出的第一行包含一个整数,表示在没有任何限制的情况下,活动较少的嘉年华的活动数的最大值。 接下来 n 行每行一个整数,其中第 i 行的整数表示在必须选择第 i 个活动的前提下,活动较少的嘉年华的活动数的最大值。
【评分标准】
对于一个测试点:
l 如果输出格式不正确(比如输出不足 n+1 行),得 0 分; l 如果输出文件第一行不正确,而且后 n 行至少有一行不正确,得 0 分;
l 如果输出文件第一行正确,但后 n 行至少有一行不正确,得 4 分;
l 如果输出文件第一行不正确,但后 n 行均正确,得 6 分;
l 如果输出文件中的 n+1 行均正确,得 10 分。
【样例输入】
5
8 2
1 5
5 3
3 2
5 3
【样例输出】
2
2
1
2
2
2
【样例说明】
在没有任何限制的情况下,最优安排可以在一个嘉年华安排活动 1, 4,而在另一个嘉年华安排活动 3, 5,活动 2 不安排。
【数据规模与约定】
所有测试数据的范围和特点如下表所示
测试点编号 |
n 的规模 |
约定 |
1 |
1≤n≤10 |
0≤Si≤109 1≤Ti≤ 109 |
2 |
1≤n≤40 |
|
3 |
||
4 |
1≤n≤200 |
|
5 |
||
6 |
||
7 |
||
8 |
||
9 |
||
10 |
解析:
考场时大概猜到是$DP$,但太菜了想不到怎么$DP$,于是就只写了$10$分的暴力,最后结果还只有$4$分
先把时间离散化,设总时间为$cnt$
处理出$H[i][j]$,表示活动的开始与结束时间都在$[i, j]$中的活动数, $O(n^{2})$
再处理出$f[i][j]$, 表示前$i$的时间内,一个分会场安排$j$个活动,另一个分会场最多安排多少活动, 注意这个有两个转移方式, 设$k$为之前的某个时间点,显然$H[k][i]$可以安排在分会场一或分会场二, 因此我们有:
$f[i][j] = max(f[i][j], f[k][j] + H[k][i])$, $f[i][j] = max(f[i][j], f[k][j-H[k][i]])$
$O(n^{3})$
继续处理出$g[i][j]$,表示从$i$开始到最后的时间内,分会场一安排$j$个活动,分会场二最多安排多少活动, 转移方式与$f$数组类似
$O(n^{3})$
显然可以得到没有任何限制情况下的答案:
$ans = maxleft { min(i, f[cnt][i]) ight } (0 leqslant i leqslant n)$
$O(n)$
麻烦就在于如何处理有限制条件下的答案
再设一个$dp[i][j]$, 表示时间$[i, j]$内的活动必选的情况下,活动最少的分会场最多能安排多少活动,容易得到一个简单的转移方程:
$dp[i][j] = maxleft { min(l+H[i][j]+r, f[i][l]+g[j][r]) ight }$
$l$表示在前$i$的时间内安排$l$个活动,$r$表示在后$j$的时间内安排$r$个活动, 并且这$l$与$r$个活动与时间$[i, j]$内的活动安排在同一会场
这个转移是$O(n^{4})$的, 显然会$T$
考虑到对于任一$l$,随着的$l$增加,$f[i][l]$必然减少,$r$与$g[j][r]$也是同理。那么对于同一个$[i, j]$,随着$l$的增加,$r$一定比上一次在$l-1$时取的$r$小,因此可以把$r$改成一个从右向左移动的指针, 这样时间复杂度就降成了$O(n^{3})$,就可过了
最后对于一个时间是$[l,r]$的活动, 它的答案应该是$maxleft { dp[i][j]
ight }(1leqslant ileqslant l, rleqslant jleqslant cnt)$, 因为取$dp[l][r]$并不一定是最优的情况, 通过$dp$数组的转移方程就可以看出可能存在$dp[i][j] geqslant dp[l][r] (1leqslant ileqslant l, rleqslant jleqslant cnt)$
细节还是看代码吧
代码:
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; const int maxn = 505, inf = 0x3f3f3f3f; int n, a[maxn], cnt; int H[maxn][maxn], f[maxn][maxn], dp[maxn][maxn], g[maxn][maxn]; int ans[maxn]; struct seg{ int l, r, id; }s[maxn]; bool cmp(seg x, seg y) { return x.r != y.r? x.r < y.r: x.l > y.l; } int main() { freopen("show.in", "r", stdin); freopen("show.out", "w", stdout); scanf("%d", &n); for(int i = 1; i <= n; ++i) { int x, y; scanf("%d%d", &x, &y); s[i].l = x; s[i].r = x + y; s[i].id = i; a[++cnt] = x; a[++cnt] = x + y; } sort(a + 1, a + cnt + 1); cnt = unique(a + 1, a + cnt + 1) - a - 1; sort(s + 1, s + n + 1, cmp); for(int i = 1; i <= n; ++i) { s[i].l = lower_bound(a + 1, a + cnt + 1, s[i].l) - a; s[i].r = lower_bound(a + 1, a + cnt + 1, s[i].r) - a; } for(int i = 1; i <= cnt; ++i) for(int j = 0; j <= n; ++j) f[i][j] = g[i][j] = -inf; int now = 1; for(int i = 1; i <= cnt; ++i) for(int j = i - 1; j >= 1; --j) { H[j][i] = H[j][i - 1] + H[j+1][i] - H[j+1][i-1]; while(s[now].r == i && s[now].l == j) { H[j][i] ++; now ++; } } for(int i = 1; i <= cnt; ++i) for(int j = 0; j <= H[1][i]; ++j) if(j == 0) f[i][j] = H[1][i]; else for(int k = 1; k < i; ++k) { if(j <= H[1][k]) f[i][j] = max(f[i][j], f[k][j] + H[k][i]); if(j >= H[k][i]) f[i][j] = max(f[i][j], f[k][j-H[k][i]]); } for(int i = cnt; i >= 1; --i) for(int j = 0; j <= H[i][cnt]; ++j) if(j == 0) g[i][j] = H[i][cnt]; else for(int k = i + 1; k <= cnt; ++k) { if(j <= H[k][cnt]) g[i][j] = max(g[i][j], g[k][j] + H[i][k]); if(j >= H[k][i]) g[i][j] = max(g[i][j], g[k][j-H[i][k]]); } for(int i = 0; i <= n; ++i) ans[0] = max(ans[0], min(i, f[cnt][i])); for(int i = 1; i <= cnt; ++i) for(int j = i + 1; j <= cnt; ++j) { int l = 0, r = n, now, nxt; for(; l <= n; ++l) { for(; r; --r) { now = min(l + r + H[i][j], f[i][l] + g[j][r]); nxt = min(l + r - 1 + H[i][j], f[i][l] + g[j][r-1]); if(nxt < now) break; } if(!r) now = min(l + r + H[i][j], f[i][l] + g[j][r]); dp[i][j] = max(now, dp[i][j]); } } for(int i = 1; i <= n; ++i) { for(int l = 1; l <= s[i].l; ++l) for(int r = s[i].r; r <= cnt; ++r) ans[s[i].id] = max(ans[s[i].id], dp[l][r]); } for(int i = 0; i <= n; ++i) printf("%d ", ans[i]); return 0; }
T3:兔兔与蛋蛋游戏
【问题描述】
这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏。 这个游戏是在一个 n 行 m 列的棋盘上进行的。游戏开始之前,棋盘上有一个格子是空的,其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色。
每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为:
l 兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格。 l 蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格。
第一个不能按照规则操作的人输掉游戏。为了描述方便,下面将操作“将第x 行第 y 列中的棋子移进空格中”记为 M(x,y)。
例如下面是三个游戏的例子。
(略)
最近兔兔总是输掉游戏,而且蛋蛋格外嚣张,于是兔兔想请她的好朋友——你——来帮助她。她带来了一局输给蛋蛋的游戏的实录,请你指出这一局游戏中所有她“犯错误”的地方。
注意:
l 两个格子相邻当且仅当它们有一条公共边。
l 兔兔的操作是“犯错误”的,当且仅当,在这次操作前兔兔有必胜策略,而这次操作后蛋蛋有必胜策略。
【输入格式】
从文件 game.in 中读入数据。 输入的第一行包含两个正整数 n、m。
接下来 n 行描述初始棋盘。其中第 i 行包含 m 个字符,每个字符都是大写英文字母"X"、大写英文字母"O"或点号"."之一,分别表示对应的棋盘格中有黑色棋子、有白色棋子和没有棋子。其中点号"."恰好出现一次。
接下来一行包含一个整数 k(1≤k≤1000),表示兔兔和蛋蛋各进行了 k 次操作。
接下来 2k 行描述一局游戏的过程。其中第 2i – 1 行是兔兔的第 i 次操作(编号为 i 的操作),第 2i 行是蛋蛋的第 i 次操作。每个操作使用两个整数 x,y 来描述,表示将第 x 行第 y 列中的棋子移进空格中。 输入保证整个棋盘中只有一个格子没有棋子,游戏过程中兔兔和蛋蛋的每个操作都是合法的,且最后蛋蛋获胜。
【输出格式】
输出到文件 game.out 中。
输出文件的第一行包含一个整数 r,表示兔兔犯错误的总次数。
接下来 r 行按递增的顺序给出兔兔“犯错误”的操作编号。其中第 i 行包含一个整数 ai 表示兔兔第 i 个犯错误的操作是他在游戏中的第 ai 次操作。
【输入样例 1】
1 6
XO.OXO
1
1 2
1 1
【输出样例 1】
1
1
【输入样例 2】
3 3
XOX
O.O XOX
4
2 3
1 3
1 2
1 1
2 1
3 1
3 2
3 3
【输出样例 2】
0
【输入样例 3】
4 4
OOXX
OXXO
OO.O
XXXO
2
3 2
2 2
1 2
1 3
【输出样例 3】
2
1
2
【样例说明】
样例 1 对应图一中的游戏过程。 样例 2 对应图三中的游戏过程。
【数据规模】
所有测试数据的范围和特点如下表所示
测试点编号 |
n 的规模 |
m 的规模 |
1 |
n = 1 |
1≤ m≤ 20 |
2 |
||
3 |
n = 3 |
m = 4 |
4 |
n = 4 |
m = 4 |
5 |
||
6 |
n = 4 |
m = 5 |
7 |
||
8 |
n = 3 |
m = 7 |
n = 2 |
1 ≤m≤ 40 |
|
9 | ||
10 | ||
11 | ||
12 | ||
13 |
||
14 |
||
15 |
1 ≤n≤ 16 |
1 ≤m≤ 16 |
16 |
||
17 |
1 ≤n≤ 40 |
1 ≤m≤ 40 |
18 |
||
19 |
||
20 |
解析:
博弈论转化为二分图
首先是有一个性质,移动棋子的路径不会交叉
把 . 看做X, 然后把X当做一边的点, 再把O看做另一边的点, 每个原图中的点向四周不同颜色的点连边#include<cstdio> #include<iostream> #include<algorithm> using namespace std; const int maxn = 50; int n, m, sx, sy; char s[maxn][maxn]; int dx[4] = {1, -1, 0, 0}; int dy[4] = {0, 0, 1, -1}; int head[maxn*maxn], tot; struct edge{ int nxt, to; }e[maxn*maxn*4]; void Addedge(int x, int y) { e[++tot] = (edge){head[x], y}; head[x] = tot; } int mat[maxn*maxn], ban[maxn*maxn], vis[maxn*maxn], timer; int ans[maxn*maxn], cnt; bool flag[maxn*maxn]; bool dfs(int x) { if(vis[x] == timer) return 0; vis[x] = timer; for(int i = head[x]; i; i = e[i].nxt) { int id = e[i].to; if(ban[id] == 1) continue; if(!mat[id] || dfs(mat[id])) { mat[id] = x; mat[x] = id; return 1; } } return 0; } int main() { scanf("%d%d", &n, &m); for(int i = 1; i <= n; ++i) scanf("%s", s[i] + 1); for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) { if(s[i][j] == '.') { sx = i; sy = j; } if(s[i][j] == 'O') for(int k = 0; k < 4; ++k) if(i + dx[k] >= 1 && i + dx[k] <= n && j + dy[k] >= 1 && j + dy[k] <= m && s[i][j] != s[i+dx[k]][j+dy[k]]) { Addedge((i - 1) * m + j, (i + dx[k] - 1) * m + j + dy[k]); Addedge((i + dx[k] - 1) * m + j + dy[k], (i - 1) * m + j); } } for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) if(!mat[(i - 1) * m + j]) { ++timer; dfs((i - 1) * m + j); } int q; scanf("%d", &q); for(int i = 1; i <= (q<<1); ++i) { int x = (sx - 1) * m + sy; ban[x] = 1; if(mat[x]) { int y = mat[x]; mat[x] = mat[y] = 0; ++timer; flag[i] = !dfs(y); } scanf("%d%d", &sx, &sy); if(!(i&1)) if(flag[i-1] && flag[i]) ans[++cnt] = (i>>1); } printf("%d ", cnt); for(int i = 1; i <= cnt; ++i) printf("%d ", ans[i]); return 0; }