当前位置:首页 > 技术 > 正文内容

POI 2015 精选算法题解析

访客 技术 2026年10月25日 1

矩阵倍增与状态扩展

针对图中路径计数问题,若所有边的权重统一为单位长度,计算特定步数内的可达性通常直接应用矩阵乘法结合二分加速。为了处理非单位权重的特殊情况,可以通过构建虚拟汇点并引入自环来标准化图结构。在此类权重范围为 [1, 3] 的场景中,传统的倍增法需要进行节点分裂(State Splitting)。将原始节点 $u$ 扩展为三个状态子节点 $(u,0), (u,1), (u,2)$,分别代表当前距离模 3 后的余数偏移量。每条实际边 $(u, v)$ 权重为 $w$ 时,转化为从 $(u, w \pmod 3)$ 指向 $(v, 0)$ 的映射关系。

通过构建增广邻接矩阵并利用二进制拆分思想进行预计算,可以高效求解满足路径总数阈值的最小步数。具体实现中定义了矩阵乘法运算及状态判定函数,利用滚动数组更新中间结果。

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;

// 定义矩阵结构体
struct Matrix {
    ll mat[150][150];
    Matrix() {
        for(int i=0; i<150; ++i)
            for(int j=0; j<150; ++j)
                mat[i][j] = 0;
    }
};

int N, M;
ll K;
Matrix base_pow[70];
Matrix mul(const Matrix& A, const Matrix& B) {
    Matrix C;
    for(int i=1; i<=N*3; ++i) {
        for(int j=1; j<=N*3; ++j) {
            for(int k=1; k<=N*3; ++k) {
                C.mat[i][j] += A.mat[i][k] * B.mat[k][j];
            }
        }
    }
    return C;
}

bool check(Matrix& A) {
    ll total = 0;
    for(int i=1; i<=N; ++i) {
        // 检查是否满足达到目标次数的条件
        if(total >= K - (A.mat[i][0] - 1)) return true;
        total += A.mat[i][0] - 1;
    }
    return false;
}

inline int read_int() {
    int x=0, f=1; char c=getchar();
    while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
    while(c>='0' && c<='9') {x=x*10+(c-'0');c=getchar();}
    return x*f;
}

int main() {
    N = read_int(); M = read_int(); scanf("%lld", &K);
    
    // 初始化基础矩阵
    for(int i=1; i<=N; ++i) {
        base_pow[0].mat[0][i+n] = 0; // Placeholder for virtual node mapping logic
        // Setup self-loops for expanded nodes
        base_pow[0].mat[i+n][i+n*2+1] = 1; 
        base_pow[0].mat[i+n*2][i+n*2] = 1;
    }
    // Note: Specific mapping logic adapted to simplified structure
    
    for(int i=1; i<=M; ++i) {
        int u = read_int(), v = read_int(), w = read_int();
        base_pow[0].mat[u+(w-1)*N][v]++;
    }

    // Binary lifting preparation
    bool found = false;
    int pos = -1;
    for(int i=1; i<=63; ++i) {
        base_pow[i] = mul(base_pow[i-1], base_pow[i-1]);
        if(check(base_pow[i])) {
            pos = i;
            found = true;
            break;
        }
    }

    if(!found) { puts("-1"); return 0; }
    
    ll ans = 0;
    Matrix res;
    // Identity initialization
    for(int i=1; i<=N; ++i) res.mat[i][i] = 1;
    
    for(int i=pos-1; i>=0; --i) {
        Matrix next_st = mul(res, base_pow[i]);
        if(!check(next_st)) {
            res = next_st;
            ans += (1LL << i);
        }
    }
    printf("%lld\n", ans);
    return 0;
}

区间动态规划优化

对于涉及区间赋值最大收益的问题,可以使用区间 DP 建模。定义状态 $dp_{l,r,val}$ 表示在闭区间 $[l,r]$ 内取到的最小值恰好为 $val$ 时的最大获利。首先需要对输入的成本参数进行坐标离散化,以便压缩状态空间。

转移方程的核心在于枚举区间最小值的所在位置 $pos$,并将原问题分解为左右两个子区间 $[l, pos-1]$ 和 $[pos+1, r]$。由于子问题需要满足最小值不小于当前层级,可以引入辅助状态 $f_{l,r,i}$ 来表示 $dp_{l,r,j}$ 在 $j \ge i$ 时的最大值,从而避免重复枚举,将复杂度从三维降低。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;

const int MAXN = 10010;
const int MAXM = 4100;

struct Constraint {
    int l, r, cost_idx;
} constraints[MAXM];

int n, m, val_cnt;
int raw_vals[MAXM];
int best_profit[MAXN][MAXN][MAXM];
int suffix_max[MAXN][MAXN][MAXM];
int decision_point[MAXN][MAXN][MAXM];
int final_assignment[MAXN];

int fast_read() {
    int x=0, sgn=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')sgn=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+(ch-'0');ch=getchar();}
    return x*sgn;
}

void reconstruct_dp(int l, int r, int k_idx) {
    if(l > r) return;
    if(suffix_max[l][r][k_idx] == 0) {
        for(int i=l; i<=r; ++i) final_assignment[i] = raw_vals[k_idx];
        return;
    }
    for(int idx=k_idx; idx<=val_cnt; ++idx) {
        if(best_profit[l][r][idx] == suffix_max[l][r][k_idx]) {
            int pivot = decision_point[l][r][idx];
            final_assignment[pivot] = raw_vals[idx];
            reconstruct_dp(l, pivot-1, idx);
            reconstruct_dp(pivot+1, r, idx);
            break;
        }
    }
}

int main() {
    n = fast_read(); m = fast_read();
    vector<int> sorted_unique;
    sorted_unique.reserve(m);

    for(int i=1; i<=m; ++i) {
        constraints[i].l = fast_read();
        constraints[i].r = fast_read();
        constraints[i].cost_idx = fast_read();
        sorted_unique.push_back(constraints[i].cost_idx);
    }
    sort(sorted_unique.begin(), sorted_unique.end());
    val_cnt = unique(sorted_unique.begin(), sorted_unique.end()) - sorted_unique.begin();
    
    // Map costs to rank
    for(int i=1; i<=m; ++i) {
        constraints[i].cost_idx = lower_bound(sorted_unique.begin(), sorted_unique.end(), constraints[i].cost_idx) - sorted_unique.begin();
    }

    // Precompute counts for efficiency
    int freq[MAXN][MAXM];
    memset(freq, 0, sizeof(freq));

    for(int len_l=n; len_l>=1; --len_l) {
        for(int len_r=len_l; len_r<=n; ++len_r) {
            // Update local counts g[i][j]
            // ... (Logic compressed for clarity)
            
            for(int i=len_l; i<=len_r; ++i) {
                for(int j=val_cnt; j>=1; --j) {
                    int gain = suffix_max[len_l][i-1][j] + suffix_max[i+1][len_r][j];
                    // Logic dependent on coverage count g[i][j]
                    int potential = gain + /*calc_coverage_gain*/(i, j, constraints, n);
                    if(potential > best_profit[len_l][len_r][j]) {
                        best_profit[len_l][len_r][j] = potential;
                        decision_point[len_l][len_r][j] = i;
                    }
                    suffix_max[len_l][len_r][j] = max(suffix_max[len_l][len_r][j+1], best_profit[len_l][len_r][j]);
                }
            }
        }
    }
    
    reconstruct_dp(1, n, 0);
    printf("%d\n", suffix_max[1][n][0]);
    for(int i=1; i<=n; ++i) printf("%d ", final_assignment[i]);
    return 0;
}

基于线段树的动态最大子段和

此类动态维护问题可转化为带修改的最大子段和查询。核心思想是利用前缀和处理元素贡献:当某个数值首次出现在序列中时,其对应位置贡献为正权值;第二次出现时,为避免重复计算,贡献为负权值以抵消之前的记录;第三次及以上则不再影响最大子段和的计算。

我们需要维护一个线段树,每个节点存储当前区间的总和、从左端点延伸的最大值、从右端点延伸的最大值以及该区间内的全局最大子段和。每次移动左边界或删除元素时,使用单点修改操作动态调整相关位置的权值。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;

struct NodeInfo {
    ll total_sum;
    ll left_max;
    ll right_max;
    ll max_subsegment;
} tree_data[4004005];

int arr_vals[1001005];
int occurrences_count[1001005];
int positions_of_val[1001005][2]; // Store up to first two positions
vector<int> value_positions[1001005];
int n, q, vals_limit;

void push_up(int node) {
    tree_data[node].total_sum = tree_data[node*2].total_sum + tree_data[node*2+1].total_sum;
    tree_data[node].left_max = max(tree_data[node*2].left_max, tree_data[node*2].total_sum + tree_data[node*2+1].left_max);
    tree_data[node].right_max = max(tree_data[node*2+1].right_max, tree_data[node*2].right_max + tree_data[node*2+1].total_sum);
    tree_data[node].max_subsegment = max({tree_data[node*2].max_subsegment, tree_data[node*2+1].max_subsegment, tree_data[node*2].right_max + tree_data[node*2+1].left_max});
}

void update_point(int node, int l, int r, int idx, ll val) {
    if(l == r) {
        tree_data[node] = {val, val, val, val};
        return;
    }
    int mid = (l + r) >> 1;
    if(idx <= mid) update_point(node*2, l, mid, idx, val);
    else update_point(node*2+1, mid+1, r, idx, val);
    push_up(node);
}

int main() {
    int rd_n = fast_read();
    int rd_q = fast_read();

    // Reading input and preprocessing positions
    // ... Initialization omitted for brevity
    // Assume 'arr_vals', 'positions_of_val', 'value_positions' filled correctly

    ll ans = 0;
    // Initial setup for max subarray calculation
    // ... Apply initial weights based on frequency logic
    
    for(int i=1; i<=rd_n; ++i) {
        ans = max(ans, tree_data[1].max_subsegment);
        
        // Reset current position weight to 0 as it moves out
        update_point(1, 1, rd_n, i, 0);
        
        occurrences_count[arr_vals[i]]++;
        int cnt = occurrences_count[arr_vals[i]];
        
        // Adjust weights for next appearances of the same value
        // If there is another instance later, restore its positive weight or set negative based on logic
        // Specific logic adapts to the constraint requirements
    }
    printf("%lld\n", ans);
    return 0;
}

双树状数组的条件求和

在处理具有大小约束的元素分配问题时,可以将大于阈值的独立数量与小于阈值的填充需求分开统计。记大于等于阈值 $s$ 的元素个数为 $count_{\ge s}$,剩余的空位需由较小元素填补。

若小元素的总和不足以填满剩余空位乘以单个容量的积,则任务无法完成。为了高效支持单点修改和区间查询,我们使用两棵独立的树状数组(BIT):第一棵统计指定范围内元素的总个数,第二棵维护这些元素的数值之和。

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;

struct QueryNode {
    int op_type, param1, param2;
};

struct BinaryIndexedTree {
    ll bit_map[2000005];
    int size_limit;

    void init(int n) {
        size_limit = n;
        fill(begin(bit_map), end(bit_map), 0);
    }

    void add_val(int index, ll delta) {
        for(; index <= size_limit; index += index & (-index)) {
            bit_map[index] += delta;
        }
    }

    ll query_prefix(int index) {
        ll result = 0;
        for(; index > 0; index -= index & (-index)) {
            result += bit_map[index];
        }
        return result;
    }
} count_tree, sum_tree;

ll fast_read_ll() {
    char ch=getchar(); ll x=0;
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+(ch-'0');ch=getchar();}
    return x;
}

int main() {
    int n = (int)fast_read_ll();
    int m = (int)fast_read_ll();
    char operation_str[5];
    
    vector<QueryNode> history;
    history.resize(m + 1);
    vector<int> distinct_values;
    distinct_values.reserve(m);

    for(int i=1; i<=m; ++i) {
        scanf("%s", operation_str);
        history[i].op_type = (operation_str[0]=='U'?0:1);
        history[i].param1 = (int)fast_read_ll();
        history[i].param2 = (int)fast_read_ll();
        distinct_values.push_back(history[i].param2);
    }

    sort(distinct_values.begin(), distinct_values.end());
    auto end_iter = unique(distinct_values.begin(), distinct_values.end());
    int unique_count = distance(distinct_values.begin(), end_iter);
    
    // Coordinate compression
    // ... Re-map history[i].param2 to ranks
    
    // Initialize trees
    // ...
    
    // Process queries
    // Logic: Calculate required space based on items >= threshold
    // Compare sum of items < threshold against requirement
    
    return 0;
}
标签: C++POI2015

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。