挂分宝典

傻逼错误

输出时的取模

字符串(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])));
        }
    }

好文收藏

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

原文地址:https://www.cnblogs.com/Zfio/p/13782803.html