zoukankan      html  css  js  c++  java
  • 挂分宝典

    傻逼错误

    输出时的取模

    字符串(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见祖宗。

    积累本

    小凯的疑惑

    对于互质的两个数(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])));
            }
        }
    

    好文收藏

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

  • 相关阅读:
    java+phantomjs实现动态网页抓取
    windows 安装 cordova
    windows系统安装 ionic
    windows系统 安装 mysql.fx
    安装 Navicat for MySQL
    windows 安装 MySQL
    调用百度地图api隐藏版权信息
    ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
    Angular 调用百度地图API接口
    Angular 使用 frame 加载网络资源显示路径不安全问题
  • 原文地址:https://www.cnblogs.com/Zfio/p/13782803.html
Copyright © 2011-2022 走看看