2018 杭电多校 - 乱搞题目

2018 杭电多校 - 乱搞题目

果然乱搞能力才是王道!

1. HDU - 6299 - Balanced Sequence(贪心)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6299

题目来源:2018 HDU 多校第一场

1.1 题意

给你 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 个括号串,每个括号串长度为 ∣ s i ∣ |s_i| si

你可以任意改变这些括号串的顺序,之后拼成一个新串。

问最大可能得到的新串中匹配括号子序列的长度。

共有 T T T 组读入,题目保证所有的括号串长度之和不超过 5 ⋅ 1 0 6 5 \cdot 10^6 5106

1.2 解题过程

首先,显而易见的是,每一个小的括号串中已经匹配的括号子序列可以直接加到答案中。

因此,我们遍历所有的括号串,将每个串用栈将已经匹配的括号子序列去除,并记录去除的总长。

之后,每个串只剩下四种情况:

  1. (((((( 形式(只有左括号);
  2. )))(((((((左括号比右括号多);
  3. ))))))((((右括号比左括号多);
  4. ))))))(只有右括号)。

将处理之后的所有括号串按照上面的类型顺序为第一关键字排序。

如果类型相同,对于第 1 类和第 4 类,无需处理,其他类型处理方式如下”:

  1. 对于第 2 类,右括号少的排在前面;
  2. 对于第 3 类,与第 2 类对称,左括号少的排在后面。

我们的原则是左边尽量不要有残留的无法匹配的右括号,右边尽量不要有残留的无法匹配的左括号。这样排序,可以保证左边残留的右括号和右边残留的左括号尽量少。

排序之后,将所有的括号串拼接起来,再次用栈进行括号匹配,并计算答案。

最后不要忘记加上最初算出的那部分答案。

时间复杂度: O ( n log ⁡ n + ∑ ∣ s i ∣ ) O(n \log n + \sum |s_i|) O(nlogn+si)

1.3 代码

int n, delta;
string input[maxn];
struct node
{
    string str;
    int type, cnta, cntb;
} data[maxn];
bool operator < (node a, node b)
{
    if(a.type != b.type) return a.type < b.type;
    if(a.type == 1) return a.cntb < b.cntb;
    else if(a.type == 2) return a.cnta > b.cnta;
}
void init()
{
    delta = 0;
    for(int i = 0; i <= n; i++)
    {
        data[i].cnta = 0;
        data[i].cntb = 0;
        data[i].str = "";
    }
}
int main()
{
    ios::sync_with_stdio(false);
    int t;
    cin >> t;
    while(t--)
    {
        cin >> n;
        init();
        for(int i = 1; i <= n; i++) cin >> input[i];
        for(int i = 1; i <= n; i++)
        {
            stack<char> st;
            bool mark[2];
            memset(mark, false, sizeof(mark));
            int length = input[i].length();
            for(int j = 0; j < length; j++)
            {
                if(st.empty())  st.push(input[i][j]);
                else if(st.top() == '(' && input[i][j] == ')')
                {
                    st.pop();
                    delta += 2;
                }
                else st.push(input[i][j]);
            }
            stack<char> st2;
            while(!st.empty())
            {
                st2.push(st.top());
                st.pop();
            }
            while(!st2.empty())
            {
                data[i].str += st2.top();
                st2.pop();
            }
            length = data[i].str.length();
            for(int j = 0; j < length; j++)
            {
                if(data[i].str[j] == '(')
                {
                    mark[0] = true;
                    data[i].cnta++;
                }
                else
                {
                    mark[1] = true;
                    data[i].cntb++;
                }
            }
            if(mark[0] && mark[1])
            {
                if(data[i].cnta >= data[i].cntb)    data[i].type = 1;
                else data[i].type = 2;
            }
            else if(mark[0])    data[i].type = 0;
            else if(mark[1])    data[i].type = 3;
        }
        sort(data + 1, data + 1 + n);
        string str;
        for(int i = 1; i <= n; i++) str += data[i].str;
        stack<char> st;
        int length = str.length();
        for(int i = 0; i < length; i++)
        {
            if(st.empty())  st.push(str[i]);
            else
            {
                if(st.top() == '(' && str[i] == ')')
                {
                    delta += 2;
                    st.pop();
                }
                else st.push(str[i]);
            }
        }
        cout << delta << endl;
    }
    return 0;
}

2. HDU - 6300 - Triangle Partition(贪心)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6300

题目来源:2018 HDU 多校第一场

2.1 题意

在一个二维坐标平面上,给你 3 n ( 1 ≤ n ≤ 1000 ) 3n(1 \le n \le 1000) 3n(1n1000) 个点,保证不存在三点共线的情况。现在要你构造 n n n 个不相交的三角形。

2.2 解题过程

不要把这个题想的太复杂!

只需将所有点按照 x x x 值为第一关键字, y y y 值为第二关键字排序,之后连续三个连续三个地构造就可以了。

时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)

3. HDU - 6301 -Distinct Values(贪心)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6301

题目来源:2018 HDU 多校第一场

3.1 题意

给定 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) m ( 1 ≤ m ≤ 1 0 5 ) m(1 \le m \le 10^5) m(1m105),现在需要构造一个长度为 n n n 的正数序列 a a a,并满足 m m m 个条件。

i i i 个条件用 l i , r i l_i, r_i li,ri 表示,表示 a [ l . . r ] a[l..r] a[l..r] 中间的数字不可以重复。

3.2 解题过程

对于每一个 l l l,计算出以 l l l 为左端点的区间中,最大的 r r r

基于此,计算出新的极大区间,然后我们只处理这些极大区间即可。

基本的思路就是对于当前的极大区间,每次填入数字为当前极大区间中未出现的最小数字。

具体实现可参考下面代码。

时间复杂度: O ( n ) O(n) O(n)

3.3 代码

struct node
{
    int l, r;
} interval[maxn];
int n;
int a[maxn], maxpos[maxn];
bool vis[maxn];
void init()
{
    for(int i = 0; i <= n; i++)
    {
        maxpos[i] = i;
        a[i] = 0;
        vis[i] = false;
    }
}
int main()
{
    int t, m, l, r;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &m);
        init();
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d", &l, &r);
            maxpos[l] = max(maxpos[l], r);
        }
        for(int i = 1; i <= n; i++)
        {
            interval[i] = (node){i, maxpos[i]};
        }
        l = 1, r = 1;
        int pos = 1;
        int num = 1;
        while(r <= n)
        {
            while(vis[num]) num++;
            vis[num] = true;
            a[r] = num;
            r++;
            if(r > interval[pos].r)
            {
                while(r > interval[pos].r && pos <= n)    pos++;
                num = 1;
                while(l < interval[pos].l)
                {
                    vis[a[l]] = false;
                    l++;
                }
            }
        }
        for(int i = 1; i <= n; i++)
        {
            if(i > 1)   printf(" ");
            printf("%d", a[i]);
        }
        printf("\n");
    }
    return 0;
}

4. HDU - 6315 - Naive Operations(线段树)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6315

题目来源:2018 HDU 多校第二场

4.1 题意

给你两个长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 的序列 a a a b b b,其中 b b b 为一个 [ 1 , n ] [1,n] [1,n] 的排列,并保持不变, a a a 序列的值最初为全 0 0 0

现在有 q ( 1 ≤ q ≤ 1 0 5 ) q(1 \le q \le 10^5) q(1q105) 次查询,查询分为两种类型:

  1. add   l   r \texttt{add l r} add l r:对 a [ l . . r ] a[l..r] a[l..r] 区间加 1 1 1
  2. query   l   r \texttt{query l r} query l r:查询 ∑ i = l r ⌊ a i b i ⌋ \sum_{i=l}^r \lfloor \frac{a_i}{b_i} \rfloor i=lrbiai

题目共有不超过 5 5 5 组输入。

4.2 解题过程

我们可以把对 a a a 区间加的操作转化为对 b b b 进行区间减的操作(当某个位置减到 0 0 0 就将当前位置的答案加 1 1 1,并将当前位置的 b i b_i bi 恢复到最初的 b i b_i bi 值)。

这样做的话,查询进行简单的区间和查询即可。

如果我们细心观察,会发现 b i b_i bi 变成 0 0 0 的次数最多只有
∑ i = 1 n ⌊ q i ⌋ = O ( q log ⁡ n ) \sum_{i=1}^n \lfloor \frac{q}{i} \rfloor = O(q \log n) i=1niq=O(qlogn)
次,因此加上线段树本身的时间复杂度,处理 b b b 的总时间复杂度为 O ( q log ⁡ 2 n ) O(q \log^2 n) O(qlog2n) 。这个复杂度是可以接受的。

因此线段树需要维护三个值:

  1. v a l u e value value 为当前节点存储的答案;
  2. m i n v a l u e minvalue minvalue m a x v a l u e maxvalue maxvalue 分别是当前节点 b i b_i bi 值的最小值和最大值。

对于第 2 种查询,直接查询当前区间的 v a l u e value value 之和即可。

对于第 1 种查询,先将区间内 m a x v a l u e maxvalue maxvalue m i n v a l u e minvalue minvalue 都减 1 1 1,之后进行最上面所述的操作。

4.3 代码

ll b[maxn];
struct node
{
    ll maxvalue, minvalue, lazy;
    ll value;
} tree[4 * maxn];
void pushup(int id)
{
    tree[id].maxvalue = max(tree[id << 1].maxvalue, tree[id << 1 | 1].maxvalue);
    tree[id].minvalue = min(tree[id << 1].minvalue, tree[id << 1 | 1].minvalue);
    tree[id].value = tree[id << 1].value + tree[id << 1 | 1].value;
}
void pushdown(int id)
{
    if(tree[id].lazy)
    {
        tree[id << 1].maxvalue += tree[id].lazy;
        tree[id << 1 | 1].maxvalue += tree[id].lazy;
        tree[id << 1].minvalue += tree[id].lazy;
        tree[id << 1 | 1].minvalue += tree[id].lazy;
        tree[id << 1].lazy += tree[id].lazy;
        tree[id << 1 | 1].lazy += tree[id].lazy;
        pushup(id);
        tree[id].lazy = 0;
    }
}
void build(int id, int l, int r)
{
    tree[id].lazy = 0;
    if(l == r)
    {
        tree[id].maxvalue = tree[id].minvalue = b[l];
        tree[id].value = 0;
        return;
    }
    int mid = (l + r) >> 1;
    build(id << 1, l, mid);
    build(id << 1 | 1, mid + 1, r);
    pushup(id);
    return;
}
void update(int id, int ns, int nt, int qs, int qt, ll value)
{
    if(nt < qs || ns > qt)  return;
    if(ns >= qs && nt <= qt)
    {
        tree[id].maxvalue += value;
        tree[id].minvalue += value;
        tree[id].lazy += value;
        return;
    }
    pushdown(id);
    int mid = (ns + nt) >> 1;
    update(id << 1, ns, mid, qs, qt, value);
    update(id << 1 | 1, mid + 1, nt, qs, qt, value);
    pushup(id);
    return;
}
ll query(int id, int ns, int nt, int qs, int qt)
{
    if(nt < qs || ns > qt)      return 0;
    if(ns >= qs && nt <= qt)    return tree[id].value;
    pushdown(id);
    int mid = (ns + nt) >> 1;
    return query(id << 1, ns, mid, qs, qt) + query(id << 1 | 1, mid + 1, nt, qs, qt);
}
void operate(int id, int ns, int nt, int qs, int qt)
{
    if(nt < qs || ns > qt)  return;
    if(ns == nt)
    {
        tree[id].maxvalue = tree[id].minvalue = b[ns];
        tree[id].value++;
        return;
    }
    pushdown(id);
    int mid = (ns + nt) >> 1;
    if(tree[id << 1].minvalue == 0) operate(id << 1, ns, mid, qs, qt);
    if(tree[id << 1 | 1].minvalue == 0) operate(id << 1 | 1, mid + 1, nt, qs, qt);
    pushup(id);
    return;
}
int main()
{
    int n, q, l, r;
    char op[15];
    while(~scanf("%d%d", &n, &q))
    {
        for(int i = 1; i <= n; i++) scanf("%lld", &b[i]);
        build(1, 1, n);
        while(q--)
        {
            scanf("%s", op);
            scanf("%d%d", &l, &r);
            if(op[0] == 'a')
            {
                update(1, 1, n, l, r, -1);
                operate(1, 1, n, l, r);
            }
            else
            {
                printf("%lld\n", query(1, 1, n, l, r));
            }
        }

    }
    return 0;
}

5. HDU - 6319 - Ascending Rating(单调栈)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6319

题目来源:2018 HDU 多校第三场 - A 题

5.1 题意

给你一个长度为 n n n 的序列 a ( 0 ≤ a i ≤ 1 0 9 ) a(0 \le a_i \le 10^9) a(0ai109),对于每个 l ( 1 ≤ l ≤ n − m + 1 ) l(1 \le l \le n - m + 1) l(1lnm+1),求出区间 [ l , l + m − 1 ] [l, l + m - 1] [l,l+m1] 中的最大值 m a x r a t i n g maxrating maxrating 和最大值的变化次数 c o u n t count count

最终输出两个值 A A A B B B,定义为
KaTeX parse error: No such environment: eqnarray* at position 8: \begin{̲e̲q̲n̲a̲r̲r̲a̲y̲*̲}̲ A&=&\sum_{i=…

5.2 解题过程

做题的时候,尝试了正向去做,会发现维护滑动窗口的最大值和最大值变化次数很困难。

实际上,我们可以倒着考虑。先考虑最右边的窗口,然后不断向左滑动。

之后不断维护一个单调递减的栈(从 a a a 的右侧向左向栈中加元素),则每一个窗口的最大值就是当前栈底元素的值,最大值的变化次数就是当前栈的大小。

可以这样维护的原因是,右边的最大值一定存在于历史最大值序列中,同时单调栈的操作恰好把那些没有成为过最大值的值都去掉了。

时间复杂度: O ( n ) O(n) O(n)

5.3 代码

ll a[maxn];
bool mark[maxn];
struct node
{
    int id;
    ll value;
} st[maxn];
int main()
{
    int t, n, m, k;
    ll p, q, r, mod;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d%d%lld%lld%lld%lld", &n, &m, &k, &p, &q, &r, &mod);
        for(int i = 1; i <= n; i++) mark[i] = false;
        for(int i = 1; i <= k; i++) scanf("%lld", &a[i]);
        for(int i = k + 1; i <= n; i++)
        {
            a[i] = (p * a[i - 1] + q * 1LL * i + r) % mod;
        }
        int l = 1, r = 0;
        st[0] = (node){0, 0x3f3f3f3f3f3f3f3f};
        ll A = 0, B = 0;
        for(int i = n; i >= n - m + 1; i--)
        {
            while(a[i] >= st[r].value)
            {
                mark[st[r].id] = true;
                st[r] = (node){r, 0x3f3f3f3f3f3f3f3f};
                r--;
            }
            st[++r] = (node){i, a[i]};
        }
        A += (st[l].value ^ (1LL * (n - m + 1)));
        B += ((1LL * (r - l + 1)) ^ (1LL * (n - m + 1)));
        for(int i = n - m; i >= 1; i--)
        {
            int j = i + m;
            if(!mark[j])
            {
                st[l] = (node){l, 0x3f3f3f3f3f3f3f3f};
                l++;
            }
            while(a[i] >= st[r].value && r >= l)
            {
                mark[st[r].id] = true;
                st[r] = (node){r, 0x3f3f3f3f3f3f3f3f};
                r--;
            }
            st[++r] = (node){i, a[i]};
            A += (st[l].value ^ (1LL * i));
            B += ((1LL * (r - l + 1)) ^ (1LL * i));
        }
        printf("%lld %lld\n", A, B);
    }
    return 0;
}

6. HDU - 6324 - Grab The Tree(异或的性质)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6324

题目来源:2018 HDU 多校第三场 - F 题

6.1 题意

有一棵 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 个节点的树,每个点都有一个权值 w i ( 1 ≤ w i ≤ 1 0 9 ) w_i(1 \le w_i \le 10^9) wi(1wi109)

现在 quailtytangjz 两个人在树上玩一个游戏,quailty 先手。

先手可以选择一些不相邻的点,其得分 A A A 为这些点权值的异或和。

后手会将剩余的点拿走,其得分 B B B 为剩余点权值的异或和。

最终得分大的一方获胜,也可能存在平局。

如果两个人都以最优策略进行,问比赛结果。

6.2 解题过程

注意到 A  xor  B A \text{ xor } B A xor B 恰好为所有 w i w_i wi 的异或和。

如果这个总的异或和为 0 0 0 的话,表明无论如何都有 A = B A=B A=B,因此此时为平局。

否则,quailty 只需选择 1 1 1 的最高位最高的数字即可获胜。

时间复杂度: O ( n ) O(n) O(n)

7. HDU - 6396 - Swordsman(暴力)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6396

题目来源:2018 HDU 多校第七场

7.1 题意

你有 k ( 1 ≤ k ≤ 5 ) k(1 \le k \le 5) k(1k5) 种属性 v 1 , v 2 , ⋯   , v k v_1,v_2,\cdots,v_k v1,v2,,vk

现在有 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 个怪物,每个怪物 i i i 会有 k k k 个防御值 a i , k ( 0 ≤ a i , k ≤ 1 0 9 ) a_{i,k}(0 \le a_{i,k} \le 10^9) ai,k(0ai,k109) k k k 个经验值 b i , k ( 0 ≤ b i , k ≤ 1 0 9 ) b_{i,k}(0 \le b_{i,k} \le 10^9) bi,k(0bi,k109)

你可以打败第 i i i 个怪物的条件是对于任意的 j ∈ [ 1 , k ] j \in [1,k] j[1,k],都有 v j ≥ a i , j v_j \ge a_{i,j} vjai,j。打败第 i i i 个怪物之后,你的属性 v j v_j vj 将增加 b i , j b_{i,j} bi,j

输出最多可以打败的怪物数量以及每个属性最终可以达到的最大值。

题目保证不可能爆 long long

7.2 解题过程

建立 k k k 个优先队列,第 i i i 个优先队列存储怪物的第 i i i 个防御值,防御值小的在队首。

之后将所有怪物的第 1 1 1 个防御值全部丢进第 1 1 1 个队列中,之后从 1 1 1 k k k 处理每个队列。

处理队列时,如果当前防御值可以达到要求,就将当前怪物的下一个防御值丢到下一个优先队列中;如果在达到要求时,已经是最后一个队列了,就更新答案和经验值。

处理完最后一个队列之后,还要回到第一个队列重新开始处理,因为这时经验值已经发生变化,可能会有新的怪物被打败了。

这个过程进行到没有怪物可以打败为止。

另外本题需要快读,否则会因为读入而超时。

时间复杂度: O ( k ⋅ n log ⁡ n ) O(k \cdot n \log n) O(knlogn)

7.3 代码

发现这个代码是自己去年写的,看起来好稚嫩啊!

const int MAXN = 100005;
int v[8], a[MAXN][16], k;
struct node
{
    int value, id;
    bool operator<(const node &b) const
    {
        return value > b.value;
    }
};
struct ios
{
    inline char read()
    {
        static const int IN_LEN = 1 << 18 | 1;
        static char buf[IN_LEN], *s, *t;
        return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
    }

    template <typename _Tp>
    inline ios &operator>>(_Tp &x)
    {
        static char c11, boo;
        for (c11 = read(), boo = 0; !isdigit(c11); c11 = read())
        {
            if (c11 == -1)
                return *this;
            boo |= c11 == '-';
        }
        for (x = 0; isdigit(c11); c11 = read())
            x = x * 10 + (c11 ^ '0');
        boo && (x = -x);
        return *this;
    }
} io;
priority_queue<node> que[5], emp;
int main(void)
{
    int t, n, tot;
    io >> t;
    while (t--)
    {
        tot = 0;
        io >> n >> k;
        for (int i = 0; i < k; i++)
            io >> v[i];
        for (int i = 0; i < n; i++)
            for (int j = 0; j < 2 * k; j++)
                io >> a[i][j];
        for (int i = 0; i < k; i++)
            que[i] = emp;

        for (int i = 0; i < n; i++)
        {
            node p;
            p.value = a[i][0];
            p.id = i;
            que[0].push(p);
        }

        while (true)
        {
            bool isFail = true;
            for (int i = 0; i < k; i++)
            {
                while (!que[i].empty())
                {
                    node f = que[i].top();
                    if (v[i] >= f.value)
                    {
                        isFail = false;
                        if (i < k - 1)
                        {
                            f.value = a[f.id][i + 1];
                            que[i + 1].push(f);
                        }
                        else
                        {
                            tot++;
                            for (int j = 0; j < k; j++)
                                v[j] += a[f.id][j + k];
                        }
                        que[i].pop();
                    }
                    else
                    {
                        break;
                    }
                    
                }
            }
            if (isFail)
                break;
        }
        printf("%d\n", tot);
        for (int i = 0; i < k; i++)
        {
            if (i > 0)
                printf(" ");
            printf("%d", v[i]);
        }
        printf("\n");
    }
    return 0;
}

8. HDU - 6415 - Rikka with Nash Equilibrium(线性 dp)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6415

题目来源:2018 HDU 多校第九场

8.1 题意

给定 n , m ( 1 ≤ n , m ≤ 80 ) n,m(1 \le n,m \le 80) n,m(1n,m80),按照下面要求构造一个 n × m n \times m n×m 的矩阵:

  1. [ 1 , n m ] [1,nm] [1,nm] 中的每个整数都恰好出现一次;

  2. 最多存在一个 ( x , y ) (x,y) (x,y) 使得 A x , y A_{x,y} Ax,y 满足
    KaTeX parse error: No such environment: align* at position 9: \begin{̲a̲l̲i̲g̲n̲*̲}̲ \begin{cas…

问构造矩阵的方案数,对 K ( 1 ≤ K ≤ 1 0 9 ) K(1 \le K \le 10^9) K(1K109) 取模。

8.2 解题过程

为了处理方便,我们从 n m nm nm 1 1 1 倒序填数。

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示选了 i i i 个数,占领了 j j j k k k 列的方案数,因为新的数必须在之前的数被占领的范围内填,所以有转移
d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] ⋅ ( j ⋅ k − ( i − 1 ) ) + d p [ i − 1 ] [ j − 1 ] [ k ] ⋅ ( n − j + 1 ) ⋅ k + d p [ i − 1 ] [ j ] [ k − 1 ] ⋅ ( m − k + 1 ) ⋅ j \begin{aligned} dp[i][j][k] &= dp[i-1][j][k] \cdot (j \cdot k - (i - 1))\\ &+ dp[i-1][j-1][k] \cdot (n - j + 1) \cdot k\\ &+ dp[i-1][j][k-1] \cdot (m - k + 1) \cdot j\\ \end{aligned} dp[i][j][k]=dp[i1][j][k](jk(i1))+dp[i1][j1][k](nj+1)k+dp[i1][j][k1](mk+1)j
初始值为 d p [ 1 ] [ 1 ] [ 1 ] = n ⋅ m dp[1][1][1]=n \cdot m dp[1][1][1]=nm,其他为 0 0 0

时间复杂度: O ( n 2 m 2 ) O(n^2m^2) O(n2m2)

8.3 代码

int dp[85 * 85][85][85];
int main(void)
{
    int t, n, m, mod;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d%d", &n, &m, &mod);
        memset(dp, 0, sizeof(dp));
        dp[1][1][1] = n * m;
        for(int i = 2; i <= n * m; i++)
        {
            for(int j = 0; j <= n; j++)
            {
                for(int k = 0; k <= m; k++)
                {
                    if (dp[i - 1][j][k])
                    {
                        long long temp;
                        temp = ((long long)dp[i - 1][j][k] * (j * k - (i - 1))) % mod;
                        dp[i][j][k] += temp;
                        dp[i][j][k] %= mod;
                        temp = ((long long)dp[i - 1][j][k] * (n - j) * k) % mod;
                        dp[i][j + 1][k] += temp;
                        dp[i][j + 1][k] %= mod;
                        temp = ((long long)dp[i - 1][j][k] * (m - k) * j) % mod;
                        dp[i][j][k + 1] += temp;
                        dp[i][j][k + 1] %= mod;
                    }
                }
            }
        }
        printf("%d\n", dp[n * m][n][m]);
    }
    return 0;
}

9. HDU - 6418 - Rikka with Stone-Paper-Scissors(期望)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6418

题目来源:2018 HDU 多校第九场

9.1 题意

两个人玩石头剪刀布卡牌游戏,第一个人有 a a a 张剪刀, b b b 张石头, c c c 张布;第二个人有 a ′ a' a 张剪刀, b ′ b' b 张石头, c ′ c' c 张布。

每一回合,胜方得 1 1 1 分,负方扣 1 1 1 分,平局则两人得分均不变。

在第一个人随机(等概率)出牌,第二个人按照最优策略出牌的情况下,求第二个人期望得到的最高得分。

题目保证 a + b + c = a ′ + b ′ + c ′ a+b+c = a' + b' + c' a+b+c=a+b+c

提示:第二个人可以根据之前第一个人的出牌情况,来决定当前回合的出牌情况。

9.2 解题过程

总轮数为 a + b + c a+b+c a+b+c

我们考虑第二个人出三种不同类型的牌时的期望得分:

  1. 出剪刀 a ′ a' a 时,期望得分为 s c o r e a = 1 ⋅ c + ( − 1 ) ⋅ b + 0 ⋅ a a + b + c score_a=\frac{1 \cdot c + (-1) \cdot b + 0 \cdot a}{a+b+c} scorea=a+b+c1c+(1)b+0a
  2. 出石头 b ′ b' b 时,期望得分为 s c o r e b = 1 ⋅ a + ( − 1 ) ⋅ c + 0 ⋅ b a + b + c score_b=\frac{1 \cdot a + (-1) \cdot c + 0 \cdot b}{a+b+c} scoreb=a+b+c1a+(1)c+0b
  3. 出布 c ′ c' c 时,期望得分为 s c o r e c = 1 ⋅ b + ( − 1 ) ⋅ a + 0 ⋅ c a + b + c score_c=\frac{1 \cdot b + (-1) \cdot a + 0 \cdot c}{a+b+c} scorec=a+b+c1b+(1)a+0c

在期望的角度下,出牌的顺序不影响答案,最终答案为
a ′ ⋅ s c o r e a + b ′ ⋅ s c o r e b + c ′ ⋅ s c o r e c a' \cdot score_a + b' \cdot score_b + c' \cdot score_c ascorea+bscoreb+cscorec

10. HDU - 6425 - Rikka with Badminton(排列组合)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6425

题目来源:2018 HDU 多校第九场

10.1 题意

共有 n n n 个人,分为四类:

  1. a a a 个人既没有球也没有拍;
  2. b b b 个人只有拍,没有球;
  3. c c c 个人只有球,没有拍;
  4. d d d 个人有球和拍。

一场比赛顺利进行的条件是参与的人里面至少两个人有拍,一个人有球。

每个人都可以决定是否参与,即存在 2 n 2^n 2n 种可能。

问 $2^n - $ 可以顺利进行比赛的情况数。

数据范围: 0 ≤ a , b , c , d ≤ 1 0 7 , n = a + b + c + d ≥ 1 0 \le a,b,c,d \le 10^7, n=a+b+c+d \geq 1 0a,b,c,d107,n=a+b+c+d1

10.2 解题过程

我们直接计算非法情况。

显然第 1 类人对情况没有任何影响,因此最终的答案可以直接乘上 2 a 2^a 2a,我们暂时不考虑 a a a

第 4 类人只能一个人上场,否则一定能顺利进行,因此情况数为 d d d

第 2 类人单独上场的情况数为 2 b − 1 2^b-1 2b1

第 3 类人单独上场的情况数为 2 c − 1 2^c-1 2c1

上述情况中,没有考虑所有 2 − 4 2-4 24 类人均不上场的情况。

下面考虑组合上场的情况:

  1. 任意个 c c c 和一个 b b b 的情况数为 ( 2 c − 1 ) ⋅ b (2^c-1) \cdot b (2c1)b
  2. 任意个 c c c 和一个 d d d 的情况数为 ( 2 c − 1 ) ⋅ d (2^c-1) \cdot d (2c1)d

加上所有 2 − 4 2-4 24 类人均不上场的情况,总情况数为
2 a ( d + ( 2 b − 1 ) + ( 1 + d + b ) ( 2 c − 1 ) + 1 ) 2^a\left( d+(2^b-1)+(1+d+b)(2^c-1)+1 \right) 2a(d+(2b1)+(1+d+b)(2c1)+1)
预处理 2 2 2 的幂次之后, O ( 1 ) O(1) O(1) 计算即可。

11. HDU - 6425 - CSGO(暴力)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6435

题目来源:2018 HDU 多校第十场 - J 题

11.1 题意

现在有 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 把主武器和 m ( 1 ≤ m ≤ 1 0 5 ) m(1 \le m \le 10^5) m(1m105) 把副武器,每把武器都有一能力值 S ( 0 ≤ S ≤ 1 0 9 ) S(0 \le S \le 10^9) S(0S109) k ( k ≤ 5 ) k(k \leq 5) k(k5) 个属性值 x i ( ∣ x i ∣ ≤ 1 0 9 ) x_i(|x_i| \leq 10^9) xi(xi109)

你需要选择一把主武器 M W MW MW 和一把副武器 S W SW SW,产生的能量为
S M W + S S W + ∑ i = 1 k ∣ x M W , i − x S W , i ∣ S_{MW} + S_{SW} + \sum_{i=1}^k |x_{MW,i}-x_{SW,i}| SMW+SSW+i=1kxMW,ixSW,i
问最大可以得到的能量。

11.2 解题过程

注意到
∣ x M W , i − x S W , i ∣ = max ⁡ ( x M W , i − x S W , i , x S W , i − x M W , i ) |x_{MW,i}- x_{SW,i}|=\max(x_{MW,i}- x_{SW,i}, x_{SW,i}- x_{MW,i}) xMW,ixSW,i=max(xMW,ixSW,i,xSW,ixMW,i)
k k k 最大只有 5 5 5,所以考虑状态压缩, 0 0 0 表示当前武器这一属性取正值, 1 1 1 表示取负值。

对两种武器分别进行状态枚举,存储每种状态所产生的能量值。

最终的答案为两种武器能量值之和的最大值。

时间复杂度: O ( 2 k ⋅ ( n + m ) ) O(2^k \cdot (n+m)) O(2k(n+m))

11.3 代码

const int MAXN = 40;
long long x[8], maxA[MAXN], maxB[MAXN];
int main(void)
{
    int t, n, m, k;
    scanf("%d", &t);
    while(t--)
    {
        memset(maxA, 128, sizeof(maxA));
        memset(maxB, 128, sizeof(maxB));
        scanf("%d%d%d", &n, &m, &k);
        for(int i = 0; i < n; i++)
        {
            long long S;
            long long maxx = -1e18;
            scanf("%lld", &S);
            for(int j = 0; j < k; j++)
                scanf("%lld", &x[j]);
            for(int j = 0; j < (1 << k); j++)
            {
                int set = j;
                long long sum = S;
                for(int l = 0; l < k; l++)
                {
                    int bit = set % 2;
                    if(bit == 1)
                        sum += x[l];
                    else
                        sum -= x[l];
                    set /= 2;
                }
                maxA[j] = max(maxA[j], sum);
            }
        }
        for (int i = 0; i < m; i++)
        {
            long long S;
            long long maxx = -1e18;
            scanf("%lld", &S);
            for (int j = 0; j < k; j++)
                scanf("%lld", &x[j]);
            for (int j = 0; j < (1 << k); j++)
            {
                int set = j;
                long long sum = S;
                for (int l = 0; l < k; l++)
                {
                    int bit = set % 2;
                    if (bit == 1)
                        sum += x[l];
                    else
                        sum -= x[l];
                    set /= 2;
                }
                maxB[j] = max(maxB[j], sum);
            }
        }
        long long answer = -1e18;
        for (int i = 0; i < (1 << k); i++)
            answer = max(answer, maxA[i] + maxB[(1 << k) - i - 1]);
        printf("%lld\n", answer);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值