[题解]CLYZ2018省选训(bao)练(zha)模拟赛 Day 7

思维题真恶心。

题目

T1:BZOJ 1201 / HNOI 2005 数三角形 (triangle)(树状数组)
T2:BZOJ 3624 / APIO 2008 免费道路 (roads)(思路+并查集)
T3:BZOJ 2663 / BeiJing WC 2012 灵魂宝石 (soulgems)(二分图匹配)

T1

分析

下面定义 (i,j) ( i , j ) 表示第 i i 行的第j顶点(共 n+1 n + 1 行,第 i i 行有i个顶点)
考虑一个朴素的做法:
一、利用递推,预处理出 5 5 个值:
(1)lef[i][j] (i,j) ( i , j ) 通过未被删除的边,向左延伸的最长距离,如样例中 lef[3][3]=0 l e f [ 3 ] [ 3 ] = 0 lef[4][4]=3 l e f [ 4 ] [ 4 ] = 3
(2) leup[i][j] l e u p [ i ] [ j ] (i,j) ( i , j ) 通过未被删除的边,向左上延伸的最长距离。
(3) riup[i][j] r i u p [ i ] [ j ] (i,j) ( i , j ) 通过未被删除的边,向右上延伸的最长距离。
(4) ledw[i][j] l e d w [ i ] [ j ] (i,j) ( i , j ) 通过未被删除的边,向左下延伸的最长距离。
(5) ridw[i][j] r i d w [ i ] [ j ] (i,j) ( i , j ) 通过未被删除的边,向右下延伸的最长距离。
统计正立三角形时,先枚举右下顶点 (i,j) ( i , j ) 。容易得出,
右下顶点为 (i,j) ( i , j ) 的正立三角形个数不超过 min(lef[i][j],leup[i][j]) min ( l e f [ i ] [ j ] , l e u p [ i ] [ j ] )
k=jmin(lef[i][j],leup[i][j]) k = j − min ( l e f [ i ] [ j ] , l e u p [ i ] [ j ] ) ,那么第 i i 行的第[k,j1]个顶点都有可能成为正立三角形的左下角。
而对于任何的 h[k,j1] h ∈ [ k , j − 1 ] (i,h) ( i , h ) 向右上延伸后,能与 (i,j) ( i , j ) 构成正立三角形的充分必要条件是:

riup[i][h]jh r i u p [ i ] [ h ] ≥ j − h

统计倒立三角形也一样,先枚举右上顶点 (i,j) ( i , j )
右上顶点为 (i,j) ( i , j ) 的正立三角形个数不超过 min(lef[i][j],ledw[i][j]) min ( l e f [ i ] [ j ] , l e d w [ i ] [ j ] )
k=jmin(lef[i][j],ledw[i][j]) k = j − min ( l e f [ i ] [ j ] , l e d w [ i ] [ j ] ) ,那么第 i i 行的第[k,j1]个顶点都有可能成为倒立三角形的左上角。
对于任何的 h[k,j1] h ∈ [ k , j − 1 ] (i,h) ( i , h ) 向右下延伸后,能与 (i,j) ( i , j ) 构成倒立三角形的充分必要条件是:
ridw[i][h]jh r i d w [ i ] [ h ] ≥ j − h

复杂度是 O(n3) O ( n 3 ) 的。考虑将上面给出的两个不等式移项:
riup[i][h]+hj r i u p [ i ] [ h ] + h ≥ j

ridw[i][j]+hj r i d w [ i ] [ j ] + h ≥ j

这样就转换成了求一个区间内,有多少个数大于一个定值。将询问离线排序之后,可以使用树状数组解决。复杂度 O(n2logn) O ( n 2 log ⁡ n )

Source

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1024, M = 4096;
int n, led[N][N], rid[N][N], lef[N][N], leu[N][N], riu[N][N], T[M], tot; ll ans;
void change(int x, int v) {
    for (; x <= 4000; x += x & -x) T[x] += v;
}
int ask(int x) {
    int res = 0; for (; x; x -= x & -x) res += T[x]; return res;
}
struct cyx {
    int id, ri;
    cyx() {}
    cyx(int _id, int _ri) : id(_id), ri(_ri) {}
} otz[M];
inline bool comp(const cyx &a, const cyx &b) {
    return a.ri > b.ri;
}
int main() {
    int i, j, x, y, z; n = read();
    for (i = 1; i <= n; i++) for (j = 1; j <= i; j++) {
        x = read(); y = read(); z = read();
        lef[i + 1][j + 1] = z; led[i][j] = riu[i + 1][j] = x;
        rid[i][j] = leu[i + 1][j + 1] = y;
    }
    n++; for (i = 1; i <= n; i++) for (j = 1; j <= i; j++)
        lef[i][j] = lef[i][j] ? lef[i][j - 1] + 1 : 0;
    for (j = 1; j <= n; j++) for (i = j; i <= n; i++) {
        leu[i][j] = leu[i][j] ? leu[i - 1][j - 1] + 1 : 0;
        riu[i][j] = riu[i][j] ? riu[i - 1][j] + 1 : 0;
    }
    for (i = n; i; i--) for (j = 1; j <= i; j++) {
        led[i][j] = led[i][j] ? led[i + 1][j] + 1 : 0;
        rid[i][j] = rid[i][j] ? rid[i + 1][j + 1] + 1 : 0;
    }
    for (i = 1; i <= n; i++) for (j = 1; j <= i; j++)
        riu[i][j] += j, rid[i][j] += j;
    for (i = 1; i <= n; i++) {
        for (j = 0; j <= 4000; j++) T[j] = 0; tot = 0;
        for (j = 1; j <= i; j++) otz[++tot] = cyx(j, riu[i][j]);
        sort(otz + 1, otz + tot + 1, comp); int orz = 1;
        for (j = i; j; j--) {
            while (orz <= tot && otz[orz].ri >= j)
                change(otz[orz].id, 1), orz++;
            ans += ask(j - 1) - ask(j - min(lef[i][j], leu[i][j]) - 1);
        }
        for (j = 0; j <= 4000; j++) T[j] = 0; tot = 0;
        for (j = 1; j <= i; j++) otz[++tot] = cyx(j, rid[i][j]);
        sort(otz + 1, otz + tot + 1, comp); orz = 1;
        for (j = i; j; j--) {
            while (orz <= tot && otz[orz].ri >= j)
                change(otz[orz].id, 1), orz++;
            ans += ask(j - 1) - ask(j - min(lef[i][j], led[i][j]) - 1);
        }
    }
    cout << ans << endl;
    return 0;
}

T2

分析

这题真的很难想。方法:
(1)先把所有的水泥路加上,然后求出必须加上的鹅卵石路。
(2)把必须加上的鹅卵石路加上,然后加其他的鹅卵石路,直到满 k k 个为止。最后加上剩余的水泥路。注意要一直保持树结构(会形成环的边不能加)。
注意判无解:
1、鹅卵石路的数量<k
2、必须加的鹅卵石路的数量 >k > k
3、所有边加入都无法连通

Source

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 2e4 + 5, M = 1e5 + 5;
int n, m, K, X[M], Y[M], V[M], fa[N], T, aX[M], aY[M], aV[M];
bool mst[M];
int cx(int x) {
    if (fa[x] != x) fa[x] = cx(fa[x]); return fa[x];
}
bool zm(int x, int y) {
    int ix = cx(x), iy = cx(y); if (ix != iy) return fa[iy] = ix, 1; return 0;
}
int main() {
    int i, cnt = 0, w = 0; n = read(); m = read(); K = read();
    for (i = 1; i <= m; i++) X[i] = read(), Y[i] = read(), V[i] = read();
    for (i = 1; i <= n; i++) fa[i] = i;
    for (i = 1; i <= m; i++) if (V[i]) if (zm(X[i], Y[i])) cnt++;
    for (i = 1; i <= m; i++)
        if (!V[i]) {
            if (zm(X[i], Y[i])) aX[++T] = X[i],
                aY[T] = Y[i], aV[T] = V[i], cnt++, mst[i] = 1; w++;
        }
    if (T > K || w < K) return puts("no solution"), 0;
    for (i = 2; i <= n; i++) if (cx(i) != cx(1))
        return puts("no solution"), 0;
    for (i = 1; i <= n; i++) fa[i] = i;
    for (i = 1; i <= T; i++) zm(aX[i], aY[i]);
    for (i = 1; i <= m; i++) if (!V[i] && !mst[i]) {
        if (zm(X[i], Y[i])) aX[++T] = X[i], aY[T] = Y[i],
        aV[T] = V[i], mst[i] = 1; if (T == K) break;
    }
    if (T < K) return puts("no solution"), 0;
    for (i = 1; i <= m; i++) if (V[i] && zm(X[i], Y[i]))
        aX[++T] = X[i], aY[T] = Y[i], aV[T] = V[i];
    for (i = 1; i <= T; i++) printf("%d %d %d\n", aX[i], aY[i], aV[i]);
    return 0;
}

T3

分析

显然是二分图匹配。求 Rmin R min 比较简单:
先二分答案。转化成判定性问题:
一个二分图,一边是魔法少女,一边是灵魂宝石。如果一个魔法少女和一个灵魂宝石之间的距离 R ≤ R ,就连一条边。求最大匹配数和 K K 的大小关系。
这样是裸的二分图匹配。
Rmax就是:
同样一个二分图。如果一个魔法少女和一个灵魂宝石之间的距离 <R < R <script type="math/tex" id="MathJax-Element-807"> (并且不能有还能匹配的边)和 K K 的大小关系。
显然(也就是我不会严谨证明),求出这个二分图的补图的最大匹配,再用N减去最大匹配,就是要求出的最小匹配。
特殊情况: N=K N = K 时, Rmax=+INF R max = + INF

Source

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 3e5 + 5; const double eps = 1e-9;
int n, K, X[N], Y[N], x[N], y[N], ecnt, nxt[N], adj[N], go[N],
vis[N], times, my[N];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}
int orzcyx(int i, int j, double R) {
    int dis = (X[i] - x[j]) * (X[i] - x[j]) + (Y[i] - y[j]) * (Y[i] - y[j]);
    R = R * R; if (fabs(dis - R) <= eps) return 0;
    if (dis < R) return 1; return 2;
}
bool dfs(int u) {
    for (int e = adj[u], v; e; e = nxt[e])
        if (vis[v = go[e]] < times) {
            vis[v] = times;
            if (!my[v] || dfs(my[v])) return my[v] = u, 1;
        }
    return 0;
}
int maxCyx() {
    int i; for (i = 1; i <= n; i++) my[i] = vis[i] = 0; times = 0;
    int ans = 0; for (i = 1; i <= n; i++) {
        times++; if (dfs(i)) ans++;
    }
    return ans;
}
int maxPyz(double R) {
    int i, j; ecnt = 0; for (i = 1; i <= n; i++) adj[i] = 0;
    for (i = 1; i <= n; i++) for (j = 1; j <= n; j++)
        if (orzcyx(i, j, R) != 2) add_edge(i, j); return maxCyx();
}
int maxLpf(double R) {
    int i, j; ecnt = 0; for (i = 1; i <= n; i++) adj[i] = 0;
    for (i = 1; i <= n; i++) for (j = 1; j <= n; j++)
        if (orzcyx(i, j, R) != 1) add_edge(i, j); return n - maxCyx();
}
int main() {
    int i; n = read(); K = read();
    for (i = 1; i <= n; i++) X[i] = read(), Y[i] = read();
    for (i = 1; i <= n; i++) x[i] = read(), y[i] = read();
    double l = 0, r = 1e5; while (r - l > 1e-7) {
        double mid = (l + r) / 2;
        if (maxPyz(mid) < K) l = mid;
        else r = mid;
    }
    printf("%.2lf ", (l + r) / 2); if (n == K) return puts("+INF"), 0;
    l = 0; r = 1e5; while (r - l > 1e-7) {
        double mid = (l + r) / 2;
        if (maxLpf(mid) > K) r = mid;
        else l = mid;
    }
    printf("%.2lf\n", (l + r) / 2);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值