讲题

P5323 光线

两个玻璃的透光率和反光率可以合并,如下列式解方程即可: 方程图

$$ \left\{ \begin{aligned} B&=Ap_1+Dq_1\cr D&=Bq_2\cr C&=Bp_2\cr E&=Dp_1+aq_1 \end{aligned} \right. $$

解得:

$$ \left\{ \begin{aligned} p_{12}&=\frac CA=\frac{p_1p_2}{1-q_1q_2}\cr q_{12}&=\frac EA=\frac{p_2^2q_1}{1-q_1q_2}+q_2 \end{aligned} \right. $$

从 1 到 n 全合并起来即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const mod inv = (mod)1 / 100;

int main() {
    mod p1 = 1, q1;
    ll n;
    cin >> n;
    while (n--) {
        mod p2, q2, nxp, nxq;
        cin >> p2 >> q2;
        p2 *= inv, q2 *= inv;
        nxp = p1 * p2 / ((mod)1 - q1 * q2);
        nxq = p2 * p2 * q1 / ((mod)1 - q1 * q2) + q2;
        p1 = nxp, q1 = nxq;
    }
    cout << p1;
    return 0;
}

NOI 嘉年华

题意

题意

$n\le 200$

做法

$$ f(i,j)\gets \max_{k g 类似。


询问的时候先枚举一个包含当前活动的时间区间,然后和左右拼接,$O(n^5)$。 $$ ans=\max_{l,r,i,j}\min(i+j+in(l,r),f(l,i)+g(r,j)) $$

预处理:

$$ans(x,y)=\max_{[x,y]\subset[l,r],i,j}\min(i+j+in(l,r),f(l,i)+g(r,j))$$

这样查询的时候就只需要枚举两维,瓶颈在预处理,$O(n^4)$


$f(l,i),g(r,j)$随着 i,j 增加单调减,因此上面的东西有决策单调性。双指针即可,复杂度 $O(n^3)$。

作业题

题意

给一个图,求出其所有生成树的边权和乘边权 gcd 之和 $n\le 30,w\le 152501$。

小贴士: 矩阵树定理可以在 $O(n^3)$ 时间内求出所有生成树树的边权乘积之和。这里的边权可以是任意数据类型,但要求这个数据类型支持加减乘除,并且符合整数的所有运算规律。

做法

显然是先枚举一个 gcd ,保留边权为 gcd 倍数的边,然后统计当前所有生成树的边权之和。随后做一个高维差分,就得到了 gcd 恰好为每个数的所有生成树边权之和。 现在问题变成了:给一个图,求出所有生成树边权之和。

暴力

枚举一条边,用矩阵树算出来有多少个生成树包含这个边(矩阵树中的边权赋为 1)。 复杂度爆炸,过不了。

智慧

$$ \prod (w_ix+1)\equiv (\sum w_i)x+1 \pmod {x^2} $$$$ (ax+b)^{-1}=(b-a)b^{-2}x+b^{-1} \pmod {x^2} $$

复杂度 $O(n^3w)$,依然过不了,但是每次枚举一个 gcd,判断一下当前可选边数是否大于 n-1,就可过。 最多有 $\frac{ m\max(d(w)) }{n-1}$ 个数满足条件,可以通过。

P5336 [THUSC2016]成绩单

题意

题面 $n\le 50,w\le 1000$ 主要想 dp 状态

做法

$f(l,r,vl,vr)$ 表示把区间 $[l,r]$ 删到只剩下值在 $[vl,vr]$ 的最小代价。$g(l,r)$ 表示把 $[l,r]$ 全删光的代价,枚举中点转移即可。

CF1637E Best Pair

题意

给一个长度为 n 的数组 a,定义 $cnt_x$ 为 x 这个值在 a 中的出现次数。定义函数 $f(x,y)=(cnt_x+cnt_y)(x+y)$(要求 $x < y$)。 还给出 m 个无序数对,称其为“坏数对”。 对于所有“不坏的”无序数对 $(x,y)$,求出 $f(x,y)$ 的最大值。 $n,m\le 3\times 10^5,a_i \le 10^9$

做法

我们先把 a 排个序,然后相同的一段缩成一个二元组 {值,出现个数}

想想 m=0 咋做。想了半天,这个函数似乎没法硬维护,只能往性质方向想尤其是根号方面的

考虑枚举一个 y,看看哪些 x 可能成为答案的:

  • 如果出现 $x_1>x_2$,满足 $cnt_{x_1}\ge cnt_{x_2}$ 那 $x_2$ 必然没用

因此,所有可能成为答案的 x,都在 y-1 向左的 cnt 的单调栈上。

因此,我们只需要:

  • 枚举右端点 y
    • 令 $x\gets y-1$
    • 不断进行直到 $x=0$:
      • 用 $f(x,y)$ 更新答案
      • $x\gets pre[x]$

内部每循环一次,$cnt_x$ 至少增加 1,而 $\sum cnt_i=n$,所以内部的循环最多不会跑超过 $\sqrt n$ 次,复杂度 $O(n\sqrt n)$。


而有 m 的时候,我们稍微改一下即可:

  • 枚举右端点 y
    • 令 $x\gets y-1$
    • 不断进行直到 $x=0$:
      • 如果 $(x,y)$ 是好的
        • 用 $f(x,y)$ 更新答案
        • $x\gets pre[x]$
      • 否则
        • $x\gets x-1$

代码

考试的时候求前驱的部分写错了调半天没过,吐了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
const char nl = '\n';
const ll MXN = 1e6 + 5;
const ll INF = 1e18;
ll n, m, arr[MXN];
char str[MXN];
vec<ll> bad[MXN];
ll v[MXN], cnt[MXN], pre[MXN];
set<pi> last;
bool ban[MXN];
int main() {
    /* freopen("test.in", "r", stdin); */
    /* freopen("test.out", "w", stdout); */
    ios::sync_with_stdio(0);
    cin.tie(0);
    setp(6);
    ll t;
    cin >> t;
    while (t--) {
        cin >> n >> m;
        for (ll i = 1; i <= n; i++) {
            cin >> arr[i];
            bad[i].clear();
        }
        sort(arr + 1, arr + 1 + n);
        ll ind = 0;
        for (ll i = 1; i <= n; i++) {
            if (arr[i] != arr[i - 1]) {
                ++ind;
                v[ind] = arr[i];
                cnt[ind] = 0;
            }
            ++cnt[ind];
        }
        cnt[0]=INF;
        stack<ll> stk;
        stk.push(0);
        for (ll i = 1; i <= ind; i++) {
            while(cnt[i]>=cnt[stk.top()])stk.pop();
            pre[i] = stk.top();
            stk.push(i);
            /* cout << pre[i]; */
        }
        while (m--) {
            ll x, y;
            cin >> x >> y;
            x = lower_bound(v + 1, v + 1 + ind, x) - v;
            y = lower_bound(v + 1, v + 1 + ind, y) - v;
            if (x > y) swap(x, y);
            bad[y].push_back(x);
        }
        ll ir = ind, il, ans = 0;
        while (ir) {
            if (ban[ir])
                --ir;
            else {
                for (ll nx : bad[ir]) ban[nx] = 1;
                ll il = ir - 1;
                while (il) {
                    if (ban[il])
                        --il;
                    else {
                        ans = max(ans, (cnt[il] + cnt[ir]) * (v[il] + v[ir]));
                        il = pre[il];
                    }
                }
                for (ll nx : bad[ir]) ban[nx] = 0;
                --ir;
            }
        }
        cout << ans << nl;
    }
    return 0;
}