『模拟赛』多校A层冲刺NOIP2024模拟赛05

Rank

烂。

image

A. 好数(number)

签,唐,没签上。

考虑之前几次类似的题方法都是选 \(k-1\) 的方案存起来以使总复杂度除以一个 \(n\),故考虑记共 \(n^2\) 个两两组合之和,枚举当前点 \(i\) 前面的点,找是否有值为它们的差的组合,复杂度 \(\mathcal{O(n^2)}\),用 map 记再挂个 \(\log n\)

赛时唐氏枚举反了,使复杂度成为 \(\mathcal{O(n^3\log n)}\),然后被 hack 10pts。

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
	char ch = getchar();lx x = 0 , f = 1;
	for(;ch<'0'||ch>'9';ch = getchar()) if(ch == '-') f = -1;
	for(;ch>= '0' && ch<= '9';ch = getchar()) x = (x<<3) + (x<<1) + (ch^48);
	return x*f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define pil pair<int, ll>
#define fi first
#define se second
const int Ratio = 0;
const int N = 2e4 + 5, M = 3e7 + 5;
int n, ans;
int a[N], nc[M], tot;
unordered_map<int,int> st;
namespace Wisadel
{
    short main()
    {
        // freopen(".in", "r", stdin) , freopen(".out", "w", stdout);
        // freopen(".err", "w", stderr);
        freopen("number.in", "r", stdin) , freopen("number.out", "w", stdout);
        n = qr;
        fo(i, 1, n) a[i] = qr;
        st[2 * a[1]] = 1;
        fo(i, 2, n)
        {
            fo(j, 1, i - 1)
                if(st[a[i] - a[j]]){ans++; break;}
            fo(j, 1, i) st[a[i] + a[j]] = 1;
        }
        printf("%d\n", ans);
        return Ratio;
    }
}
int main(){return Wisadel::main();}

B. SOS字符串(sos)

不难想的分讨型 dp,但赛时脑子被卡住了一点没想 dp。

发现转移只用到后串的最后几个字符,且只用到两种状态:

  1. \(S\rightarrow SO\)
  2. \(SO\rightarrow SOS\)

故 dp 数组设为 \(f_{i,0/1/2}\),0/1 对应上面两种,2 表示除上述两种以外的状态,SOS 也计入状态 2。题目要求 SOS 出现三次及以上数量,我们再开一维 0/1/2/3 表示目前已经出现了几次 SOS,超过 3 次计入 3 状态内。然后就可以开始分讨状态转移方程了。

  • 对于 \(f_{i,0,j}\)\(i-1\) 为 S,第 \(i\) 位仍为 S;\(i-1\) 为状态 2,第 \(i\) 位也可放 S,故有:

\[f_{i,0,j}=f_{i-1,0,j}+f_{i-1,2,j} \]

  • 对于 \(f_{i,1,j}\)\(i-1\) 为 S,\(i\) 位放 O,故有:

\[f_{i,1,j}=f_{i-1,0,j} \]

  • 对于 \(f_{i,2,j}\)\(i-1\) 为 S,\(i\) 位放除 S、O 以外的 24 种字母;\(i-1\) 为 SO,\(i\) 位放除 S 以外的 25 种字母;\(i-1\) 为状态 2,\(i\) 位放除 S 以外的 25 种字母,故有:

\[f_{i,2,j}=f_{i-1,0,j}\times 24+f_{i-1,1,j}\times 25+f_{i-1,2,j}\times 25 \]

  • 考虑当 \(j\gt 0\) 时,SOS 可由 SO 加 S 转移而来,故有:

\[f_{i,2,j}=f_{i-1,1,j-1} \]

  • 考虑当 \(j= 3\) 时,由于 3 次及以上都计入 3 内,SOS 可由 SO 加 S 转移而来,故有:

\[f_{i,2,j}=f_{i-1,1,j} \]

最终答案为 \(\sum_{i=0}^2\ f_{n,i,3}\)。复杂度线性,有 4 的常数。

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
	char ch = getchar();lx x = 0 , f = 1;
	for(;ch<'0'||ch>'9';ch = getchar()) if(ch == '-') f = -1;
	for(;ch>= '0' && ch<= '9';ch = getchar()) x = (x<<3) + (x<<1) + (ch^48);
	return x*f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define pil pair<int, ll>
#define fi first
#define se second
const int Ratio = 0;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
int n;
ll f[N][3][4];
namespace Wisadel
{
    short main()
    {
        // freopen(".in", "r", stdin) , freopen(".out", "w", stdout);
        // freopen(".err", "w", stderr);
        freopen("sos.in", "r", stdin) , freopen("sos.out", "w", stdout);
        n = qr;
        f[0][2][0] = 1;
        fo(i, 1, n)
        {
            fo(j, 0, 3)
            {
                f[i][0][j] = (f[i][0][j] + f[i - 1][2][j] + f[i - 1][0][j]) % mod;
                f[i][1][j] = (f[i][1][j] + f[i - 1][0][j]) % mod;
                f[i][2][j] = (f[i][2][j] + f[i - 1][0][j] * 24 + f[i - 1][1][j] * 25 + f[i - 1][2][j] * 25) % mod;
                if(j > 0) f[i][2][j] = (f[i][2][j] + f[i - 1][1][j - 1]) % mod;
                if(j == 3) f[i][2][j] = (f[i][2][j] + f[i - 1][1][j]) % mod;
            }
        }
        printf("%lld\n", (f[n][0][3] + f[n][1][3] + f[n][2][3]) % mod);
        return Ratio;
    }
}
int main(){return Wisadel::main();}

C. 集训营的气球(balloon)

很好的 trick:退背包。

先考虑部分分,发现存在修改与查询,故考虑线段树。发现 \(c\le 20\),我们可以求出每一个子段内不同 \(c\) 的方案数,超过 \(c\) 计入 \(c\) 内。

证明

证:\(f_{rt,k}=\sum_{i+j=k}f_{ls,i}\times f_{rs,j} (k\lt c)\)\(f_{rt,c}=\sum_{i+j\ge c} f_{ls,i}\times f_{rs,j}\)

对于前一个式子是比较显然的:左选 \(i\) 个,右选 \(j\) 个得出共选 \(i+j\) 个的方案数。主要考虑第二个式子。

已知 \(i+j\ge c\),即左中所有子方案都选 \(i\) 个,右中所有子方案都选 \(j\) 个,依据乘法原理,得出左右子方案两两相乘得到总方案的所有子方案,每个子方案都选 \(i+j\ge c\) 个,显然计入 \(c\) 中不重不漏。

这样做时间复杂度是 \(\mathcal{O(nc^2\log n+q)}\) 的,空间复杂度按四倍空间算是 \(\mathcal{O(4nc^2)}\) 的,无论时间还是空间都过不去 \(n=10^6\) 的点。这样算上前三个 Subtask 的背包得分,最高可获得 80pts。

考虑正解。正解需要由前三个 Subtask 所推的 01 背包做法优化得来,考虑怎么背包。将题目视为容量为 \(c\),每个人体积为 1 并将 \(a_i\)\(b_i\) 看作权重计入,发现需要算大于 \(c\) 的所有容量的方案数,复杂度是 \(\mathcal{O(n^2)}\) 的,显然不可行。

发现容量不超过 \(c\) 的情况很小,故正难则反考虑容斥,用总方案数减去容量小于 \(c\) 的方案数求解。这样在不带修的情况下,我们的复杂度就降到 \(\mathcal{O(nc)}\) 了。

考虑带修改怎么做。我们将修改操作视为删除与添加,删除时直接在 dp 数组中删去该需求的全部贡献,其实就是反着做 01 背包,然后在插入一个新需求,即给它单独做一个 01 背包。这就是退背包。这样我们的总复杂度就是 \(\mathcal{O(nc+qc)}\) 了。

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
	char ch = getchar();lx x = 0 , f = 1;
	for(;ch<'0'||ch>'9';ch = getchar()) if(ch == '-') f = -1;
	for(;ch>= '0' && ch<= '9';ch = getchar()) x = (x<<3) + (x<<1) + (ch^48);
	return x*f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define pil pair<int, ll>
#define fi first
#define se second
const int Ratio = 0;
const int N = 1e6 + 1, M = 1e6;
const int mod = 1e9 + 7;
int n, c, m;
ll a[N], b[N];
ll f[21], tot;
namespace Wisadel
{
    ll Wqp(ll x, int y)
    {
        ll res = 1;
        while(y){if(y & 1) res = res * x % mod; x = x * x % mod; y >>= 1;}
        return res;
    }
    void Wjin(int x)
    {
        fu(i, c - 1, 1) f[i] = (f[i - 1] * a[x] % mod + f[i] * b[x] % mod) % mod;
        f[0] = f[0] * b[x] % mod;
        tot = tot * (a[x] + b[x]) % mod;
    }
    void Wtui(int x)
    {
        ll ny = Wqp(b[x], mod - 2);
        f[0] = f[0] * ny % mod;
        fo(i, 1, c - 1) f[i] = (f[i] - f[i - 1] * a[x] % mod + mod) % mod * ny % mod;
        tot = tot * Wqp(a[x] + b[x], mod - 2) % mod;
    }
    short main()
    {
        // freopen(".in", "r", stdin) , freopen("a.out", "w", stdout);
        // freopen(".err", "w", stderr);
        freopen("balloon.in", "r", stdin) , freopen("balloon.out", "w", stdout);
        n = qr, c = qr;
        fo(i, 1, n) a[i] = qr;
        fo(i, 1, n) b[i] = qr;
        f[0] = tot = 1;
        fo(i, 1, n) Wjin(i);
        m = qr;
        fo(i, 1, m)
        {
            int x = qr, aa = qr, bb = qr;
            Wtui(x);
            a[x] = aa, b[x] = bb;
            Wjin(x);
            ll ans = tot;
            fo(j, 0, c - 1) ans = (ans - f[j] + mod) % mod;
            printf("%lld\n", ans);
        }
        return Ratio;
    }
}
int main(){return Wisadel::main();}

D. 连通子树与树的重心(tree)

原[FJOI2014] 树的重心

树的重心具有如下性质:

  • 如果重心为两个 \(a\)\(b\),那么 \(a\)\(b\) 必定是一条边上的两个端点,且把这条边断开后,以 \(a\)\(b\) 为根的子树大小相同。

  • 如果重心为 1 个,若这棵树的大小为 \(S\),以重心为根时其最大的子树大小为 \(m\),则有 \(S-m\gt m\)

证明摘自洛谷。

image

那么和 T3 很像,我们设 \(f_{u,i}\)\(u\) 节点所在连通块大小为 \(i\) 的方案数,那么显然有:

\[f_{u,k}=\sum_{i+j=k}\ f_{v,i}\times f_{u,j}(u\rightarrow v) \]

这样两个重心的情况就可以在 \(\mathcal{O(n^2)}\) 内解决了。

考虑一个重心的情况,依旧从暴力开始想,最暴力的方法就是枚举重心的儿子,然后背包做,复杂度是 \(\mathcal{O(n^3)}\) 的,原题是多测范围小可通过,模拟赛中则不行,需要另行优化。

考虑 T3 怎么做的?容斥和退背包!我们算不合法的情况,即 \(S-m\le m\)。比较显然根节点最多只有一个子节点满足这个情况,因此我们直接枚举它的子节点,重心所在连通块大小和子节点的节点数,这样复杂度就到了 \(\mathcal{O(n^2)}\)。然后再容斥,因为此时 \(f_{u,S-i}\times f_{v,i}\) 会算重 \(v\) 子树的贡献,因此我们做一个退背包将它删掉就好了。

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
	char ch = getchar();lx x = 0 , f = 1;
	for(;ch<'0'||ch>'9';ch = getchar()) if(ch == '-') f = -1;
	for(;ch>= '0' && ch<= '9';ch = getchar()) x = (x<<3) + (x<<1) + (ch^48);
	return x*f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define pil pair<int, ll>
#define fi first
#define se second
const int Ratio = 0;
const int N = 5000 + 5;
const int mod = 10007;
int n, c1, c2, W = 1e9, ans;
int siz[N], w[N];
int hh[N], to[N << 1], ne[N << 1], cnt;
int f[N][N];
namespace Wtask2
{
    void Wdfs(int u, int fa)
    {
        siz[u] = 1, f[u][1] = 1;
        for(int i = hh[u]; i != -1; i = ne[i])
        {
            int v = to[i];
            if(v == fa) continue;
            Wdfs(v, u);
            fu(j, siz[u], 1) fu(k, siz[v], 1)
                f[u][j + k] = (f[u][j + k] + f[u][j] * f[v][k] % mod) % mod;
            siz[u] += siz[v];
        }
    }
    short main()
    {
        fill(siz + 1, siz + 1 + n, 0);
        Wdfs(c1, c2), Wdfs(c2, c1);
        fo(i, 1, n) ans = (ans + f[c1][i] * f[c2][i] % mod) % mod;
        printf("%d\n", ans);
        return Ratio;
    }
}
namespace Wtask1
{
    int zc[N][N];
    void Wdfs(int u, int fa)
    {
        siz[u] = 1;
        f[u][1] = 1;
        for(int i = hh[u]; i != -1; i = ne[i])
        {
            int v = to[i];
            if(v == fa) continue;
            Wdfs(v, u);
            fu(j, siz[u], 1) fu(k, siz[v], 1)
                f[u][j + k] = (f[u][j + k] + f[u][j] * f[v][k] % mod) % mod;
            siz[u] += siz[v];
        }
    }
    short main()
    {
        Wdfs(c1, 0);
        for(int i = hh[c1]; i != -1; i = ne[i])
        {
            int v = to[i];
            fo(j, 1, n)
            {
                zc[v][j] = f[c1][j];
                fo(k, 1, min(j, siz[v]))
                    zc[v][j] = (zc[v][j] - zc[v][j - k] * f[v][k] % mod + mod) % mod;
            }
        }
        fo(i, 1, n)
        {
            ans = (ans + f[c1][i]) % mod;
            for(int j = hh[c1]; j != -1; j = ne[j])
            {
                int v = to[j];
                fo(k, (i + 1) / 2, min(i, siz[v]))
                    ans = (ans - f[v][k] * zc[v][i - k] % mod + mod) % mod;
            }
        }
        printf("%d\n", ans);
        return Ratio;
    }
}
namespace Wisadel
{
    void Wadd(int u, int v)
    {
        to[++cnt] = v;
        ne[cnt] = hh[u];
        hh[u] = cnt;
    }
    void Wfc(int u, int fa)
    {
        siz[u] = 1;
        for(int i = hh[u]; i != -1; i = ne[i])
        {
            int v = to[i];
            if(v == fa) continue;
            Wfc(v, u);
            siz[u] += siz[v];
            w[u] = max(w[u], siz[v]);
        }
        w[u] = max(w[u], n - siz[u]);
        W = min(W, w[u]);
    }
    short main()
    {
        // freopen(".in", "r", stdin) , freopen(".out", "w", stdout);
        // freopen(".err", "w", stderr);
        freopen("tree.in", "r", stdin) , freopen("tree.out", "w", stdout);
        memset(hh, -1, sizeof hh);
        n = qr;
        fo(i, 1, n - 1)
        {
            int a = qr, b = qr;
            Wadd(a, b), Wadd(b, a);
        }
        Wfc(1, 0);
        fo(i, 1, n) if(w[i] == W)
            if(!c1) c1 = i;
            else c2 = i;
        if(!c2) return Wtask1::main();
        else return Wtask2::main();
        return Ratio;
    }
}
int main(){return Wisadel::main();}

签到题没签上属实有点唐,好在赛时不知道没影响心态,T3 暴力打出来了。

还是,没思路想 dp!今天四道题一眼三个取模,结果三个 dp,两个都用到了退背包,就当加训 dp 了,学到新 trick 不亏。

晚上开小会了,挺燃的,以后要开始专注模式了。


完结撒花~

最后一张!

Aqr 钦定的又一张

image

image

posted @ 2024-10-11 21:05  Ratio_Y  阅读(66)  评论(1编辑  收藏  举报