Skip to content

泉州一中信息学Blog

信息学奥赛技术分享博客

  • 登录
  • 小学oj
  • 中学oj
  • 测试页面1
  • Toggle search form

HLOJ-TEST ROUND 2-T1

Posted on 2025年2月6日 By 张, 高畅 HLOJ-TEST ROUND 2-T1无评论

考虑到无法理解 T2,还是先把 T1 总结亿下吧。

原题链接

可能有关联的题目:NOI2016优秀的拆分,SCOI2016萌萌哒。

求最小代价毫无疑问类似最小生成树。

要想求最小生成树的边就要把所有的平方串找出来。

传统方法毫无疑问是 O(n^2) 的,O(n^2) 枚举端点,O(1) 哈希检验。

然而这样太慢了,而且还没有考虑后续怎么做最小生成树(森林)。

首先按 w_i 排序,类似 kruskal 的第一步。

考虑借鉴 [NOI2016优秀的拆分] 的方法,若当前枚举长度为 2 \cdot len 的平方串,在 i=len,2 \cdot len,3 \cdot len,4 \cdot len … 的地方设置特殊点。

对于相邻的两个特殊点 i 和 j=i+len ,用二分+hash 求出 LCP(最长公共前缀)的长度和 LCS(最长公共后缀)的长度。若两者相加是大于等于 len 的,说明 $\forall i \in[i-l{LCS}+1,i+l{LCP}-1],i与i+len$ 均可以连边。

分析上面的复杂度。枚举长度是 O(n) 的,枚举相邻两个特殊点看起来是 O(n) 但由于事实上是调和级数因此是带上枚举长度一共是 O(n \log n) 的。
二分 + hash 再带上一个 \log n ,因此上面总共的复杂度是 O (n \log^2 n) 的。

现在问题转化成给予两个区间 [l1,r1] 和 [l2,r2] (设 l1 具体地,集合[5,8]并入[1,4]时,par_{2,5}=1([1,4]的左端点很显然是1$)

我们给出合并时的代码:

void unite(int x,int y,int k)
{
    int fx = find(x,k),fy = find(y,k);
    if (fx == fy)return;
    par[fx][k] = fy;
    if(!k)return;
    unite(x, y, k - 1);
    unite(x + (1 << (k - 1)),y + (1 << (k - 1)), k - 1);
}

其中 k 表示层数。
合并当前层,之后分治的去递归下去。
在主函数的代码形如:

for(int k = 19; ~k; k --)
{
    if (l1 + (1 << k) - 1 <= r1)
    {
         unite(l1, l2, k);
         l1 += (1 << k);
         l2 += (1 << k);
    }
}

接下来分析总体复杂度。
看起来是嵌套的,其实不然。
学过最小生成树的我们知道 n 个节点的树只有 n-1 条边,因此合并最多仅会发生 O(n) 次。

每次合并是 \log n 的,因为树高就那么高。

因此最后总体复杂度取前面二分+hash的较高复杂度,为 O(n \log^2 n)

给出所有代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
typedef pair<int,int> PII;
typedef long long LL;
const LL B = 131, mod = 998244353;
int n, a[N], par[N][20];
LL power[N], h[N], res = 0;
PII w[N];
LL get(int l,int r){return ((h[r] - (h[l - 1] * power[r - l + 1] % mod)) % mod + mod) % mod;}
int find(int x,int k)
{
    if (par[x][k] == x)return x;
    else return par[x][k] = find(par[x][k],k);
}
void unite(int x,int y,int k,int cost)
{
    int fx = find(x,k),fy = find(y,k);
    if (fx == fy)return;
    par[fx][k] = fy;
    if(!k)
    {
        res += cost;
        return;
    }
    unite(x, y, k - 1, cost);
    unite(x + (1 << (k - 1)),y + (1 << (k - 1)), k - 1, cost);
}
int S(int x,int y,int dir)
{
    int l = 0, r;
    if(dir) r = min(n - x + 1, n - y + 1);
    else r = min(x, y);
    while (l < r)
    {
        int mid = (l + r + 1) >> 1;
        LL w1, w2;
        if (dir)
        {
            w1 = get(x, x + mid - 1);
            w2 = get(y, y + mid - 1);
        }
        else
        {
            w1 = get(x - mid + 1, x);
            w2 = get(y - mid + 1, y);
        }
        if(w1 == w2)l = mid;
        else r = mid - 1;
    }
    return l;
}
void work()
{
    res = 0;
    scanf("%d", &n);
    power[0] = 1;
    for (int i = 1; i <= n; i ++)
    {
        scanf("%d", &a[i]);
        power[i] = power[i - 1] * B % mod;
        h[i] = (h[i - 1] * B % mod + a[i]) % mod;
        for(int j = 0; j <= 19; j++)par[i][j] = i;
    }
    for (int i = 1; i <= n / 2; i ++)
    {
        scanf("%d", &w[i].first);
        w[i].second = i;
    }
    sort(w + 1, w + (n / 2) + 1);
    for (int pos = 1; pos <= n / 2; pos ++)// O (n)
    {
        int len = w[pos].second;
        for (int i = len; i + len <= n; i += len)
        {
            int x = i, y = i + len;
            if(a[x] != a[y]) continue;
            int LCP = S(x, y, 1), LCS = S(x, y, 0);
            if (LCP + LCS >= len + 1)// cover
            {
                int l1 = x - LCS + 1, r2 = y + LCP - 1;
                int l2 = l1 + len, r1 = r2 - len;
                // unite [i,i + len] for i in [l1,r2-len-1]
                for (int k = 19; ~k; k --)
                {
                    if(l1 + (1 << k) - 1 <= r1)
                    {
                        unite(l1, l2, k, w[pos].first);
                        l1 += (1 << k);
                        l2 += (1 << k);
                    }
                }
            }
        }
    }
    printf("%lld\n",res);
}
int main()
{
    freopen("endless.in", "r", stdin);
    freopen("endless.out", "w", stdout);
    int T;
    scanf("%d", &T);
    while(T --)work();
    return 0;
}

最后感谢 konata 将卡hash的两组数据删除了。

训练日志

文章导航

Previous Post: 寒假集训Day5(即将离开不再负责)
Next Post: HLOJ-TEST ROUND 4-T1/T2(构造)- 1

发表回复 取消回复

要发表评论,您必须先登录。

2025年 6月
一 二 三 四 五 六 日
 1
2345678
9101112131415
16171819202122
23242526272829
30  
« 2月    

2024常州 Class Classic OI Problems Contest cqr的长乐集训2023 CZYZ LOC New Game NOI NOIP Password Protected PM_PK Preview Problems Retrospect Selfmade Qusetion STL The end Training Uneasy Problem 蒟蒻 通报

  • 训练日志
  • 链表
  • 入门
  • 模拟
  • dfs序
  • 并查集
  • spfa
  • 最小割
  • 矩阵树定理
  • 仙人掌
  • BSGS
  • 凸包
  • 回文自动机
  • 递推与动归
  • 堆
  • 莫队算法
  • ST表
  • Treap
  • 树套树
  • 可持久化线段树
  • 初赛
  • 搜索
  • 贪心
  • 深度优先搜索
  • 欧拉图
  • dijkstra
  • 费用流
  • 哈夫曼树
  • kruskual
  • 置换
  • 旋转卡壳
  • KMP
  • 区间动归
  • STL
  • 链表
  • 可并堆
  • sply
  • 主席树
  • 可持久化字典树
  • 算法
  • 动态规划
  • 构造
  • 广度优先搜索
  • 最短路
  • floyd
  • 最大流
  • 虚树
  • prim
  • 筛法
  • 半平面交
  • 字典树
  • 背包动归
  • 基础数据结构
  • 分块
  • 线段树
  • 替罪羊树
  • K-DTree
  • 图论
  • 二分法
  • 迭代搜索
  • 拓扑排序
  • 有上下界网络流
  • 生成树
  • 快速幂
  • 后缀数组
  • 树形动归
  • 哈希表
  • 中级数据结构
  • 平衡树
  • 可持久化数据结构
  • 数据结构
  • 三分法
  • 启发式搜索
  • 图的连通
  • 点分治
  • 博弈论
  • AC自动机
  • 状压动归
  • 单调栈
  • 树状数组
  • 高级数据结构
  • OI资料
  • 数学
  • 高精度
  • 差分约束
  • 树上倍增
  • 素数测试
  • 后缀自动机
  • 数位动归
  • 单调队列
  • 新闻
  • 几何
  • 随机化
  • 二分图染色
  • 树链剖分
  • 欧拉函数
  • manacher
  • 斜率优化
  • 离线处理
  • 信息学奥赛学长风采
  • 字符串
  • 二分图匹配
  • prufer编码
  • 卡特兰数
  • 密码学
  • 决策单调
  • 赛后总结
  • 其他
  • 2-SAT
  • 最近公共祖先
  • 矩阵乘法
  • 记忆化搜索
  • 网络流
  • Link cut tree
  • 排列组合
  • 树
  • 高斯消元
  • 乘法逆元
  • 容斥原理
  • 调和级数
  • 概率与期望
  • 模线性方程组
  • 莫比乌斯反演
  • 快速傅里叶变换
  • 扩展欧几里德
  • 最大公约数与最小公倍数

近期文章

  • DP杂题
  • 2025年2月13日模拟赛
  • HLOJ-TEST ROUND 4-T1/T2(构造)- 3
  • HLOJ-TEST ROUND 4-T1/T2(构造)- 2
  • HLOJ-TEST ROUND 4-T1/T2(构造)- 1

近期评论

归档

  • 2025年2月
  • 2025年1月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年3月
  • 2024年2月
  • 2024年1月
  • 2023年12月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年3月
  • 2023年2月
  • 2023年1月
  • 2022年12月

Copyright © 2025 泉州一中信息学Blog.

Powered by PressBook WordPress theme