• 挂分宝典


    SB错误

    输出时的取模

    字符串(catalan数):

    (lxhgww)最近接到了一个生成字符串的任务,任务需要他把(n)(1)(m)(0)组成字符串,但是任务还要求在组成的字符串中,在任意的前(k)个字符中,(1)的个数不能少于(0)的个数。现在(lxhgww)想要知道满足要求的字符串共有多少个,聪明的程序员们,你们能帮助他吗?

    输出数据是一行,包括1个数字,表示满足要求的字符串数目,这个数可能会很大,只需输出这个数除以(20100403)的余数

    错误的打开:

    printf("%lld
    ", C(n + m, n) - C(n + m, n + 1));
    

    正确的打开:

    printf("%lld
    ", (C(n + m, n) - C(n + m, n + 1) + mod) % mod);
    

    挂分:30分

    中间变量的类型

    甜圈:

    错误的打开:

    void Pushdown(int u) {
        int mul = t[u].mul, add = t[u].add;
        if (mul != 1 && add) {
    	t[(u<<1)].sum *= mul;
    	t[(u<<1)].mul *= mul;
    	t[(u<<1)].add *= mul;
    	t[(u<<1|1)].sum *= mul;
    	t[(u<<1|1)].mul *= mul;
    	t[(u<<1|1)].add *= mul;
    	t[(u<<1)].sum += add;
    	t[(u<<1)].add += add;
    	t[(u<<1|1)].sum += add;
    	t[(u<<1|1)].add += add;
    	t[u].add = 0;
    	t[u].mul = 1;
        }
    }
    

    正确的打开:

    void Pushdown(int u) {
        unsigned long long mul = t[u].mul, add = t[u].add;
        if (mul != 1 && add) {
    	t[(u<<1)].sum *= mul;
    	t[(u<<1)].mul *= mul;
    	t[(u<<1)].add *= mul;
    	t[(u<<1|1)].sum *= mul;
    	t[(u<<1|1)].mul *= mul;
    	t[(u<<1|1)].add *= mul;
    	t[(u<<1)].sum += add;
    	t[(u<<1)].add += add;
    	t[(u<<1|1)].sum += add;
    	t[(u<<1|1)].add += add;
    	t[u].add = 0;
    	t[u].mul = 1;
        }
    }
    

    挂分: < 90分(<是因为代码别的地方也有锅)

    分块时数组记得多开至少一个块

    不然就会莫名奇妙地越界???反正调了一下午就因为这个。

    Upd:发现是个人实现方法的问题,正常的写法没问题...

    匈牙利一定要用时间戳!!!

    memset什么的不要

    一定要开longlong !!!!!

    一年OI一场空,不开longlong见祖宗。

    多重判断时,一定考虑加不加else!!!

    错误的打开方式:

    if (pre && a[now]) tot++, t++, pre = 1;
    if (!pre && !a[now]) tot++, t++, pre = 1;
    if (pre && !a[now]) tot++, pre = 0;
    if (!pre && a[now]) tot++, pre = 0;
    

    正确的打开方式:

    if (pre && a[now]) tot++, t++, pre = 1;
    else if (!pre && !a[now]) tot++, t++, pre = 1;
    else if (pre && !a[now]) tot++, pre = 0;
    else if (!pre && a[now]) tot++, pre = 0;
    

    断环成链时,数组一定开两倍!!!

    数组该清空的时候就清空,不要蜜汁自信。

    FHQ_Treap pushup记得左+右+1

    积累本

    小凯的疑惑

    对于互质的两个数(a, \, b),其不能组成的最大的数为(a imes b - a - b)

    证明:https://www.luogu.com.cn/blog/user13091/solution-p3951

    另一个用到这个结论的题是小奇挖矿2

    菜肴制作

    建反图,大根堆跑拓扑,可让编号小的尽量靠前。

    让编号小的尽量靠前不等于字典序最小。

    另一个有关的题是:Wide Swap

    「HEOI2012」采花、HH的项链

    暴力是莫队。

    正解是将询问按r排序,根据题意维护每个颜色的最近出现位置。

    采花是维护每个颜色最近两个出现位置,HH的项链则是只维护最近一个,查询用树状数组实现(树状数组下标为0直接特判掉)。

    读入时维护:

    for (int i = 1; i <= n; ++i) {
        col[i] = read();
        last[i] = colst[col[i]];
        colst[col[i]] = i;
    }
    

    回答询问:

    for (int pos = 1, i = 1; pos <= n && i <= m; ++pos) {
        Add(last[pos], 1);
        Add(last[last[pos]], -1);
        for (; q[i].r == pos && i <= m; ++i) ans[q[i].id] = Ask(pos) - Ask(q[i].l - 1);
    }
    

    军训队列

    考虑先从小到大排序,结果一定不劣。
    定义(dp[i][j])为前(i)个,分(j)组的最小答案。
    然后转移变成了:

    [dp[i][j]= min_{k=1}^{i}(dp[i - 1][j - 1] + (a[i] - a[k])^2) ]

    因为后面的多项式既跟(i)有关又跟(j)有关,不能直接单调队列。
    然后考虑到(a[i])单调递增,故可以斜率优化之。

    Simple

    对于一个(c= x imes n + y imes m)来说,一定有(gcd(n,m)|c)
    其实也就是(c)一定可以表示为(x imes gcd(n,m) + y imes gcd(n,m))
    所以让式子两边都除以(gcd(n,m)),对答案是没有影响的。
    为什么这样做呢,因为除完之后,(frac{n}{gcd(n,m)})(frac{m}{gcd(n,m)})就是互质的了。
    所以就可以用上上面凯爹的诱惑的结论。

    ll ans = 0;
    ll d = Gcd(n, m), qd = q / d;
    if (n > m) swap(n, m);
    n /= d, m /= d;
    if (qd >= n * m) ans += (qd - n * m + 1);
    ll k = min(qd, n * m - 1);
    for (ll i = 0; i * m <= k; ++i) {
          ll x = k - i * m;
          ans += x / n + (i != 0);
    }
    printf("%lld
    ", q - ans);
    

    元素周期表

    CV自粉兔

    建立一张二分图,左边的点代表 (n) 个周期,右边的点代表 (m) 个主族。

    把每一个元素 ((x,y)) 看作一条边,连接第 (x) 周期和第 (y) 主族。

    那么我们的目标是是这个二分图变成完全二分图,也就是有 (n imes m) 条边。

    考虑核聚变的条件:

    ((r_1, c_1) + (r_1, c_2) + (r_2, c_1) o (r_2, c_2))

    可以发现这个过程是不改变二分图中的连通分量的个数的。

    而反过来,对于二分图中的某一个连通分量,也可以通过核聚变的方式,把这个连通分量变成“完全”的,也就是连接左右两部分的所有边都存在。

    那么答案就是将这个二分图添加尽量少的边使得它联通的边数。

    也就是: ( ext{连通分量的个数}-1)

    然后,也可以用并查集实现维护联通块的个数。

    Init();
    for (int i = 1; i <= q; ++i) {
        scanf("%d %d", &r[i], &c[i]);
        Merge(r[i], c[i] + n);
    }
    int tot = 0;
    for (int i = 1; i <= n + m; ++i) {	
        if (fa[i] == i) tot++;
    }
    printf("%d
    ", tot - 1);
    

    建设城市

    小奇正在玩一款名为《魔方王国》的游戏。在游戏中它需要建设(n)座不同的城市。

    小奇有(m)个相同的建设队,它需要将这些建设队分配到每个城市中。每个城市至少要分配1个建设队,至多分配(k)个建设队。当然,每个城市分配的建设队数量必须是整数。

    你需要求出有多少种不同的方案。两个方案被视为不同的,当且仅当存在至少一个城市分配到的建设队数量不同。
    (n leq 10^9)(m,k leq 10^7)

    简单的题干,不简单的题...

    首先,将(m)个物品分成(n)组,每组都不为空,方案数为(m-1 choose n-1),这也就是高考数学里常用的隔板法...

    连这都不知道数学能上100分? ——(sir)

    像我一样数学菜到上不了100分的各位请移步大佬博客

    然后隔板法已经保证了每组都不为空,所以我们需要对上界做出限制。

    然后至少有(i)组超过(k)个的方案数为({n choose i} imes {{m - 1 - k imes i} choose {n - 1}})

    应该挺好理解的,但是发现({m-1 choose n-1} - {n choose 1} imes {{m - 1 - k imes 1} choose {n - 1}})并不对。

    因为后面这个东西减多了。

    举个例子:在(n choose 1)中,我们选中了1组,并分给了1组(k)个物品,然后再隔板法随机分,假如又分给了1组2个物品,分给了2组(k+1)个物品。然后会发现假如在(n choose 1)中选中了2组,分给了2组(k)个物品,又在隔板法中分给了2组1个物品,1组(k+2)个物品。假设其他组的分配方案两次都是一样的。

    显然1组有(k+2)个物品,2组有(k+1)个物品这种情况我们算成了两次,但是由于每组最终分配到的数量是一样的,所以这只能算一种情况而不是两种。

    怎么解决呢。

    (不)容易发现,有两组超标的情况会多算1次,有三组超标的情况会多算2次...(这一块题解上并没写,所以感觉写得心里好没底)

    然后就可以容斥搞他了。显然最后的答案是随便选-至少一个不满足的+至少两个不满足的-至少三个不满足的......

    剩下的就是(n)很大,所以用排列数乘上阶乘逆元求组合数,跟那一天她与我许下约定是一样的。

    ll ans = 0;
    for (int i = 0; i <= n; ++i) {
        if (m - 1 - k * i < 0) break;
        if (i & 1) {
            ans = (ans - A[i] * inv[i] % mod * C(m - 1 - k * i, n - 1) % mod + mod) % mod;
        } else {
            ans = (ans + A[i] * inv[i] % mod * C(m - 1 - k * i, n - 1) % mod) % mod;
        }
    }
    printf("%lld", ans);
    

    过河

    因为每个青蛙都是等价的,所以能过一个是一个,这样的贪心是没问题的。

    在贪心正确的基础上,发现限制跳过去的青蛙数量的瓶颈是:从一个石头(包括岸边)一步能跳到的石子数的最小值。

    然后滑动窗口滑一遍就行了。

    int ans = INT_MAX;
    a[0] = 0;
    a[n + 1] = L;
    int l = 0, r = 1;
    for (; r <= n + 1; ++l) {
        while (a[r] - a[l] <= D && r <= n + 1) r++;
        //printf("l: %d r: %d len: %d
    ", l, r, r - l - 1);
        ans = min(ans, r - l - 1);
    }
    if (ans >= m) printf("Excited
    ");
    else printf("%d
    ", ans);
    

    「JLOI2013」卡牌游戏

    概率版约瑟夫游戏。

    无论是概率DP,还是约瑟夫游戏,其实都是倒着做比正着做好做。这题也是一样。

    定义(dp[i][j])为还剩(i)个人时,(j)号获胜的概率。

    显然的(dp[1][1]=1)

    转移见代码:

    dp[1][1] = 1.0;
    for (int i = 2; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            int pos;
            if (a[j] % i) pos = a[j] % i;
    	else pos = i;
    	for (int k = 1; k < i; ++k) {
    	    pos++;
    	    if (pos > i) pos -= i;
    	    dp[i][pos] += dp[i - 1][k] / (double) m;
    	}
        }
    }
    for (int i = 1; i <= n; ++i) printf("%.2lf%% ", dp[n][i] * 100.0);
    

    「TJOI2017」可乐

    一眼就是矩阵优化DP。

    问题是自爆怎么处理,也挺套路的,设一个虚点表示自爆即可,每个点都向这个虚点连边。

    一定要注意这个虚点也要自己跟自己连边!!!

    不然状态是传不过来的。把自爆理解为到了自爆点之后只能留在原地,不能到别处就行了。

    两个矩阵的初始化:

    for (int i = 1; i <= m; ++i) {
        scanf("%d %d", &u, &v);
        base[u][v] = 1;
        base[v][u] = 1;
    }
    for (int i = 1; i <= n + 1; ++i) { //注意虚点也要连边
        base[i][n + 1] = 1;
        base[i][i] = 1;
    }
    ans[1][1] = 1;
    

    快速幂: (卡常小技巧:矩阵乘时尽量把取模放外面,尤其是这样模数特别小的时候,直接1.98s变430ms)

        while (t) {
            if (t & 1) {
                memset(tmp, 0, sizeof tmp);
                for (int i = 1; i <= n + 1; ++i) {
                    for (int j = 1; j <= n + 1; ++j) {
                        for (int k = 1; k <= n + 1; ++k) {
                            tmp[i][j] = (tmp[i][j] + ans[i][k] * base[k][j]);
                        }
                        tmp[i][j] %= mod; //把取模放外面
                    }
                }
                for (int i = 1; i <= n + 1; ++i)
                    for (int j = 1; j <= n + 1; ++j)
                        ans[i][j] = tmp[i][j];
            }
            memset(tmp, 0, sizeof tmp);
            for (int i = 1; i <= n + 1; ++i) {
                for (int j = 1; j <= n + 1; ++j) {
                    for (int k = 1; k <= n + 1; ++k) {
                        tmp[i][j] = (tmp[i][j] + base[i][k] * base[k][j]);
                    }
                    tmp[i][j] %= mod;
                }
            }
            for (int i = 1; i <= n + 1; ++i)
                for (int j = 1; j <= n + 1; ++j)
                    base[i][j] = tmp[i][j];
            t >>= 1;
        }
    

    另一个很好的矩阵优化DP的题是「NOI2020」美食家,虽然做过但是让我再做一遍我感觉还是做不出来...

    「NOIP2016」换教室

    考虑定义(dp[i][j][0/1])为到第(i)个时间段,已经使用了(j)次申请机会,该点是否尝试申请

    而不能定义成:第(i)个时间段,已经使用了(j)次申请机会,该点在哪个教室

    因为申请教室不一定成功,暴力算到状态里会多出很多不必要且麻烦的计算。
    而且发现我们只考虑(i)(i-1),所有可能的转移情况并不多。

    转移式子实在太长了一行放不下,所以分行写了一下,可能有点奇怪。

        dp[1][0][0] = 0;
        dp[1][1][1] = 0;
        for (int i = 2; i <= ti; ++i) {
            dp[i][0][0] = dp[i - 1][0][0] + dist[pos[0][i - 1]][pos[0][i]];
            for (int j = 1; j <= min(haha, i); ++j) {
                int C1 = pos[0][i - 1], C2 = pos[1][i - 1], C3 = pos[0][i], C4 = pos[1][i];
                dp[i][j][0] = min(dp[i][j][0],
                                  min(dp[i - 1][j][0] + dist[C1][C3],
                                      dp[i - 1][j][1] + dist[C1][C3] * (1.0 - k[i - 1]) +
                                        dist[C2][C3] * k[i - 1]));
                dp[i][j][1] = min(dp[i][j][1],
                                  min(dp[i - 1][j - 1][0] + dist[C1][C4] * k[i] + dist[C1][C3] * (1 - k[i]),
                                      dp[i - 1][j - 1][1] + 
                                            dist[C2][C4] * k[i] * k[i - 1] +
                                                dist[C2][C3] * k[i - 1] * (1 - k[i]) +
                                                    dist[C1][C4] * (1 - k[i - 1]) * k[i] +
                                                        dist[C1][C3] * (1 - k[i - 1]) * (1 - k[i])));
            }
        }
    

    求和

    对于一些有取模而且没有逆元的除法,可以考虑在取模之前先把除数给除掉,尤其适用于除数较小的情况。
    另外,快速乘是每次×2,别再写挂了...

    ll Qmul(ll x, ll t) {
    	x %= mod;
    	ll s = 0;
    	while (t) {
    		if (t & 1) s = (s + x) % mod;
    		x = x * 2 % mod;
    		t >>= 1;
    	}
    	return s;
    }
    ll Calc(ll x, ll y) {
    	ll tmp = x + y;
    	if (x % 2 == 0) x /= 2;
    	else if (y % 2 == 0) y /= 2;
    	else tmp /= 2;
    	return Qmul(Qmul(x, y), tmp);
    }
    

    简单计算

    考虑所求答案:

    [left lfloor frac{q}{p} ight floor + left lfloor frac{2q}{p} ight floor + left lfloor frac{3q}{p} ight floor + ...... + left lfloor frac{(p-1)q}{p} ight floor + left lfloor frac{pq}{p} ight floor ]

    与下面这个好求的式子的差别:

    [left lfloor frac{q}{p} + frac{2q}{p} + frac{3q}{p} + ...... + frac{(p - 1)q}{p} + frac{pq}{p} ight floor ]

    显然答案里的每一项因为直接下取整会比上面的每一项少一点贡献。
    考虑计算偏差的贡献,可以找规律发现偏差的贡献一定是若干段以(0)为首项,以(gcd(p,q))为公差,以(p-gcd(p,q))为末项的等差数列
    所以总的偏差为:

    [frac{frac{(p-gcd(p,q)) imes gcd(p,q)}{2}}{p} imes frac{p}{gcd(p,q)} = frac{p - gcd(p, q)}{2} ]

    最终化简的答案为:

    [left lfloor frac{frac{(q+pq) imes p}{2}}{p} ight floor - left lfloor frac{p - gcd(p, q)}{2} ight floor = left lfloor frac{q(p+1)}{2} ight floor - left lfloor frac{p - gcd(p, q)}{2} ight floor ]

    分组配对

    亲 学 长:

    这个第二题我好像提到过

    用倍增来代替二分 降低复杂度

    感觉不太彳亍啊

    学到了学到了,先用倍增缩小答案的取值范围,然后再二分。

    物理课

    用pi时,不要傻乎乎地手打 3.1415926535898 ...double会自动卡精的...用 acos(-1) 更香。

    「CSP-S 2019」括号树(括号序列)

    ans[i]记录到第i个点的答案。
    cnt[i]表示从当前匹配上的括号向左有几个连续的匹配好的同级括号。
    last[i]记录第i个位置上一个未匹配的左括号在哪。

    考虑一个新匹配好的括号的贡献,一定是他自己加上他向左连续延伸的同级括号个数。

    序列版(链):

        for (int i = 1; i <= n; ++i) {
            last[i] = last[i - 1];
            ans[i] = ans[i - 1];
            if (s[i] == '(') last[i] = i, cnt[i] = 0;
            else if (s[i] == ')' && last[i]) {
                ans[i] += 1 + cnt[last[i] - 1];
                cnt[i] = cnt[last[i] - 1] + 1;
                last[i] = last[last[i] - 1];
            }
        }
    

    挪到树上:

        for (int i = 1; i <= n; ++i) {
            last[i] = last[fa[i]];
            ans[i] = ans[fa[i]];
            if (ch[i] == '(') {
                last[i] = i;
            } else if (ch[i] == ')' && last[i]) {
                cnt[i] = cnt[fa[last[i]]] + 1;
                last[i] = last[fa[last[i]]];
                ans[i] += cnt[i];
            }
            finalans ^= (i * ans[i]);
        }
    

    最小距离

    求每个特殊点到最近的特殊点的距离。

    考虑多源最短路,记录每个点是被哪个特殊点更新的。

    然后可以枚举每条边,如果一条边的两端是被同一个特殊点染色的,那么这条边一定不会对答案有贡献。

    否则就更新两端的答案即可。

    多源最短路(就是最开始入队多个点):

    void Dij() {
        memset(dis, 0x3f, sizeof dis);
        memset(vis, 0, sizeof vis);
        for (int i = 1; i <= qq; i++) {
            dis[id[i]] = 0;
            col[id[i]] = id[i];
            q.push(NODE(id[i],0));
        }
        while (!q.empty()) {
            int u = q.top().id;
            q.pop();
            if (vis[u]) continue;
            vis[u] = 1;
            for (int i = head[u]; i; i = e[i].nex) {
                int v = e[i].to;
                if (dis[v] > dis[u] + e[i].w) {
                    dis[v] = dis[u] + e[i].w;
                    col[v] = col[u];
                    q.push(NODE(v, dis[v]));
                }
            }
        }
    }
    

    更新答案:

        for (int i = 1; i <= m; i++) {
            u = rw[i].from, v = rw[i].to, w = rw[i].w;
            if (col[u] == col[v] || !col[u] || !col[v]) continue;
            ans[col[u]] = min(ans[col[u]], dis[u] + dis[v] + w);
            ans[col[v]] = min(ans[col[v]], dis[u] + dis[v] + w);
        }
    

    真相

    显然一个说这句话的人会把一条递推链断开。

    所以我们从每个说这句话的人反向推回去,即可得到这个人说假话时和说真话时各自的贡献(即该块内说真话的人的数量),贡献差加到对应的桶里。

    然后枚举每个桶,用一个初始的答案加上桶里的答案差,检查是否匹配即可。

    反推时细节有点小恶心(一堆if然后没打else调了半天)。

        while (T--) {
            memset(buc, 0, sizeof buc);
            id = 0;
            n = read();
            char op[4];
            for (int i = 1; i <= n; ++i) {
                scanf("%s", op);
                if (op[0] == '+') {
                    a[i] = 1;
                } 
                else if (op[0] == '-') {
                    a[i] = 0;
                } 
                else {
                    a[i] = -1;
                    pos[++id] = i;
                    k[id] = read();
                }
            }
            int startans = 0;
            for (int i = 1; i <= id; ++i) {
                int now = pos[i] - 1;
                int t = 1;
                int pre = 1;
                int tot = 1;
                if (!now) now = n;
                while (a[now] != -1) {
                    if (pre && a[now]) tot++, t++, pre = 1;
                    else if (!pre && !a[now]) tot++, t++, pre = 1;
                    else if (pre && !a[now]) tot++, pre = 0;
                    else if (!pre && a[now]) tot++, pre = 0;
                    now = now - 1;
                    if (!now) now = n;
                }
                startans += tot - t;
                buc[k[i]] += 2 * t - tot;
            }
            if (!id) {
                int t;
                if (a[1]) t = 1;
                else if (!a[1]) t = 0;
                int now = 2;
                if (now == n + 1) now = 1;
                while (now != 1) {
                    if (t && a[now]) t = 1;
                    else if (t && !a[now]) t = 0;
                    else if (!t && a[now]) t = 0;
                    else if (!t && !a[now]) t = 1;
                    if (now == n) break;
                    now = now + 1;
                }
                if (t) printf("consistent
    ");
                else printf("inconsistent
    ");
                continue;
            }
            int flag = 0;
            for (int i = 0; i <= n; ++i) {
                int ans = startans + buc[i];
                if (ans == i) {
                    flag = 1;
                    break;
                }
            }
            if (flag) printf("consistent
    ");
            else printf("inconsistent
    ");
        }
    
    

    「AtCoder ARC107B」Quadruple

    题意:给定(N,K),求满足以下两个条件的可重排列((a,b,c,d))的个数。

    1. (1 leq a,b,c,d leq N)
    2. (a+b-c-d=K)
    #include <cstdio>
    #include <iostream>
    long long N, K;
    int main() {
    	std:: cin >> N >> K;
    	if (K < 0) K = -K;
    	K = 2 * N - K;
    	long long now = 2 * N, ans = 0;
    	while (K >= 2) {
    		ans += (now > N + 1 ? (now - 1 - 2 * (now - N - 1)) : now - 1) * (K > N + 1 ? (K - 1 - 2 * (K - N - 1)) : K - 1);
    		K--, now--;
    	}
    	std:: cout << ans << '
    ';
    	return 0;
    }
    

    奇技淫巧

    1. 当你不知道用哪一种贪心对的时候,那就全用。

    2. 概率不会不要慌,(-nan)(0)来帮忙。

    好文收藏

    一种基于错误的寻找重心方法的点分治的复杂度分析

    「洛谷日报」FHQ_Treap

  • 相关阅读:
    天空卫士陆明:数据法在企业如何落地
    陈宏申:浅谈京东电商商品文案挖掘难点与优化实践
    陈胡:Apache SeaTunnel实现 非CDC数据抽取实践
    李阳:京东零售OLAP平台建设和场景实践
    李卓豪:网易数帆数据中台逻辑数据湖的实践
    苗大东:京东基于强化学习的电商搜索排序算法
    构建AR视频空间大数据平台(物联网及工业互联网、视频、AI场景识别)
    长连接长轮询
    Nacos数据模型
    漏桶和令牌桶的区别
  • 原文地址:https://www.cnblogs.com/Zfio/p/book.html
Copyright © 2020-2023  润新知