SCOI2016 Day2 简要题解

「SCOI2016」妖怪

题意

\(n\) 只妖怪,每只妖怪有攻击力 \(\text{atk}\) 和防御力 \(\text{dnf}\) ,在环境 \((a, b)\) 下,它可以把攻击力和防御力以 \(a : b\) 的代价呼唤,它的最强战斗力为互换后的攻击力与防御力的最大值。

现要给他们一种环境,使得它们的最强战斗力最小。答案保留四位小数。

\(1 \le n \le 10^6, 0 < \text{atk}, \text{dnf} \le 10^6\)

题解

首先,一个很显然的性质,如果把一只妖怪的两个属性看做二维平面上的一个点,那么在某一环境下妖怪的战斗力,也就是过这个点的某一条直线与 \(x\) 轴和 \(y\) 轴交点坐标之和。

于是题目所求就可以转化为,给定二位平面内的 \(n\) 个点,确定一个直线的斜率,使得分别过这 \(n\) 个点的 \(n\) 条直线的横纵坐标之和最大的直线尽可能小。

我们猜测一下,它其实关于斜率的函数图像是凸的,那么就是可以三分的,我们直接三分最值就好啦QAQ 复杂度是 \(O(n \log \epsilon^{-1})\) 的,特别好写。

为啥呢?

对于每一项展开得到 \(\text{atk}+\frac{\text{dnf}}{b}a + \text{dnf} + \frac{\text{atk}}{a} b\)

\(T=\frac{a}{b}\) 那么原式 \(=\text{atk}+T\text{dnf} + \text{dnf} + \frac{\text{atk}}{T}\) 这就是那个单峰的对勾函数, 把单峰函数复合为求最大值,发现也是个单峰函数,其实就是个凸的。

前面直接三分常数还是挺大的,我们有更好的做法。

于是我们可以先求出凸包,那么对答案造成贡献的点一定在凸包上。前面说了这个是对勾函数。于是我们分类讨论出函数的最小值即可。时间复杂度 \(O(n \log n)\)

总结

遇到计算几何最优化的题。首先猜测凸性,然后三分。实在不行,再猜测可用点在凸包上,然后就方便讨论。

代码

三分

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2015.in", "r", stdin);
    freopen ("2015.out", "w", stdout);
#endif
}

const double eps = 1e-13;

const int N = 1e6 + 1e3;

int n, atk[N], def[N];

inline double f(double k) {
    double ans = .0;
    For (i, 1, n)
        chkmax(ans, atk[i] + def[i] - (k * atk[i] + def[i] / k));
    return ans;
}

int main () {

    File();

    n = read();
    For (i, 1, n)
        atk[i] = read(), def[i] = read();

    double l = - 5, r = 0;
    while (r - l > eps) {
        double mid1 = (l + r) / 2.0,
               mid2 = (mid1 + r) / 2.0;
        if (f(mid1) < f(mid2)) r = mid2; else l = mid1;
    }
    printf ("%.4lf\n", f(l));

    return 0;

}

凸壳

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

typedef long long ll;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2015.in", "r", stdin);
    freopen ("2015.out", "w", stdout);
#endif
}

const double eps = 1e-10, inf = 1e10;

const int N = 1e6 + 1e3;

struct Point {
    int x, y;
    inline double Min() {
        return x + y + 2 * sqrt(1ll * x * y);
    }
    inline double f(double k) {
        chkmin(k, -eps);
        return x + y - (x * k + y / k);
    }
};

inline Point operator - (const Point &lhs, const Point &rhs) {
    return (Point) {lhs.x - rhs.x, lhs.y - rhs.y};
}

double Slope (const Point &lhs, const Point &rhs) {
    if (lhs.x == rhs.x) return inf;
    return 1.0 * (rhs.y - lhs.y) / (rhs.x - lhs.x);
}

inline ll Cross(Point a, Point b) { return 1ll * a.x * b.y - 1ll * a.y * b.x; }

struct Cmp {
    inline bool operator () (const Point &lhs, const Point &rhs) const {
        return lhs.x != rhs.x ? lhs.x < rhs.x : lhs.y < rhs.y;
    }
};

Point P[N], stk[N]; int top;

inline bool Check(Point a, Point b, Point c) { return Cross(c - a, b - a) <= 0; }

int main () {

    File();

    int n = read();
    For (i, 1, n)
        P[i].x = read(), P[i].y = read();
    sort(P + 1, P + n + 1, Cmp());

    stk[top = 1] = P[1];
    stk[top = 2] = P[2];

    For (i, 3, n) {
        while (top > 1 && Check(stk[top - 1], stk[top], P[i])) -- top; 
        stk[++ top] = P[i];
    }

    double ans = inf;
    For (i, 1, top) {
        double pre = i > 1 ? Slope(stk[i - 1], stk[i]) : eps, suf = i < top ? Slope(stk[i], stk[i + 1]) : - inf;
        chkmin(pre, .0); chkmin(suf, .0);
        double res = min(stk[i].f(pre), stk[i].f(suf)), Best = - sqrt(1.0 * stk[i].y / stk[i].x);
        if (suf < Best && Best < pre) chkmin(res, stk[i].Min()); chkmin(ans, res);
    }
    printf ("%.4lf\n", ans);

    return 0;

}

「SCOI2016」美味

题意

有一个长度为 \(n\) 的序列 \(\{a_i\}\)

\(q\) 次询问,每次四个整数 \(b, x, l, r\) ,可以把 \(b\) 变成 \(b \text{ xor } (a_i + x)\) (只能变一次),其中 \(i \in [l, r]\) ,问 \(b\) 的最大值。

$ 1 \leq n \leq 2 \times 10 ^ 5, 0 \leq a_i, b_i, x_i < 10 ^ 5, 1 \leq m \leq 10 ^ 5 $

题解

如果 \(x = 0\) 那么就是区间 \(01-Trie\)std :: set 啦。

但是有 \(x\) 怎么做呢?其实还是可以类似于 \(01-Trie\)

我们按位考虑,我们从高到低考虑,看 \(b\) 的每一位是否能异或出 \(1\)

不难发现,当前的询问会对到一道连续区间 \([l, r]\)

这下问题就转化为,区间查询权值在一段区间内的数是否存在,这个直接用主席树维护即可。

复杂度是 \(O(n \log n \log a_i)\) 的。

总结

可以考虑用数据结构去模拟另外一个数据结构。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2016.in", "r", stdin);
    freopen ("2016.out", "w", stdout);
#endif
}

const int N = 2e5 + 1e3;

template<int Maxn>
struct ChairMan_Tree {

    int tot[Maxn], ls[Maxn], rs[Maxn], Size = 0;

    inline void Update(int &o, int pre, int l, int r, int up) {
        tot[o = ++ Size] = tot[pre] + 1;
        ls[o] = ls[pre]; rs[o] = rs[pre]; if (l == r) return ;
        int mid = (l + r) >> 1;
        if (up <= mid) Update(ls[o], ls[pre], l, mid, up);
        else Update(rs[o], rs[pre], mid + 1, r, up);
    }

    bool Query(int x, int y, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr) return tot[y] - tot[x];
        int mid = (l + r) >> 1;
        if (ql <= mid && Query(ls[x], ls[y], l, mid, ql, qr)) return true;
        if (qr > mid && Query(rs[x], rs[y], mid + 1, r, ql, qr)) return true;
        return false;
    }

};

ChairMan_Tree<int(4 * N * log2(N))> T;

int n, m, rt[N], a[N];

int main () {

    File();

    n = read(); m = read();

    For (i, 1, n) a[i] = read();
    int lim = *max_element(a + 1, a + n + 1);

    For (i, 1, n)
        T.Update(rt[i], rt[i - 1], 0, lim, a[i]);

    while (m --) {
        int b = read(), x = read(), l = read() - 1, r = read();
        int len = ceil(log2(max(b, lim + x))), ans = 0;

        Fordown (i, len, 0) {
            int need = ((b >> i) & 1) ^ 1, L, R;
            L = ans + (need << i) - x;
            R = ans + (1 << i + need) - 1 - x;
            ans |= (!need ^ (L <= lim && R >= 0 && T.Query(rt[l], rt[r], 0, lim, L, R))) << i;
        }
        printf ("%d\n", ans ^ b);

    }

    return 0;

}

「SCOI2016」围棋

题意

\(q\) 次询问,每次给你一个 \(2 \times c\) 的模板,问有多少个 \(n \times m\) 只包含 W, B, X 的棋盘至少包含一个模板。

$ n \leq 100, m \leq 12, c \leq 6, q \leq 5 $

题解

\(m\) 很小,显然是考虑轮廓线 \(dp\)

一个很直观的想法是记下连续两行上的状态,直接做,复杂度是 \(O(qnm3^{2m})\) 的,显然过不去啦。

其实很多状态是没有意义的,我们只需要记上面一圈轮廓线的对应的结尾是否能匹配上 \(S_1\) 即可。我们模板上面一排为 \(S_1\) 下面一排为 \(S_2\)

然后这样状态显然还不够,因为你无法去转移到后面的状态,对于匹配问题我们只需要多记下最后一个位置匹配到最远的字符就好啦,然后转移就用 \(\text{KMP}\) 去转。

具体来说,令 f[i][j][S][x][y] 为到 \(i\)\(j\) 列,轮廓线能匹配上的状态为 \(S\)\((i, j)\) 结尾的串匹配到 \(S_1\) 的第 \(x\) 位,\(S_2\) 的第 \(y\) 位。

其实状态没有那么满,用个哈希表转移一下,跑的挺快的。复杂度是 \(O(nm2^{m}c^2)\)

总结

dp 一定要记得压状态鸭!!

代码

至于实现,可以记不合法的方案数,最后用 \(3^{nm}\) 减掉即可。其实记合法的也一样。。。

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2017.in", "r", stdin);
    freopen ("2017.out", "w", stdout);
#endif
}

const int N = 13, Mod = 1e9 + 7;

int n, m, c, q;

inline int encode(int S, int x, int y) {
    return S * (c + 1) * (c + 1) + x * (c + 1) + y;
}

inline void decode(int code, int &S, int &x, int &y) {
    S = code / (c + 1) / (c + 1);
    x = code / (c + 1) % (c + 1);
    y = code % (c + 1);
}

unordered_map<int, int> M[2];

char S1[N], S2[N]; int fail1[N], fail2[N];

inline void get_fail(char *S, int *fail) {
    fail[1] = 0;
    For (i, 2, strlen(S + 1)) {
        int j = fail[i - 1];
        while (j && S[j + 1] != S[i]) j = fail[j];
        fail[i] = j + (S[i] == S[j + 1]);
    }
}

inline int kmp(char *S, int *fail, int pos, char T) {
    while (pos && S[pos + 1] != T) pos = fail[pos];
    return pos + (S[pos + 1] == T);
}

inline void Add(int &a, int b) {
    if ((a += b) >= Mod) a -= Mod;
}

int cur;
inline void Insert(int S, int x, int y, int val) {
    Add(M[cur][encode(S, x, y)], val);
}

char ch[3] = {'W', 'B', 'X'};

int main() {

    File();

    n = read(); m = read(); c = read(); q = read();

    while (q --) {

        scanf ("%s", S1 + 1); get_fail(S1, fail1);
        scanf ("%s", S2 + 1); get_fail(S2, fail2);

        M[0].clear(); M[1].clear();

        cur = 1; Insert(0, 0, 0, 1); cur ^= 1;

        int ans = 0;
        For (i, 1, n) For (j, 1, m) {
            for (auto it : M[cur ^ 1]) {
                int S, x, y, val = it.second;
                decode(it.first, S, x, y);
                Rep (k, 3) {
                    int nx = kmp(S1, fail1, x, ch[k]);
                    int ny = kmp(S2, fail2, y, ch[k]);
                    if ((S & 1) && ny == c) continue;
                    int nS = (S >> 1) | ((nx == c) << (m - 1));
                    if (j == m) {
                        nx = ny = 0;
                        if (i == n) Add(ans, val);
                    }
                    Insert(nS, nx, ny, val);
                }
            }
            M[cur ^= 1].clear();
        }

        int res = 1;
        For (i, 1, n * m) res = res * 3ll % Mod;
        printf ("%d\n", (res - ans + Mod) % Mod);

    }

    return 0;

}

转载于:https://www.cnblogs.com/zjp-shadow/p/10357479.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值