winter training 8 E F题解
E题
树上三角形 BZOJ3251 (斐波那契的性质+暴力)
题解:
若路径上有两点的点权为x,y
则若有个点z且z>abs(x-y)且z<x+y,则可以构成三角形
类似斐波那契数列1 2 3 5 8 13 。。。
发现最好情况下int范围只有不到50个点满足无法构成三角形
那么只要路径点超过50个直接输出Y,否则暴力。
类似的校赛上的一道题,HZNU2580也是这个思想。
#include<bits/stdc++.h> using namespace std; #define pi acos(-1.0) #define inf 0x3f3f3f3f typedef long long ll; typedef double db; int mo[4][2] = { -1,0,1,0,0,-1,0,1 }; //int mo[8][2] = { -1,0,1,0,-1,-1,1,1,0,-1,0,1,1,-1,-1,1 }; const int maxn = 2e5 + 10; ll n, m, is = 0, t, c = 0, ans = 0, p[maxn], dep[maxn], fa[maxn], q[maxn]; vector< ll >v[maxn]; bool vis[maxn]; inline ll io() { ll sum = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-')f = -1; ch = getchar(); } while (ch <= '9' && ch >= '0') { sum = (sum << 1) + (sum << 3) + (ch ^ 48); ch = getchar(); } return sum * f; } void dfs(int x) { for (int i = 0; i < v[x].size(); i++) { dep[v[x][i]] = dep[x] + 1; dfs(v[x][i]); } } int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cout << fixed << setprecision(2); n = io(), m = io(); for (int i = 1; i <= n; i++)p[i] = io(); for (int i = 1; i < n; i++) { ll a = io(), b = io(); v[a].push_back(b); fa[b] = a; } dep[1] = 1; dfs(1); while (m--) { ll a, b, op; scanf("%lld%lld%lld", &op, &a, &b); if (op)p[a] = b; else { is = 0; int cnt = 0; if (dep[a] < dep[b])swap(a, b); while (dep[a] > dep[b]) { q[++cnt] = p[a]; a = fa[a]; if (cnt >= 50) { is = 1; break; } } while (a != b) { q[++cnt] = p[a]; q[++cnt] = p[b]; a = fa[a]; b = fa[b]; if (cnt >= 50) { is = 1; break; } } q[++cnt] = p[a]; if (cnt >= 50)is = 1; sort(q + 1, q + 1 + cnt); for (int i = 3; i <= cnt; i++) { if (q[i - 2] + q[i - 1] > q[i]) { is = 1; break; } } if (is)printf("Y\n"); else printf("N\n"); } } return 0; }
F题:
Buy or Build UVA1151 (最小生成树+状压枚举)
题意:平面上有N个点(1≤N≤1000),若要新建边,费用是2点的欧几里德距离的平方。另外还有Q个套餐,每个套餐里的点互相联通,总费用为Ci。问让所有N个点连通的最小费用。(2组数据的输出之间要求有换行)
题解:这是《算法竞赛入门经典》P368上的例题。q很小,由于枚举量为O(2^q),给边排序的时间复杂度为O(n^2logn),而排序之后每次kruskal算法的时间复杂度为O(n^2),因此总时间复杂度为O(2^qn^2+n^2logn),对于题目来说的规模太大了。只需一个小小的优化即可降低时间复杂度:先求一次原图 (不购买任何套餐) 的最小生成树,得到n-1条边,其余的边就没用了。然后枚举买哪些套餐,则枚举套餐后再求最小生成树时,图上的边已经寥寥无几。在枚举的时候注意要用到状压。
#include<bits/stdc++.h> using namespace std; #define pi acos(-1.0) #define inf 0x3f3f3f3f typedef long long ll; typedef double db; int mo[4][2] = { -1,0,1,0,0,-1,0,1 }; //int mo[8][2] = { -1,0,1,0,-1,-1,1,1,0,-1,0,1,1,-1,-1,1 }; const int maxn = 4010; int n, m, is = 0, t, c = 0, p[maxn]; struct xx { int ch; int q[maxn]; int g; }xx[10]; struct node { int x, y; }d[maxn]; struct edge { int f, t, w; }edge[maxn * maxn]; int find(int x) { if (x == p[x])return x; return p[x] = find(p[x]); } bool cmp(struct edge a, struct edge b) { return a.w < b.w; } int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cout << fixed << setprecision(2); scanf("%d", &t); while (t--) { scanf("%d%d", &n, &m); c = 0; for (int i = 1; i <= m; i++) { int ch, a; scanf("%d%d", &a, &ch); xx[i].ch = ch; xx[i].g = a; for (int k = 1; k <= a; k++)scanf("%d", &xx[i].q[k]); } for (int i = 1; i <= n; i++)scanf("%d%d", &d[i].x, &d[i].y); for (int i = 1; i <= n; i++) { for (int k = i + 1; k <= n; k++) { edge[++c].w = (d[i].x - d[k].x) * (d[i].x - d[k].x) + (d[i].y - d[k].y) * (d[i].y - d[k].y); edge[c].f = i; edge[c].t = k; } } ll ans = 1e18; sort(edge + 1, edge + 1 + c, cmp); for (int i = 0; i < (1 << m); i++) { int cnt = 0; ll sum = 0; for (int k = 1; k <= n; k++)p[k] = k; for (int k = 1; k <= m; k++) { if (i & (1 << (k - 1))) { sum += xx[k].ch; int x = find(xx[k].q[1]); for (int j = 2; j <= xx[k].g; j++) { int y = find(xx[k].q[j]); if (x != y)p[y] = x, ++cnt; } } } for (int i = 1; cnt < n - 1; i++) { int x = find(edge[i].f), y = find(edge[i].t); if (x != y)sum += edge[i].w, p[x] = y, ++cnt; } ans = min(ans, sum); } printf("%lld\n", ans); if (t)printf("\n"); } return 0; }