2023冲刺国赛模拟 19.1

T1 矩阵

正解做法是二维分块,然而直接上树状数组跑的飞快,不过我写的根号重构,加 Fastio 后可过。

code
#include <cstdio>
#include <algorithm>
#include <ctime>
#include <iostream>
using namespace std;

char buf[1 << 21], *p1, *p2;
inline char gc ()
{
    if ( __builtin_expect(p1 == p2, false) )
    {
        p1 = buf;
        p2 = buf + fread(buf, 1, 1 << 21, stdin);
        if ( p2 != (buf + ( 1 << 21 )) )
            *p2++ = EOF;
    }
    return *p1++;
}

inline int Read ()
{
    int x = 0; char c = gc();
    while ( c < '0' || c > '9' )
        c = gc();
    while ( c >= '0' && c <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + c - '0';
        c = gc();
    }
    return x;
}

char s[60]; int top;
inline void Write ( long long x )
{
    top = 0;
    do
    {
        s[++top] = x % 10 + '0';
        x /= 10;
    } while ( x );
    while ( top )
        putchar(s[top--]);
    putchar('\n');
    return;
}


const int max1 = 1000, lim = 100;

int n, m, q;
struct Point
{
    int x, y, w;
}p[lim + 5];
int len;
long long sum[max1 + 5][max1 + 5][2];

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

    int opt, x, y, z, a, b; long long ans;
    n = Read(), m = Read(), q = Read();
    while ( q -- )
    {
        opt = Read(), x = Read(), y = Read();
        if ( opt == 1 )
        {
            z = Read();
            sum[x][y][0] += z;
            p[++len].x = x, p[len].y = y, p[len].w = z;
            if ( len == lim )
            {
                len = 0;
                for ( int i = 1; i <= n; i ++ )
                    for ( int j = 1; j <= m; j ++ )
                        sum[i][j][1] = sum[i - 1][j][1] + sum[i][j - 1][1] - sum[i - 1][j - 1][1] + sum[i][j][0];
            }
        }
        else
        {
            a = Read(), b = Read();
            ans = sum[a][b][1] - sum[x - 1][b][1] - sum[a][y - 1][1] + sum[x - 1][y - 1][1];
            for ( int i = 1; i <= len; i ++ )
                if ( p[i].x >= x && p[i].x <= a && p[i].y >= y && p[i].y <= b )
                    ans += p[i].w;
            Write(ans);
        }
    }

    cerr << 1.0 * clock() / CLOCKS_PER_SEC << endl;

    return 0;
}

T2 种草

首先考虑 ai 全部相等的情况。

建立费用流模型,从 ii+1 连接流量为 ai ,费用为 0 的边( n+1 为汇点 T ),同时 S1 连边。

对于区间 [L,R,w]LR+1 建立流量为 1 ,费用为 w 的边。

仔细思考发现这可以限制每个点不会被超过 ai 个区间覆盖。

由于数据范围很大,因此考虑 Dijikstra 费用流,由于图初始为 DAG ,因此求解每个点的势能可以直接 DP ,复杂度可以做到 O(ainlogn)

考虑解决原问题,由于保证 a 序列单调不降,因此只需要在第一次到达值 x 的点 i 建立 Si 流量为 1 ,费用为 0 的边即可。

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

const int max1 = 1e5;
const long long inf = 0x3f3f3f3f3f3f3f3f;
int n, m;

struct Graph
{
    struct Node
    {
        int next, v, flow;
        long long cost;
    }edge[max1 * 5 + 5];
    int head[max1 + 5], total;
    long long h[max1 + 5];

    bool vis[max1 + 5];
    long long dis[max1 + 5];
    int flow[max1 + 5], pre_edge[max1 + 5], pre_point[max1 + 5];

    long long maxflow, maxcost;

    struct Point
    {
        int x; long long d;
        Point () {}
        Point ( int __x, long long __d )
            { x = __x, d = __d; }

        bool operator < ( const Point &A ) const
            { return d < A.d; }
    };

    priority_queue <Point> que;

    void Clear ()
    {
        memset(head, 0, sizeof(int) * ( n + 2 ));
        total = 1;
        return;
    }

    void Add ( int u, int v, int f, long long c )
    {
        edge[++total].v = v;
        edge[total].flow = f;
        edge[total].cost = c;
        edge[total].next = head[u];
        head[u] = total;
        return;
    }

    void Add_Edge ( int u, int v, int f, long long c )
    {
        return Add(u, v, f, c), Add(v, u, 0, -c);
    }

    void Build ()
    {
        for ( int i = 0; i <= n + 1; i ++ )
            h[i] = -inf;
        h[0] = 0;
        for ( int now = 0; now <= n; now ++ )
        {
            for ( int i = head[now]; i; i = edge[i].next )
            {
                int v = edge[i].v, f = edge[i].flow;
                long long c = edge[i].cost;
                
                if ( f )
                    h[v] = max(h[v], h[now] + c);
            }
        }
        return;
    }

    bool Dijikstra ()
    {
        for ( int i = 0; i <= n + 1; i ++ )
            dis[i] = -inf, vis[i] = false;
        
        dis[0] = 0, flow[0] = 0x3f3f3f3f, pre_edge[0] = 0, pre_point[0] = 0;
        while ( !que.empty() )
            que.pop();
        
        que.push(Point(0, 0));
        while ( !que.empty() )
        {
            int x = que.top().x;
            que.pop();
            if ( vis[x] )
                continue;
            
            vis[x] = true;
            for ( int i = head[x]; i; i = edge[i].next )
            {
                int v = edge[i].v, f = edge[i].flow;
                long long c = edge[i].cost + h[x] - h[v];
                if ( f && dis[v] < dis[x] + c )
                {
                    dis[v] = dis[x] + c;
                    flow[v] = min(flow[x], f);
                    pre_edge[v] = i;
                    pre_point[v] = x;
                    que.push(Point(v, dis[v]));
                }
            }
        }

        return vis[n + 1];
    }

    void Update ()
    {
        for ( int i = 0; i <= n + 1; i ++ )
            h[i] += dis[i];
        maxflow += flow[n + 1];
        maxcost += h[n + 1] * flow[n + 1];
        int now = n + 1;
        while ( now )
        {
            edge[pre_edge[now]].flow -= flow[n + 1];
            edge[pre_edge[now] ^ 1].flow += flow[n + 1];
            now = pre_point[now];
        }
        return;
    }

    void EK ()
    {
        Build(); maxflow = maxcost = 0;
        while ( Dijikstra() )
            Update();
        return;
    }
}Graph;

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

    scanf("%d%d", &n, &m); Graph.Clear();
    int now = 0;
    for ( int i = 1, x; i <= n; i ++ )
    {
        scanf("%d", &x);
        while ( now != x )
            ++now, Graph.Add_Edge(0, i, 1, 0);
        Graph.Add_Edge(i, i + 1, x, 0);
    }

    for ( int i = 1, L, R, w; i <= m; i ++ )
    {
        scanf("%d%d%d", &L, &R, &w);
        Graph.Add_Edge(L, R + 1, 1, w);
    }

    Graph.EK();

    printf("%lld\n", Graph.maxcost);

    return 0;
}

T3 基环树

考虑树的情况,一种很显然的 dp 思路是设 fi,0/1/2 表示当前考虑以 i 为根的子树,根节点形成了长度为 0/1/2 的链时的最大价值。

考虑 u 节点所有的儿子进行转移,容易发现长度为 0 和长度为 1 的链会配对,长度为 2 的链会连接形成一个长度为 3 的路径,长度为 1 的链会连接形成长度为 2 的路径,长度为 0 的链会连接形成一个长度为 1 的路径,因此把长度为 0 的链看做 1 ,长度为 1 的链看做 1 ,长度为 2 的链看做 0 ,这实际上是一个简单的背包 dp 。

直接做的复杂度为 O(n2) ,考虑对每个点的儿子随机 shuffle ,此时背包容量的大小期望为 O(n) ,也就是 n21n21 随机排列后前缀和最大值期望为 O(n) (显然我不会证明)。

考虑基环树,由于一次选择的路径最长为 3 ,考虑在环上选择 3 个点,由于这 3 个点所涉及的 4 条边一定不出自同一次选择,因此可以枚举选择不同的位置断环,直接按照树做即可。

code
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <ctime>
#include <random>
#include <cstring>
using namespace std;

const int max1 = 1e5, B = 300;
const long long inf = 0x3f3f3f3f3f3f3f3f;
mt19937 rnd((unsigned long long)new int);

int n;
struct Node
{
    int next, v, w;
}edge[max1 * 2 + 5];
int head[max1 + 5], total, del_edge, point;
int father[max1 + 5], circle[max1 + 5], len;
bool vis[max1 + 5], is_circle[max1 + 5];

struct Point
{
    int v, w;
}son[max1 + 5];
int cnt;

long long f[max1 + 5][3], g[max1 * 2 + 5], tmp[max1 * 2 + 5], ans;
void Add ( int u, int v, int w )
{
    edge[++total].v = v;
    edge[total].w = w;
    edge[total].next = head[u];
    head[u] = total;
    return;
}

bool Find_Circle ( int now, int fa )
{
    vis[now] = true; father[now] = fa;
    for ( int i = head[now]; i; i = edge[i].next )
    {
        int v = edge[i].v;
        if ( v == fa )
            continue;
        
        if ( !vis[v] )
        {
            if ( Find_Circle(v, now) )
                return true;
        }
        else
        {
            len = 0;
            int u = now;
            while ( u != v )
            {
                circle[++len] = u;
                is_circle[u] = true;
                u = father[u];
            }
            circle[++len] = u;
            is_circle[u] = true;
            return true;
        }
    }
    return false;
}

void Dfs ( int now, int fa )
{
    if ( !is_circle[now] && vis[now] )
        return;
    
    vis[now] = true;

    for ( int i = head[now]; i; i = edge[i].next )
    {
        int v = edge[i].v, w = edge[i].w;
        if ( i == del_edge || ( i ^ 1 ) == del_edge || v == fa )
            continue;

        Dfs(v, now);
    }

    cnt = 0;
    for ( int i = head[now]; i; i = edge[i].next )
    {
        int v = edge[i].v, w = edge[i].w;
        if ( i == del_edge || ( i ^ 1 ) == del_edge || v == fa )
            continue;
        son[++cnt].v = v;
        son[cnt].w = w;
    }

    f[now][0] = f[now][1] = f[now][2] = -inf;

    if ( cnt <= B )
    {
        int sum = 0;
        g[n] = 0; g[n - 1] = g[n + 1] = -inf;
        for ( int i = 1; i <= cnt; i ++ )
        {
            int v = son[i].v, w = son[i].w;
            
            for ( int k = -sum; k <= sum; k ++ )
                tmp[n + k] = g[n + k];
            for ( int k = -sum - 1; k <= sum + 1; k ++ )
                g[n + k] = -inf;
            
            for ( int k = -sum; k <= sum; k ++ )
            {
                g[n + k] = max(g[n + k], tmp[n + k] + max(f[v][0], f[v][2] + w));
                g[n + k + 1] = max(g[n + k + 1], tmp[n + k] + f[v][0] + w);
                g[n + k - 1] = max(g[n + k - 1], tmp[n + k] + f[v][1] + w);
            }
            ++sum;
        }

        if ( now == point )
        {
            int w = edge[del_edge].w;
            for ( int k = sum; k >= -sum; k -- )
                g[n + k + 1] = max(g[n + k + 1], g[n + k] + w);
        }

        f[now][0] = max(f[now][0], g[n]);
        f[now][1] = max(f[now][1], g[n + 1]);
        f[now][2] = max(f[now][2], g[n - 1]);
    }
    else
    {
        shuffle(son + 1, son + 1 + cnt, rnd);

        int sum = 0;
        g[n] = 0; g[n - 1] = g[n + 1] = -inf;
        for ( int i = 1; i <= cnt; i ++ )
        {
            int v = son[i].v, w = son[i].w;
            
            for ( int k = -sum; k <= sum; k ++ )
                tmp[n + k] = g[n + k];
            for ( int k = -sum - 1; k <= sum + 1; k ++ )
                g[n + k] = -inf;
            
            for ( int k = -sum; k <= sum; k ++ )
            {
                g[n + k] = max(g[n + k], tmp[n + k] + max(f[v][0], f[v][2] + w));
                g[n + k + 1] = max(g[n + k + 1], tmp[n + k] + f[v][0] + w);
                g[n + k - 1] = max(g[n + k - 1], tmp[n + k] + f[v][1] + w);
            }
            ++sum;
            sum = min(sum, B);
        }

        if ( now == point )
        {
            int w = edge[del_edge].w;
            for ( int k = sum; k >= -sum; k -- )
                g[n + k + 1] = max(g[n + k + 1], g[n + k] + w);
        }

        f[now][0] = max(f[now][0], g[n]);
        f[now][1] = max(f[now][1], g[n + 1]);
        f[now][2] = max(f[now][2], g[n - 1]);
    }

    return;
}

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

    scanf("%d", &n); total = 1;
    for ( int i = 1, u, v, w; i <= n; i ++ )
    {
        scanf("%d%d%d", &u, &v, &w);
        Add(u, v, w), Add(v, u, w);
    }

    Find_Circle(1, 0); ans = -inf;
    memset(vis, 0, sizeof(vis));

    for ( int i = 1; i <= 3; i ++ )
    {
        int pre = i - 1, suf = i + 1;
        pre = pre == 0 ? len : pre;
        suf = suf == len + 1 ? 1 : suf;
        int now = circle[i]; pre = circle[pre], suf = circle[suf];

        for ( int k = head[now]; k; k = edge[k].next )
        {
            int v = edge[k].v;
            if ( v == pre || v == suf )
            {
                del_edge = k;
                point = v;
                Dfs(now, 0);
                ans = max(ans, f[now][0]);

                if ( ( i == 1 && v == pre ) || ( i == 3 && v == suf ) )
                {
                    point = now;
                    Dfs(now, 0);
                    ans = max(ans, f[now][0]);
                }
            }
        }
    }

    printf("%lld\n", ans);
    return 0;
}

感觉自己题解写的越来越水了。

posted @   KafuuChinocpp  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示