[题解]NOIP2017 Day2 Solution - by xyz32768

感觉Day2比2013还恶心……后来看了清华爷zzq的讲解,才搞出T2。

T1 cheese

模拟题。把可以互相到达的球连边后,用bfs或并查集判断是否连通。
注意细(keng)节(dian):在极端情况下,两点之间的距离会达到 1.21019 (爆long long),所以要用unsigned long long或double。
代码:

#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 = 1005; const double eps = 1e-8;
int n, h, r, X[N], Y[N], Z[N], fa[N]; double DIS;
inline int cx(const int &x) {
    if (fa[x] != x) fa[x] = cx(fa[x]);
    return fa[x];
}
inline void zm(const int &x, const int &y) {
    int ix = cx(x), iy = cx(y);
    if (ix != iy) fa[iy] = ix;
}
inline bool check(const int &i, const int &j) {
    if (X[i] - X[j] > (r << 1) || Y[i] - Y[j] > (r << 1) ||
    Z[i] - Z[j] > (r << 1)) return 0;
    double dis = 1.0 * (X[i] - X[j]) * (X[i] - X[j]) + 1.0 *
    (Y[i] - Y[j]) * (Y[i] - Y[j]) + 1.0 * (Z[i] - Z[j]) * (Z[i] - Z[j]);
    return abs(DIS - dis) <= eps || dis < DIS;
}
void work() {
    int i, j; n = read(); h = read(); r = read(); DIS = 4.0 * r * r;
    for (i = 1; i <= n + 2; i++) fa[i] = i;
    for (i = 1; i <= n; i++) X[i] = read(), Y[i] = read(), Z[i] = read();
    for (i = 1; i < n; i++) for (j = i + 1; j <= n; j++)
        if (check(i, j)) zm(i, j);
    for (i = 1; i <= n; i++) {
        if (Z[i] <= r) zm(n + 1, i);
        if (Z[i] >= h - r) zm(i, n + 2);
    }
    puts(cx(n + 1) == cx(n + 2) ? "Yes" : "No");
}
int main() {
    //freopen("cheese.in", "r", stdin);
    //freopen("cheese.out", "w", stdout);
    int T = read();
    while (T--) work();
    return 0;
}

T2 treasure

看到 12 的数据范围,容易想到状压DP,可以建立如下模型:
f[u][dep][S] 表示 u 的深度为dep u 的子树内包含的点集为S u 的子树的最小代价。
容易推出转移:
f[u][dep][S]=minSSuS,vS(f[v][dep+1][S]+f[u][dep][SS]+val<u,v>dep)
其中 val<u,v> <u,v> <script type="math/tex" id="MathJax-Element-11"> </script>的边权。枚举子集,实现即为:

for (i = (S - 1) & S; i; i = (i - 1) & S);

这时候复杂度为 O(3nn3) ,考虑优化,可以发现,枚举 v 特别浪费时间。所以容易想到,把f[v][dep+1][S]+val<u,v>dep看成一个整体考虑,也就是,令:
g[u][dep][S]=min(f[v][dep][S]+val<u,v>(dep1))
这时候,转移方程优化为:
f[u][dep][S]=minSS,uS(f[u][dep[SS]+g[u][dep+1][S])
复杂度 O(3nn2)
代码:

#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 = 16, C = (1 << 12) + 5, INF = 0x3f3f3f3f;
int n, m, G[N][N], f[N][N][C], g[N][N][C];
void dfs(int u, int dep, int S) {
    if (f[u][dep][S] != -1) return;
    if (S == (1 << u - 1)) return (void) (f[u][dep][S] = g[u][dep][S] = 0);
    int i, j; f[u][dep][S] = g[u][dep][S] = INF;
    for (j = (S - 1) & S; j; j = (j - 1) & S) {
        if ((j >> u - 1) & 1) continue; dfs(u, dep, S - j); dfs(u, dep + 1, j);
        f[u][dep][S] = min(f[u][dep][S], f[u][dep][S - j] + g[u][dep + 1][j]);
    }
    for (i = 1; i <= n; i++) {
        if (!((S >> i - 1) & 1) || G[u][i] == INF) continue;
        dfs(i, dep, S);
        g[u][dep][S] = min(g[u][dep][S], f[i][dep][S] + G[u][i] * (dep - 1));
    }
}
int main() {
    //freopen("treasure.in", "r", stdin);
    //freopen("treasure.out", "w", stdout);
    memset(G, INF, sizeof(G)); memset(f, -1, sizeof(f));
    int i, j, k, x, y, z, ans = INF; n = read(); m = read();
    for (i = 1; i <= m; i++) {
        x = read(); y = read(); z = read();
        G[x][y] = min(G[x][y], z);
        G[y][x] = min(G[y][x], z);
    }
    for (i = 1; i <= n; i++) ans =
        min(ans, (dfs(i, 1, (1 << n) - 1), f[i][1][(1 << n) - 1]));
    cout << ans << endl;
    return 0;
}

T3 phalanx

好像是NOIP有史以来第一道数据结构题。
蒟蒻用的是Splay的做法,即用 n 棵Splay维护每一行的前m1个元素,再用一棵维护第 m 列的元素,这样就是Splay的区间操作了。
考虑到空间问题,对于前n棵Splay,可以不建出所有的点,而是对于没有被改过(编号连续)的位置,在对应的点上维护一个区间,操作时分裂区间即可。(相似题目:BZOJ 3595)
据说标算是树状数组。
代码:

#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;
}
typedef long long ll;
const int N = 3e6 + 5;
int n, m, q, T, fa[N], lc[N], rc[N], rt[N]; ll sze[N], L[N], R[N];
int which(int x) {return rc[fa[x]] == x;}
void upt(int x) {
    sze[x] = R[x] - L[x] + 1;
    if (lc[x]) sze[x] += sze[lc[x]];
    if (rc[x]) sze[x] += sze[rc[x]];
}
void rotate(int x) {
    int y = fa[x], z = fa[y], b = lc[y] == x ? rc[x] : lc[x];
    if (z) (lc[z] == y ? lc[z] : rc[z]) = x;
    fa[x] = z; fa[y] = x; if (b) fa[b] = y;
    if (lc[y] == x) rc[x] = y, lc[y] = b;
    else lc[x] = y, rc[y] = b; upt(y); upt(x);
}
void splay(int x, int tar, int r) {
    while (fa[x] != tar) {
        if (fa[fa[x]] != tar) {
            if (which(x) == which(fa[x])) rotate(fa[x]);
            else rotate(x);
        }
        rotate(x);
    }
    if (!tar) rt[r] = x;
}
void BuildRow(int row) {
    int i, x = ++T; L[rt[row] = x] = 1ll * (row - 1) * m + 1;
    R[x] = 1ll * row * m - 1; fa[lc[x] = ++T] = x; fa[rc[x] = ++T] = x;
    sze[lc[x]] = sze[rc[x]] = 1; L[lc[x]] = R[lc[x]] = 1ll * (row - 1) * m;
    L[rc[x]] = R[rc[x]] = 1ll * row * m; upt(x);
}
int BuildLast(int l, int r) {
    int mid = l + r >> 1, x = ++T;
    if (l == 0 && r == n + 1) rt[n + 1] = x;
    if (l < mid) fa[lc[x] = BuildLast(l, mid - 1)] = x;
    if (mid < r) fa[rc[x] = BuildLast(mid + 1, r)] = x;
    L[x] = R[x] = 1ll * mid * m; return upt(x), x;
}
void build() {
    int i; for (i = 1; i <= n; i++) BuildRow(i);
    BuildLast(0, n + 1);
}
void DivLeft(int x, ll mid) {
    int y = lc[x], z = ++T;
    fa[lc[x] = z] = x; if (y) fa[lc[z] = y] = z;
    L[z] = L[x]; L[x] = mid + 1; R[z] = mid;
    upt(z); upt(x);
}
void DivRight(int x, ll mid) {
    int y = rc[x], z = ++T;
    fa[rc[x] = z] = x; if (y) fa[rc[z] = y] = z;
    R[z] = R[x]; R[x] = mid - 1; L[z] = mid;
    upt(z); upt(x);
}
void Divide(int x, ll mid) {
    ll tl = L[x], tr = R[x];
    if (tl < mid) DivLeft(x, mid - 1);
    if (mid < tr) DivRight(x, mid + 1);
}
void Find(int wh, ll rk, int tar) {
    int x = rt[wh]; while (x) {
        ll tmp = lc[x] ? sze[lc[x]] : 0;
        if (rk > tmp && rk <= tmp + (R[x] - L[x] + 1))
            return splay(x, tar, wh), Divide(x, L[x] + rk - tmp - 1);
        else if (rk <= tmp) x = lc[x];
        else rk -= tmp + (R[x] - L[x] + 1), x = rc[x];
    }
}
ll query(int x, int y) {
    ll res; Find(x, y, 0); Find(x, y + 2, rt[x]);
    int u = rt[x], v = rc[u], w = lc[v]; res = L[w];
    fa[w] = lc[v] = 0; upt(v); upt(u); Find(n + 1, x, 0);
    Find(n + 1, x + 2, rt[n + 1]); int r = rt[n + 1], s = rc[r], t = lc[s];
    fa[t] = lc[s] = 0; upt(s); upt(r);
    Find(x, m - 1, 0); Find(x, m, rt[x]);
    fa[lc[rc[rt[x]]] = t] = rc[rt[x]]; upt(rc[rt[x]]); upt(rt[x]);
    Find(n + 1, n, 0); Find(n + 1, n + 1, rt[n + 1]);
    fa[lc[rc[rt[n + 1]]] = w] = rc[rt[n + 1]];
    return upt(rc[rt[n + 1]]), upt(rt[n + 1]), res;
}
ll ask(int x) {
    ll res; Find(n + 1, x, 0); Find(n + 1, x + 2, rt[n + 1]);
    int u = rt[n + 1], v = rc[u], w = lc[v]; res = L[w];
    fa[w] = lc[v] = 0; upt(v); upt(u);
    Find(n + 1, n, 0); Find(n + 1, n + 1, rt[n + 1]);
    fa[lc[rc[rt[n + 1]]] = w] = rc[rt[n + 1]];
    return upt(rc[rt[n + 1]]), upt(rt[n + 1]), res;
}
int main() {
    //freopen("phalanx.in", "r", stdin);
    //freopen("phalanx.out", "w", stdout);
    int x, y; n = read(); m = read(); q = read(); build();
    while (q--) {
        x = read(); y = read();
        printf("%lld\n", y == m ? ask(x) : query(x, y));
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值