2023冲刺国赛模拟 17.1

T1 掌分治

由于当前分治中心的贡献为其所在连通块的大小,因此考虑统计每个点 \(u\) ,对于当前分治中心 \(root\) 的贡献,也就是统计当前分治中心 \(root\) 与点 \(u\) 连通的方案数。

设随机选择分治中心形成的序列为 \(p\) ,如果原图是一棵树,不难发现这等价于 \(root\to u\) 路径上所有点在 \(p\) 中出现的位置大于 \(root\) 出现的位置,这很容易进行统计,考虑原图是一棵仙人掌,比较显然的想法是建立圆方树,圆方树上方点连接的所有圆点在仙人掌上构成一个简单环,由于 \(root\to u\) 的路径上的所有方点代表两种路径,因此只需要钦定其中一条连通即可,两条道路同时连通的情况会被重复计算,需要减去,可以使用背包 dp 计算 \(root\to u\) 的路径上,钦定 \(i\) 个点使得 \(root\)\(u\) 连通的容斥系数之和,可以在 \(O(n^3)\) 的复杂度下求解答案。

code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int max1 = 400;
const int mod = 998244353;

int n, m;
vector <int> edge[max1 + 5];
vector <int> new_edge[max1 * 2 + 5];
int matrix;

int dfn[max1 + 5], low[max1 + 5], dfs_clock;
int s[max1 + 5], top;

int inv[max1 + 5], fac[max1 + 5], ifac[max1 + 5];
int val[max1 + 5];

int f[max1 + 5][max1 + 5], ans;

int A ( int n, int m )
{
    if ( n < m || n < 0 || m < 0 )
        return 0;
    return 1LL * fac[n] * ifac[n - m] % mod;
}

void Tarjan ( int now )
{
    dfn[now] = low[now] = ++dfs_clock;
    s[++top] = now;

    for ( auto v : edge[now] )
    {
        if ( !dfn[v] )
        {
            Tarjan(v);
            low[now] = min(low[now], low[v]);

            if ( low[v] == dfn[now] )
            {
                ++matrix; int x;
                do
                {
                    x = s[top--];
                    new_edge[matrix].push_back(x);
                } while ( x != v );
                new_edge[now].push_back(matrix);
            }
        }
        else
            low[now] = min(low[now], dfn[v]);
    }
    return;
}

void Dfs ( int now, int fa )
{
    if ( now <= n )
    {
        for ( int i = 0; i <= n - 1; i ++ )
        {
            if ( !f[now][i] )
                continue;

            ans = ( ans + 1LL * f[now][i] * val[i] ) % mod;
        }

        for ( auto v : new_edge[now] )
            Dfs(v, now);
    }
    else
    {
        int len = new_edge[now].size() - 1;
        
        for ( int i = 0; i <= len; i ++ )
        {
            int v = new_edge[now][i];
            memset(f[v], 0, sizeof(int) * n);
            int x = i + 1;
            for ( int k = 0; k <= n - x - 1; k ++ )
                f[v][k + x] = ( f[v][k + x] + f[fa][k] ) % mod;
            x = len - i + 1;
            for ( int k = 0; k <= n - x + 1; k ++ )
                f[v][k + x] = ( f[v][k + x] + f[fa][k] ) % mod;
            x = len + 1;
            for ( int k = 0; k <= n - x + 1; k ++ )
                f[v][k + x] = ( f[v][k + x] - f[fa][k] + mod ) % mod;
            
            Dfs(v, now);
        }
    }
    return;
}

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

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

    inv[1] = 1;
    for ( int i = 2; i <= n; i ++ )
        inv[i] = 1LL * ( mod - mod / i ) * inv[mod % i] % mod;
    fac[0] = ifac[0] = 1;
    for ( int i = 1; i <= n; i ++ )
    {
        fac[i] = 1LL * fac[i - 1] * i % mod;
        ifac[i] = 1LL * ifac[i - 1] * inv[i] % mod;
    }

    for ( int i = 0; i <= n - 1; i ++ )
        for ( int p = 1; p <= n; p ++ )
            val[i] = ( val[i] + 1LL * A(n - p, i) * fac[n - i - 1] ) % mod;

    for ( int i = 1; i <= n; i ++ )
    {
        memset(dfn + 1, 0, sizeof(int) * n); dfs_clock = top = 0;
        for ( int k = 1; k <= n + n; k ++ )
            new_edge[k].clear();
        matrix = n;
        Tarjan(i);

        memset(f[i], 0, sizeof(int) * n);
        f[i][0] = 1;

        Dfs(i, 0);
    }

    ans = 1LL * ans * ifac[n] % mod;
    printf("%d\n", ans);

    return 0;
}

T2 图圈圈

一群人写随机化,实在是太邪恶了。

首先对原图建立 dfs 树,如果存在 \(n\) 条返祖边,显然一定存在两条本质不同,长度相同的简单环。

对于 \(m\le 2n\) 的情况,容易发现我们只需要找到原图中 \(O(n)\) 条本质不同的环就可以判断,那么考虑枚举环上断点 \(root\) 进行 Dfs ,设当前 Dfs 到达的点为 \(u\) ,考虑一条没有被经过的出边 \((u,v)\) ,如果 \(v=root\) ,直接统计当前环,如果 \(v\) 没有被访问过,并且删去原先选择的边和 \((u,v)\) 后, \(root\)\(v\) 连通,此时一定存在环包含原先选择的边和 \((u,v)\) ,因此继续 Dfs 。

容易发现上述过程可以保证每次 Dfs 都是有效的一步(一定能够找到一个环),简单计算复杂度,考虑到环的数量为 \(n\) ,由于环的大小为 \(n\) ,因此一个环会产生 \(n\) 个断点,也就是一个环贡献 \(n\) 次 Dfs ,同时每次 Dfs 需要进行复杂度为 \(O(n)\) 的 Check ,因此总复杂度为 \(O(n^3)\)

发现 \(m\) 的上界仍然很松,考虑继续优化。

假设原图不存在两个本质不同,长度相同的环,考虑随机删去原图中 \(\sqrt{n}\) 条边,那么一个长度为 \(L\) 的环存在的概率为 \((1-\tfrac{1}{\sqrt{n}})^L\) ,那么最终环的个数的期望最大为 \(\sum_{i=3}^{n}(1-\tfrac{1}{\sqrt{n}})^L\le \sqrt{n}\) 。然而当 \(m>n+2\sqrt{n}\) 时,任意删去 \(\sqrt{n}\) 条边后对原图建立 Dfs 树一定存在 \(\sqrt{n}\) 条返祖边,因此最终环的个数的期望一定大于 \(\sqrt{n}\)

这样可以得到一个较紧的界 \(m\le n+2\sqrt{n}\) ,由于环至少包含一条返祖边,因此可以将所有返祖边涉及到的点单独取出后建立虚树,容易发现虚树大小不超过 \(O(\sqrt{n})\) ,因此压缩后的环长度不超过 \(O(\sqrt{n})\) ,同时 Check 的复杂度不超过 \(O(\sqrt{n})\) ,因此总复杂度为 \(O(n^2)\)

code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
#include <utility>
#include <iostream>
using namespace std;
const int max1 = 2e4;

int n, m;
vector <int> edge[max1 + 5], tree[max1 + 5];
vector < pair <int, int> > new_edge[max1 + 5];
vector <int> id[max1 + 5];
bool choose[max1 + 5]; int total;

int belong[max1 + 5], cnt, deep[max1 + 5];
int siz[max1 + 5], father[max1 + 5], son[max1 + 5];
int top[max1 + 5], dfn[max1 + 5], rk[max1 + 5], dfs_clock;
int point[max1 + 5], len, s[max1 + 5], stop;

int bin[max1 + 5];
bool vis[max1 + 5], able[max1 + 5];

void YES ()
{
    printf("Yes\n");
    exit(0);
}

void Dfs ( int now, int fa, int id )
{
    belong[now] = id;
    deep[now] = deep[fa] + 1;
    for ( auto v : edge[now] )
    {
        if ( v == fa )
            continue;

        if ( !deep[v] )
        {
            Dfs(v, now, id);
            tree[now].push_back(v);
        }
        else
        {
            if ( deep[v] < deep[now] )
            {
                point[++len] = v;
                point[++len] = now;
            }
        }
    }
    return;
}

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

    int max_siz = 0;
    for ( auto v : tree[now] )
    {
        Find_Heavy_Edge(v, now, 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 : tree[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;
}

void Check ( int now )
{
    able[now] = true;
    int num = 0;
    for ( auto v : new_edge[now] )
    {
        if ( !choose[id[now][num]] && !vis[v.first] && !able[v.first] )
            Check(v.first);
        ++num;
    }
    return;
}

bool Check ( int root, int target )
{
    vis[root] = vis[target] = false;
    for ( int i = 1; i <= len; i ++ )
        able[point[i]] = false;
    Check(root);
    vis[root] = true; vis[target] = false;
    return able[target];
}

void Solve ( int now, int root, int dis )
{
    if ( now != root && !Check(root, now) )
        return;

    vis[now] = true;

    int num = 0;
    for ( auto p : new_edge[now] )
    {
        int v = p.first, w = p.second;

        if ( !choose[id[now][num]] )
        {
            if ( !vis[v] )
            {
                choose[id[now][num]] = true;
                Solve(v, root, dis + w);
                choose[id[now][num]] = false;
            }
            else
            {
                if ( v == root )
                {
                    bin[dis + w] += w;
                    if ( bin[dis + w] > dis + dis + w + w )
                        YES();
                }
            }
        }
        ++num;
    }
    vis[now] = false;
    return;
}

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

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

    if ( m > n + sqrt(n) + sqrt(n) )
        YES();

    for ( int i = 1; i <= n; i ++ )
    {
        if ( !belong[i] )
        {
            total = len = dfs_clock = 0; ++cnt; Dfs(i, 0, cnt); point[++len] = i;

            Find_Heavy_Edge(i, 0, 0); Connect_Heavy_Edge(i, i);
            
            sort(point + 1, point + 1 + len, [&]( const int &x, const int &y ) { return dfn[x] < dfn[y]; });
            len = unique(point + 1, point + 1 + len) - ( point + 1 );

            int num = len;
            for ( int k = 1; k <= num - 1; k ++ )
                point[++len] = Get_Lca(point[k], point[k + 1]);
            
            sort(point + 1, point + 1 + len, [&]( const int &x, const int &y ) { return dfn[x] < dfn[y]; });
            len = unique(point + 1, point + 1 + len) - ( point + 1 );

            stop = 0;
            for ( int k = 1; k <= len; k ++ )
            {
                while ( stop && Get_Lca(point[s[stop]], point[k]) != point[s[stop]] )
                    --stop;
                
                if ( stop )
                {
                    int dis = deep[point[k]] - deep[point[s[stop]]];
                    new_edge[point[s[stop]]].push_back(make_pair(point[k], dis));
                    new_edge[point[k]].push_back(make_pair(point[s[stop]], dis));
                    id[point[s[stop]]].push_back(++total);
                    id[point[k]].push_back(total);
                    choose[total] = false;
                }
                s[++stop] = k;
            }

            for ( int k = 1; k <= len; k ++ )
            {
                int now = point[k];
                for ( auto v : edge[now] )
                {
                    if ( deep[v] < deep[now] - 1 )
                    {
                        new_edge[v].push_back(make_pair(now, 1));
                        new_edge[now].push_back(make_pair(v, 1));
                        id[v].push_back(++total);
                        id[now].push_back(total);
                        choose[total] = false;
                    }
                }
            }

            for ( int k = 1; k <= len; k ++ )
            {
                for ( int w = 1; w <= len; w ++ )
                    vis[point[w]] = false;

                Solve(point[k], point[k], 0);
            }
        }
    }

    printf("No\n");
    return 0;
}

T3 下大雨

原先考过,然而没有改。

由于雨滴一定向下落,因此这 \(n\) 个雨棚实际上存在一定的拓扑关系,可以使用 set + 扫描线找到这个拓扑关系。

考虑 dp ,设 \(f_i\) 表示雨滴下落到 \(i\) 时最少需要打几个洞,设当前考虑的雨棚为 \(x\) ,如果当前雨棚左低右高,那么转移是首先对当前雨棚涉及到的区间做后缀 \(\min\) ,然后进行区间 \(+1\) 的操作,如果当前雨棚左高右低,就是区间做前缀 \(\min\) ,然后进行区间 \(+1\)

考虑使用 map 维护差分实现上述操作,维护两个 map ,一个维护正数,另一个维护负数,区间 \(+1\) 直接简单插入即可,考虑区间前缀 \(\min\) ,这相当于将当前区间内所有正数差分全部推平,那么在正数差分的 map 中二分,不断删去正数差分,考虑到之后的 dp 值尚未改变,因此删去差分时需要简单修改后面第一个的差分,后缀 \(\min\) 同理。

势能分析发现复杂度为 \(O(n\log n)\)

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

const int max1 = 5e5;
const int inf = 0x3f3f3f3f;

int n, L, R;
struct Segment
{
    int x1, x2, y1, y2;
}seg[max1 + 5];

struct Set_Segment
{
    int id;

    Set_Segment () {}
    Set_Segment ( int __id )
        { id = __id; }

    bool operator < ( const Set_Segment &A ) const
    {
        int x = max(seg[id].x1, seg[A.id].x1);

        return (__int128_t) ( ( 1LL * seg[id].x1 * seg[id].y2 - 1LL * seg[id].x2 * seg[id].y1 ) + 1LL * ( seg[id].y1 - seg[id].y2 ) * x ) * ( seg[A.id].x1 - seg[A.id].x2 ) < 
               (__int128_t) ( ( 1LL * seg[A.id].x1 * seg[A.id].y2 - 1LL * seg[A.id].x2 * seg[A.id].y1 ) + 1LL * ( seg[A.id].y1 - seg[A.id].y2 ) * x ) * ( seg[id].x1 - seg[id].x2 );
    }
};

struct Point
{
    int pos, v;

    bool operator < ( const Point &A ) const
    {
        if ( pos == A.pos )
            return v > A.v;

        return pos < A.pos;
    }
};

Point p[max1 * 2 + 5]; set <Set_Segment> Set;
vector <int> edge[max1 + 5]; int deg[max1 + 5];
queue <int> que;
map <int, int> f, g, ans;

void Insert ( int pos, int v )
{
    if ( f.find(pos) != f.end() )
        v += f[pos], f.erase(pos);
    if ( g.find(pos) != g.end() )
        v += g[pos], g.erase(pos);

    if ( v > 0 )
        f.insert(make_pair(pos, v));
    if ( v < 0 )
        g.insert(make_pair(pos, v));
    return;
}

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

    scanf("%d%d%d", &L, &R, &n); L <<= 1, R <<= 1;
    for ( int i = 1; i <= n; i ++ )
    {
        scanf("%d%d%d%d", &seg[i].x1, &seg[i].y1, &seg[i].x2, &seg[i].y2);
        seg[i].x1 <<= 1, seg[i].x2 <<= 1, seg[i].y1 <<= 1, seg[i].y2 <<= 1;
        if ( seg[i].x1 > seg[i].x2 )
            swap(seg[i].x1, seg[i].x2), swap(seg[i].y1, seg[i].y2);

        p[i].pos = seg[i].x1, p[i].v = i;
        p[n + i].pos = seg[i].x2, p[n + i].v = -i;
    }

    sort(p + 1, p + 1 + n + n);

    for ( int i = 1; i <= n + n; i ++ )
    {
        int v = p[i].v;
        if ( v > 0 )
        {
            if ( !Set.empty() )
            {
                if ( *Set.begin() < Set_Segment(v) )
                {
                    int tmp = (*--Set.lower_bound(Set_Segment(v))).id;
                    edge[v].push_back(tmp);
                    ++deg[tmp];
                }
                if ( Set_Segment(v) < *--Set.end() )
                {
                    int tmp = (*Set.upper_bound(Set_Segment(v))).id;
                    edge[tmp].push_back(v);
                    ++deg[v];
                }
            }
            Set.insert(Set_Segment(v));
        }
        else
        {
            v = -v;
            Set.erase(Set_Segment(v));
        }
    }

    Insert(-2e9 - 5, inf);
    Insert(L, -inf);
    Insert(R + 1, inf);
    Insert(2e9 + 5, -inf);
    f[-2e9 - 5], g[-2e9 - 5], f[2e9 + 5], g[2e9 + 5];

    for ( int i = 1; i <= n; i ++ )
        if ( !deg[i] )
            que.push(i);

    while ( !que.empty() )
    {
        int x = que.front(); que.pop();
        for ( auto v : edge[x] )
        {
            --deg[v];
            if ( !deg[v] )
                que.push(v);
        }

        if ( seg[x].y1 < seg[x].y2 )
        {
            while ( true )
            {
                pair <int, int> pos = *--g.upper_bound(seg[x].x2);

                if ( pos.first < seg[x].x1 + 1 )
                    break;
                
                g.erase(pos.first);
                int pre = max(seg[x].x1, max((--f.lower_bound(pos.first)) -> first, (--g.lower_bound(pos.first)) -> first));
                Insert(pre, pos.second);
            }

            Insert(seg[x].x1 + 1, 1); Insert(seg[x].x2 + 1, -1);
        }
        else
        {
            while ( true )
            {
                pair <int, int> pos = *f.lower_bound(seg[x].x1 + 1);

                if ( pos.first > seg[x].x2 )
                    break;
                
                f.erase(pos.first);

                int nxt = min(seg[x].x2 + 1, min(f.upper_bound(pos.first) -> first, g.upper_bound(pos.first) -> first));
                Insert(nxt, pos.second);
            }

            Insert(seg[x].x1, 1), Insert(seg[x].x2, -1);
        }
    }

    for ( auto v : f )
        ans[v.first] += v.second;
    for ( auto v : g )
        ans[v.first] += v.second;
    
    ans[L], ans[R];
    
    int sum = 0, Min = inf;
    for ( auto v : ans )
    {
        sum += v.second;

        if ( v.first >= L && v.first <= R )
            Min = min(Min, sum);
    }
    
    printf("%d\n", Min);

    return 0;
}
posted @ 2023-06-13 10:41  KafuuChinocpp  阅读(25)  评论(0)    收藏  举报