2023冲刺国赛模拟 33.1

发布时间 2023-07-10 16:00:27作者: KafuuChinocpp

T1 染色

直接操作分块,可以做到 \(O(n\sqrt{n})\) 。(显然树剖求 lca 约为 \(O(1)\)

code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;

const int max1 = 1e5, B = 300;

int n, m;
vector <int> edge[max1 + 5];

int father[max1 + 5], val[max1 + 5];
long long sum[max1 + 5];
int siz[max1 + 5], deep[max1 + 5], son[max1 + 5];
int top[max1 + 5], dfn[max1 + 5], rk[max1 + 5], dfs_clock;

int tmp[max1 + 5], len;

bool black[max1 + 5];
int cnt[max1 + 5];
long long f[max1 + 5], g[max1 + 5];

void Find_Heavy_Edge ( int now, int depth )
{
    sum[now] = sum[father[now]] + val[now];
    siz[now] = 1, deep[now] = depth, son[now] = 0;

    int max_siz = 0;
    for ( auto v : edge[now] )
    {
        Find_Heavy_Edge(v, depth + 1);

        if ( siz[v] > max_siz )
            max_siz = siz[v], son[now] = v;
        
        siz[now] += siz[v];
    }
    return;
}

void Connect_Heavy_Edge ( int now, int ancestor )
{
    top[now] = ancestor;
    dfn[now] = ++dfs_clock;
    rk[dfs_clock] = now;
    if ( son[now] )
        Connect_Heavy_Edge(son[now], ancestor);
    
    for ( auto v : edge[now] )
    {
        if ( v == son[now] )
            continue;
        
        Connect_Heavy_Edge(v, v);
    }
    return;
}

int Get_Lca ( int u, int v )
{
    while ( top[u] != top[v] )
    {
        if ( deep[top[u]] < deep[top[v]] )
            swap(u, v);
        u = father[top[u]];
    }

    if ( deep[u] > deep[v] )
        swap(u, v);
    return u;
}

long long Get_Dis ( int u, int v )
{
    return sum[u] + sum[v] - (sum[Get_Lca(u, v)] << 1);
}

void Solve ()
{
    memset(cnt + 1, 0, sizeof(int) * n);
    memset(f + 1, 0, sizeof(long long) * n);
    len = 0;

    for ( int i = n; i >= 2; i -- )
    {
        int now = rk[i];
        cnt[now] += (int) black[now];

        f[father[now]] += f[now] + 1LL * cnt[now] * val[now];
        cnt[father[now]] += cnt[now];
    }
    cnt[1] += (int) black[1];

    g[1] = f[1];
    for ( int i = 2; i <= n; i ++ )
    {
        int now = rk[i];
        g[now] = g[father[now]] + 1LL * (cnt[1] - cnt[now] - cnt[now]) * val[now];
    }
    return;
}

int main ()
{
    freopen("color.in", "r", stdin);
    freopen("color.out", "w", stdout);

    scanf("%d%d", &n, &m);
    for ( int i = 2; i <= n; i ++ )
    {
        scanf("%d", &father[i]); ++father[i];
        edge[father[i]].push_back(i);
    }

    for ( int i = 2; i <= n; i ++ )
        scanf("%d", &val[i]);

    Find_Heavy_Edge(1, 0);
    Connect_Heavy_Edge(1, 1);

    int opt, x;
    for ( int i = 1; i <= m; i ++ )
    {
        scanf("%d%d", &opt, &x); ++x;
        if ( opt == 1 )
        {
            if ( !black[x] )
            {
                tmp[++len] = x;
                black[x] = true;
            }
        }
        else
        {
            long long ans = g[x];
            for ( int k = 1; k <= len; k ++ )
                ans += Get_Dis(x, tmp[k]);
            printf("%lld\n", ans);
        }

        if ( !(i % B) )
            Solve();
    }

    return 0;
}

T2 寻宝游戏

题目给出了判定一个点是否在多边形内的方法,由于这只与射线经过的边的奇偶性有关,因此考虑状压奇偶性。

\(f_{i, j, S}\) 表示当前位于点 \((i, j)\) ,宝藏和陷阱的奇偶性为 \(S\) 的最短移动次数,大力 Bfs 转移即可。

code
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;

const int max1 = 20;
const int inf = 1e8;
const int dx[] = { -1, 1, 0, 0 }, dy[] = { 0, 0, -1, 1 };

int n, m, k;
char mp[max1 + 5][max1 + 5];
int px[max1 + 5], py[max1 + 5], val[max1 + 5];

int f[max1 + 5][max1 + 5][1 << 8];

struct Point
{
    int x, y, S;

    Point () {}
    Point ( int __x, int __y, int __S )
        { x = __x, y = __y, S = __S; }
};

queue <Point> que;

int main ()
{
    freopen("game.in", "r", stdin);
    freopen("game.out", "w", stdout);

    scanf("%d%d", &n, &m);
    for ( int i = 1; i <= n; i ++ )
        scanf("%s", mp[i] + 1);
    
    for ( int i = 1; i <= n; i ++ )
    {
        for ( int j = 1; j <= m; j ++ )
        {
            if ( mp[i][j] >= '0' && mp[i][j] <= '9' )
            {
                ++k;
                px[mp[i][j] - '0'] = i;
                py[mp[i][j] - '0'] = j;
            }
        }
    }
    
    for ( int i = 1; i <= k; i ++ )
        scanf("%d", &val[i]);
    
    Point p;

    for ( int i = 1; i <= n; i ++ )
    {
        for ( int j = 1; j <= m; j ++ )
        {
            if ( mp[i][j] == 'B' )
            {
                ++k;
                px[k] = i;
                py[k] = j;
                val[k] = -inf;
            }

            if ( mp[i][j] == 'S' )
                p = Point(i, j, 0), que.push(p);
        }
    }

    for ( int i = 1; i <= n; i ++ )
        for ( int j = 1; j <= m; j ++ )
            for ( int S = 0; S < (1 << k); S ++ )
                f[i][j][S] = inf;
    
    f[p.x][p.y][p.S] = 0;

    while ( !que.empty() )
    {
        int x = que.front().x, y = que.front().y, S = que.front().S;
        que.pop();

        for ( int i = 0; i < 4; i ++ )
        {
            int nx = x + dx[i], ny = y + dy[i];
            if ( nx < 1 || nx > n || ny < 1 || ny > m || (mp[nx][ny] != '.' && mp[nx][ny] != 'S') )
                continue;
            
            int nS = S;
            if ( i < 2 )
            {
                for ( int j = 1; j <= k; j ++ )
                    if ( min(x, nx) == px[j] && py[j] > ny )
                        nS ^= 1 << (j - 1);
            }

            if ( f[nx][ny][nS] == inf )
            {
                f[nx][ny][nS] = f[x][y][S] + 1;
                que.push(Point(nx, ny, nS));
            }
        }
    }

    int ans = 0;
    for ( int S = 0; S < (1 << k); S ++ )
    {
        if ( f[p.x][p.y][S] == inf )
            continue;

        int up = -f[p.x][p.y][S];
        for ( int i = 1; i <= k; i ++ )
            up += ((S >> (i - 1)) & 1) * val[i];
        
        ans = max(ans, up);
    }

    printf("%d\n", ans);

    return 0;
}

T3 点分治

发现需要求解的就是本质不同的点分树的数量,没有办法根据删点的过程划分子问题,因此考虑最朴素的树形 dp 。

考虑一条边 \((u, v)\) ,如果我们已经得到 \(u, v\) 子树形成的点分树,考虑进行合并。

考虑合并后的点分树的根节点,容易发现这只能是 \(u\) 子树点分树的根节点或 \(v\) 子树点分树的根节点,如果根节点为 \(u\) 子树点分树的根节点,容易发现 \(u\) 子树点分树根节点中除 \(u\) 节点所在子树的其余儿子,一定为当前根节点的儿子,因此将这些部分删去,剩余部分仍然为两棵点分树,可以直接递归进行合并。容易发现 \(u\)\(v\) 节点被删去后合并终止。

发现合并过程产生的贡献只与 \(u, v\) 节点在点分树中的深度有关,因此设 \(f_{u, i}\) 表示以 \(u\) 为根的子树, \(u\) 节点在点分树中的深度为 \(i\) 的方案数。转移考虑 \(u, v\) 节点哪个先被删去,如果 \(u\) 先被删去,枚举 \(v\) 点分树中在 \(u\) 之前被删去的深度进行转移; \(v\) 节点先被删去同理。简单优化可以做到 \(O(n^2)\)

code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;

const int max1 = 5000;
const int mod = 1e9 + 7;

int n;
vector <int> edge[max1 + 5];

int C[max1 + 5][max1 + 5];
int siz[max1 + 5], f[max1 + 5][max1 + 5], tmp[max1 + 5], sum[max1 + 5];

void Dfs ( int now, int fa )
{
    siz[now] = 1;
    f[now][1] = 1;

    for ( auto v : edge[now] )
    {
        if ( v == fa )
            continue;
        
        Dfs(v, now);
        memcpy(tmp + 1, f[now] + 1, sizeof(int) * siz[now]);
        memset(f[now] + 1, 0, sizeof(int) * (siz[now] + siz[v]));

        sum[siz[v] + 1] = 0;
        for ( int i = siz[v]; i >= 1; i -- )
            sum[i] = (sum[i + 1] + f[v][i]) % mod;
        
        for ( int i = 1; i <= siz[now]; i ++ )
            for ( int k = 1; k <= siz[v]; k ++ )
                f[now][i + k - 1] = (f[now][i + k - 1] + 1LL * tmp[i] * C[i - 1 + k - 1][i - 1] % mod * sum[k]) % mod;
        
        for ( int j = 1; j <= siz[v]; j ++ )
        {
            sum[0] = 0;
            for ( int i = 1; i <= siz[now]; i ++ )
                sum[i] = (sum[i - 1] + C[i - 1 + j - 1][j - 1]) % mod;
            for ( int i = 1; i <= siz[now]; i ++ )
                f[now][i + j] = (f[now][i + j] + 1LL * tmp[i] * f[v][j] % mod * sum[i]) % mod;
        }
        siz[now] += siz[v];
    }
    return;
}

int main ()
{
    freopen("dianfen.in", "r", stdin);
    freopen("dianfen.out", "w", stdout);

    scanf("%d", &n);
    for ( int i = 2, u, v; i <= n; i ++ )
    {
        scanf("%d%d", &u, &v);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }

    C[0][0] = 1;
    for ( int i = 1; i <= n; i ++ )
    {
        C[i][0] = 1;
        for ( int j = 1; j <= i; j ++ )
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
    }

    Dfs(1, 0);

    int ans = 0;
    for ( int i = 1; i <= n; i ++ )
        ans = (ans + f[1][i]) % mod;
    printf("%d\n", ans);

    return 0;
}