介于本场比赛题目难度并不按照难度顺序排列,所以题解顺序不同。
需要题面或数据可以私聊我QQ(不一定在):468170499.
T3:最短路(path)
来源:jzoj3470
Describe:
给定一个n个点m条边的有向图,有k个标记点,要求从规定的起点按任意顺序经过所有标记点到达规定的终点,问最短的距离是多少。
Hint:
20%的数据n<=10。
50%的数据n<=1000。
另有20%的数据k=0。
100%的数据n<=50000,m<=100000,0<=k<=10,1<=z<=5000。
解题思路:
我在考场上写这道题的时候,一眼没看出来。在最后看了一眼数据,发现k<=10,那么答案就非常显然了。我们可以把标记点为起点跑一边最短路。我感觉可能会卡SPFA(然而出题人很凉心),所以就写了一个堆优化的Dijkstra。时间复杂度为O(log(N + M) * N * K)(我习惯变量用小写)。因为k很小,所以我们可以枚举所有的方案,再跑一层k的循环判断。时间复杂度为(K! * K),勉强不会超时,可以写状态压缩优化成O(2^K * K * K),因为这道题暴力能过,所以就没写,以后有空再补吧。这道题我在考场上是只过了一个样例,只有九分。主要是没看清题,有向图连了无向边。还有dis数组因为不能开到n方的大小,所有有一维应该要存点的编号,然后我在这里好像弄错了一点什么。。。
Code:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 50500, M = 202000, K = 11; int n, m, k, s, t; int tot = 0, Link[M], Next[M], to[M]; ll w[M]; inline int read() { int num = 0; bool flag = 1; char c = getchar(); for (; c < '0' || c > '9'; c = getchar()) if (c == '-') flag = 0; for (; c >= '0' && c <= '9'; c = getchar()) num = (num << 3) + (num << 1) + c - 48; return flag ? num : -num; } inline ll readll() { ll num = 0; bool flag = 1; char c = getchar(); for (; c < '0' || c > '9'; c = getchar()) if (c == '-') flag = 0; for (; c >= '0' && c <= '9'; c = getchar()) num = (num << 3) + (num << 1) + c - 48; return flag ? num : -num; } inline void add(int x, int y, ll z) { Next[++ tot] = Link[x]; Link[x] = tot; to[tot] = y; w[tot] = z; } // dis[i][j]表示从point[i]号节点为起点到j的最短距离 ll minn = LLONG_MAX; int ans[N], point[N]; bool use[K]; ll dis[K][N]; bool vis[K][N]; void dij(int start) { priority_queue < pair < ll, int> > q; for (int i = 1; i <= n; ++ i) dis[start][i] = 1e15, vis[start][i] = 0; dis[start][point[start]] = 0; q.push(make_pair(0, point[start])); for(; q.size(); ) { int x = q.top().second; q.pop(); if (vis[start][x]) continue; vis[start][x] = 1; for(int i = Link[x]; i; i = Next[i]) { int y = to[i], z = w[i]; if (dis[start][y] > dis[start][x] + z) { dis[start][y] = dis[start][x] + z; q.push(make_pair(-dis[start][y], y)); } } } } void dfs(int depth) { if (depth == k + 1) { ll sum = 0; sum += dis[0][point[ans[1]]]; for (int i = 1; i < k; ++ i) sum += dis[ans[i]][point[ans[i + 1]]]; sum += dis[ans[k]][t]; minn = min(minn, sum); } for (int i = 1; i <= k; ++ i) if (!use[i]) { use[i] = 1; ans[depth] = i; dfs(depth + 1); use[i] = 0; } } int main() { freopen("path.in", "r", stdin); freopen("path.out", "w", stdout); n = read(), m = read(), k = read(), s = read(), t = read(); for (int i = 1; i <= m; ++ i) { int x = read(), y = read(); ll z = readll(); add(x, y, z); } point[0] = s; dij(0); for (int i = 1; i <= k; ++ i) { int sign = read(); point[i] = sign; dij(i); } dfs(1); if (minn == 1e15) puts("-1"); else printf("%lld\n", minn); return 0; }
T1:生成输入数据(input)
Describe:
首先看到题目别太开心,这题可不是让你出数据~^_*
背景神马的就忽略了。这题就是给你一棵带边权的树,然后这棵树是某个完全图唯一的最小生成树。问原来的完全图中所有边可能的最小边权和是多少。
完全图是任意两个点之间都有边相连的图。
Hint:
20%的数据满足:T≤5,n≤5,wi≤5
另外30%的数据满足:n≤1000,给定的树是一条链
100%的数据满足:T≤10,n≤20000,wi≤10000
解题思路:
来源:jzoj2490
刚拿到这道题的时候,一脸懵逼,反正最后看了半天没看出来,就写了一个最小边+1的贪心,不知道这次为什么这次数据那么毒瘤,一分都没拿到。
这道题其实和TYVJ1391走廊泼水节相似,只要在走廊泼水节的答案+原图的所有权值和就是答案了。可以从Kruskal的思想入手(可以理解为反着做Kruskal):把边按边权从小到大排序,判断当前边所连向的两点是否在同一联通块内,如果不是,则答案+=(两个联通块内的点互相连接的边数-1)*(当前边+1(+1是因为整张图只能有原图那一颗最小生成树,否则可能有多棵最小生成树)),所以我们可以再开一个数组存每个联通块的大小即可。
本题需要long long!
Code:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 20200; int T, n; ll ans; struct edge{ int h, t; ll w; }e[N]; inline int read() { int num = 0; bool flag = 1; char c = getchar(); for (; c < '0' || c > '9'; c = getchar()) if (c == '-') flag = 0; for (; c >= '0' && c <= '9'; c = getchar()) num = (num << 3) + (num << 1) + c - 48; return flag ? num : -num; } inline ll readll() { ll num = 0; bool flag = 1; char c = getchar(); for (; c < '0' || c > '9'; c = getchar()) if (c == '-') flag = 0; for (; c >= '0' && c <= '9'; c = getchar()) num = (num << 3) + (num << 1) + c - 48; return flag ? num : -num; } bool cmp(edge x, edge y) { return x.w < y.w; } int fa[N]; ll size[N]; inline int get(int x) { if (x == fa[x]) return x; return fa[x] = get(fa[x]); } int main() { freopen("input.in", "r", stdin); freopen("input.out", "w", stdout); T = read(); while(T --) { ans = 0; n = read(); for (int i = 1; i < n; ++ i) { e[i].h = read(); e[i].t = read(); e[i].w = readll(); ans += e[i].w; } sort(e + 1, e + n, cmp); for (int i = 1; i <= n; ++ i) fa[i] = i, size[i] = 1; for (int i = 1; i < n; ++ i) { int x = e[i].h, y = e[i].t; ll z = e[i].w; int fx = get(x), fy = get(y); if (fx != fy) { ans += (z + 1) * (size[fx] * size[fy] - 1LL); fa[fx] = fy; size[fy] += size[fx]; } } printf("%lld\n", ans); } return 0; }
T2:三角形灯阵(triangle)
Describe:
中秋节的晚上,小x在桌面上放了许多好看的彩灯。遗憾的是,这些彩灯可能并非 全部都亮着。于是,小x打算把全部这些彩灯都点亮。
但是,小x很快发现,这些彩灯的摆放是非常有规律的,事实上,彩灯的位置都在平面的 正三角形镶嵌的某个交点处。距离为单位长度的彩灯被认为相互相邻。可以看出,每个彩灯 最多与六个彩灯相邻,相邻的彩灯都在以其为中心的单位正六边形的顶点上。
下图就是一种合法的彩灯摆放(对应样例数据):
其中,实心和空心的圆点分别代表亮与灭的彩灯,实线边代表彩灯间的相邻关系。
图中的边组成了若干单位正三角形(边长为单位长度的三角形),可以看出,每个彩灯 与它相邻的彩灯最多可以组成六个单位正三角形。而所有的单位三角形可被分成两类,我们 称为 A 类以及 B 类,图中加阴影的三角形属于 A 类。A 类三角形的特征是,三个顶点分别 在上方、左下方及右下方。
每个 A 类三角形中有一个开关(图中未画出),按动三角形中的开关 开关会改变三个顶点上每个彩灯的亮灭状态(亮变成灭,灭变成亮)。
那么,要点亮所有彩灯,最少需要按动多少次开关呢?
Hint:
对于 30%的数据,N<=100。
对于 100%的数据,3<=N<=50000。
解题思路:
仔细读题发现,当遇到一个A型三角形时,只要保证顶上那个点是亮的就行了吧,那么我们把这个点和跟这个点有关的边去掉,即这个点连向的点的入度--,如果这个点入度为0,那么入队,反复操作就好。
所以我们只要关注一个点左上、右上、左下、右下(其实我们让一个点与它左上、右上的点连一条有向边就好)的点,所以只要连接当前点与这些点即可(最开始应该把入度为0的点入队)
(说的不是很清楚,还是看die码吧,其实就是一个topsort的过程,所以才说数据范围是突破点)
Code:
#include<bits/stdc++.h> using namespace std; const int N = 50050, M = N * 6; int n, m, ans = 0; bool val[N]; int in[N]; int tot = 0, Link[M]; struct edge{ int next, to; }e[M]; inline void add(int x, int y) { e[++ tot].next = Link[x]; Link[x] = tot; e[tot].to = y; } inline int read() { int num = 0; bool flag = 1; char c = getchar(); for (; c < '0' || c > '9'; c = getchar()) if (c == '-') flag = 0; for (; c >= '0' && c <= '9'; c = getchar()) num = (num << 3) + (num << 1) + c - 48; return flag ? num : -num; } queue <int> q; void topsort() { for (int i = 1; i <= n; ++ i) if (!in[i]) q.push(i); while(q.size()) { int x = q.front(); q.pop(); if (!val[x]) ++ ans;
// 如果val[x]==0,即需要被点亮,则累计答案 for (int i = Link[x]; i; i = e[i].next) { int y = e[i].to; if (!val[x]) val[y] ^= 1;
// ^= 1 表示0变成1,1变成0 -- in[y]; if (!in[y]) q.push(y); } } } int main() { freopen("triangle.in", "r", stdin); freopen("triangle.out", "w", stdout); n = read(); char ch; for (int i = 1; i <= n; ++ i) val[i] = (bool)((int)getchar() - 48), ch = getchar(); m = read(); for (int i = 1; i <= m; ++ i) { int x = read(), y = read(), z = read(); if (z == 1 || z == 2) ++ in[y], add(x, y); if (z == 4 || z == 5) ++ in[x], add(y, x);
//注意连边方向 } topsort(); printf("%d\n", ans); return 0; }
T4:map(map)
Describe:
Hint:
解题思路:
看到数据范围,发现n,q<=4000的直接暴力加边就好,树也是很显然,然而我写T3过于执着(悲催的是最后还没分),就没写这60分,有点伤……
我先想到一种方法,可以把图上每个强连通分量缩成一个点,然后整张图变成了一棵树,然后每个询问输出它们的距离(LCA即可)。然而我的这个方法很快就被yjz dalao叉掉了:这张图里可能有重边,那么就不会有树。
正解其实已经很接近了(估计是在想这个算法的时候题意理解错了吧),我们可以按照边双缩成一个点,然后