@description@

给定一个矩阵。求它的所有子矩阵中本质不同的行的个数之和。

input

第一行,两个正整数 n, m。

第二行,n * m 个正整数,第 i 个数表示 A[i/m][i mod m]。

保证 n * m <= 10^5, 1 <= A[i][j] <= 10^9

output

输出一个非负整数表示答案。

sample input

2 2

1 1 1 2

sample output

11

@solution@

假如我们枚举矩阵的左右边界,从上往下扫描行。

假如第 i 行上一个与它相同的行在第 j 行,则它对答案的贡献,即只考虑它这一行(因为包含其他与第 i 行相同的行已经被统计过了)的子矩阵数量,等于它到下边界的距离*它到第 j 行的距离。

我们可以只枚举左边界,再把每一行插入 trie 里面。这样我们就可以不用特意去枚举右边界(因为插入进 trie 的时候就可以顺便统计出每一列作为右边界的贡献),就可以省去繁杂的字符串比较匹配,简化时间复杂度。

其实是因为右边界移动时有些之前的信息可以被保留下来。

再细细品味,可以发现左边界移动时有些信息也可以保留下来。

具体的操作而言,可以是先固定左边界在第一列,往右移动时将根的所有子树合并成一棵 trie,同时动态维护出答案。

具体到算法细节,我们在每个结点中维护一个 set 表示包含这个结点所表示的字符串的行集合,再维护一个 val 表示这个结点对答案的贡献。

如果向右移动左边界,先减去根的所有儿子对答案的贡献 val,然后随便选中根的某一个子树,将其他的子树向它合并。

如果两个子树 A 要向 B 合并,首先要将 A, B 根结点合并成一个结点。对于 A 根结点的某一个儿子,如果 B 没有则 B 根结点接指针到这个儿子;否则再递归合并 A, B 的这一棵子树。

如果两个结点 p 和 q 合并,其实最主要的是 p 和 q 的 set 合并,我们采用启发式合并的方法(小的往大的合)。枚举 p 中的 set 中的每一个行,将这个行插入 q 中的 set,同时求出只包含这一行的子矩阵个数,即在它上面且离它最近的行到它的距离 * 在它下面且离它最近的行到它的距离。

时间复杂度看似很高,实际上总结点数 = 结点大小 = n*m,每次结点合并都会至少减少一个结点,每次子树合并实际上只有结点合并时才会遍历这个结点。而结点的合并只会合并同一深度的结点,同一深度的 set 大小之和刚好等于行数 n,又因为我们采用的是启发式合并,所以每个值最多被合并 log 次。加上 set 的维护是 log 级别的。

所以时间复杂度 O(nlog^2n)(这个 n 是矩阵大小 10^5)。

话说我感觉本题好像不需要子树的启发式合并……

@accepted code@

常数很大,本地测试过不了全部数据。可能是 STL 用得太猛了。

#include<set>
#include<map>
#include<cstdio>
#include<algorithm>
using namespace std;
struct node;
typedef set<int> Set;
typedef set<int>::iterator set_it;
typedef map<int, node*>::iterator map_it;
typedef long long ll;
const int MAXN = 500000;
Set pl1[MAXN + 5], *cnt1;
struct node{
map<int, node*>ch;
Set *s; ll val;
}pl2[MAXN + 5], *root, *cnt2, *nw;
ll nwtot; int n, m, x;
void init() {
cnt1 = &pl1[0], cnt2 = &pl2[0];
root = nw = cnt2;
nwtot = 0;
}
node *newnode() {
cnt2++, cnt2->s = (++cnt1), cnt2->s->insert(0), cnt2->s->insert(n+1);
return cnt2;
}
void insert(int id, int x) {
if( !nw->ch.count(x) ) nw->ch[x] = newnode();
nw = nw->ch[x];
set_it it1 = nw->s->lower_bound(id), it2 = it1; it1--;
ll del = 1LL*(id - (*it1))*((*it2) - id);
nw->val += del, nwtot += del;
nw->s->insert(id);
}
void node_merge(node *a, node *b) {
for(map_it it=a->ch.begin();it!=a->ch.end();it++) {
if( b->ch.count(it->first) ) {
node *tmp = b->ch[it->first];
if( tmp->s->size() < it->second->s->size() ) {
swap(tmp->s, it->second->s);
swap(tmp->val, it->second->val);
}
nwtot -= it->second->val;
for(set_it it2=it->second->s->begin();it2!=it->second->s->end();it2++) {
if( !(*it2) || (*it2) == n+1 ) continue;
set_it it3=tmp->s->lower_bound(*it2), it4 = it3; it3--;
ll del = 1LL*((*it2) - (*it3))*((*it4) - (*it2));
nwtot += del, tmp->val += del;
tmp->s->insert(*it2);
}
node_merge(it->second, tmp);
}
else b->ch[it->first] = it->second;
}
}
void trie_merge() {
node *rt = root->ch.begin()->second;
for(map_it it=root->ch.begin();it!=root->ch.end();it++) {
nwtot -= it->second->val;
if( it != root->ch.begin() )
node_merge(it->second, rt);
}
root = rt;
}
inline int read() {
int x = 0; char ch = getchar();
while( ch > '9' || ch < '0' ) ch = getchar();
while( '0' <= ch && ch <= '9' ) x = 10*x + ch-'0', ch = getchar();
return x;
}
int main() {
init(); n = read(), m = read();
for(int i=0;i<n*m;i++) {
if( i % m == 0 ) nw = root;
x = read(); insert(i/m + 1, x);
}
ll ans = nwtot;
for(int i=1;i<m;i++)
trie_merge(), ans += nwtot;
printf("%lld\n", ans);
}

@details@

trie 还能合并,是真的没想到。

话说我即使不加启发式合并也能跑得很快。随机化合并大法好啊。

STL 多起来的确很容易让人昏昏沉沉的,而且还不好调试。

@雅礼集训01/10 - T1@ matrix的更多相关文章

  1. @雅礼集训01/13 - T1@ union

    目录 @description@ @solution@ @part - 1@ @part - 2@ @part - 3@ @accepted code@ @details@ @description@ ...

  2. @雅礼集训01/06 - T3@ math

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给出 n, m, x,你需要求出下列式子的值: \[\sum_{ ...

  3. 雅礼集训DAY 6 T1 xmasdag

    感谢gryz的mly大好人再次给我提供了题目和数据. 和昨晚那个题几乎一样,都是x^n最后转化成第二类斯特林数*阶乘*Σ(和路径长度有关的组合数),而因为组合数是可以利用Pascal公式实现O(1)递 ...

  4. 【loj6034】「雅礼集训 2017 Day2」线段游戏

    #6034. 「雅礼集训 2017 Day2」线段游戏 内存限制:256 MiB 时间限制:1000 ms 标准输入输出 题目类型:传统 评测方式:Special Judge 上传者: 匿名 题目描述 ...

  5. 雅礼集训1-9day爆零记

    雅礼集训1-9day爆零记 先膜一下虐爆我的JEFF巨佬 Day0 我也不知道我要去干嘛,就不想搞文化科 (文化太辣鸡了.jpg) 听李总说可以去看(羡慕)各路大佬谈笑风声,我就报一个名吧,没想到还真 ...

  6. 「雅礼集训 2017 Day2」解题报告

    「雅礼集训 2017 Day2」水箱 我怎么知道这种题目都能构造树形结构. 根据高度构造一棵树,在树上倍增找到最大的小于约束条件高度的隔板,开一个 \(vector\) 记录一下,然后对于每个 \(v ...

  7. 「雅礼集训 2017 Day1」 解题报告

    「雅礼集训 2017 Day1」市场 挺神仙的一题.涉及区间加.区间除.区间最小值和区间和.虽然标算就是暴力,但是复杂度是有保证的. 我们知道如果线段树上的一个结点,\(max=min\) 或者 \( ...

  8. [LOJ 6031]「雅礼集训 2017 Day1」字符串

    [LOJ 6031] 「雅礼集训 2017 Day1」字符串 题意 给定一个长度为 \(n\) 的字符串 \(s\), \(m\) 对 \((l_i,r_i)\), 回答 \(q\) 个询问. 每个询 ...

  9. [LOJ 6030]「雅礼集训 2017 Day1」矩阵

    [LOJ 6030] 「雅礼集训 2017 Day1」矩阵 题意 给定一个 \(n\times n\) 的 01 矩阵, 每次操作可以将一行转置后赋值给某一列, 问最少几次操作能让矩阵全为 1. 无解 ...

随机推荐

  1. Jeecms网站直接访问html静态页面

    jeecms网站维护,遇到了直接通过链接的方式访问静态页面,jeecms官网也做了详细的解答,但是没有得到满意的结果.但是通过自己的深入研究以及别人的帮助,发现了一个很好的解决方法. 首先说明一下je ...

  2. neo4j中对节点关系和聚类的思考

    由于neo4j在查找过程中具有事务,所以查询的速度非常慢!给出的建议如下: 一,将所有查询放在一个Session中,当所有查询完毕以后在关闭Driver和Session: 二,使用neo4j连接池,使 ...

  3. 依赖注入的方式(DI)

    方式: 接口注入: setter方法注入: 构造方法注入: 接口注入: public class ClassA { private InterfaceB clzB; public void doSom ...

  4. javascript之键盘事件的方法

    键盘事件包含onkeydown.onkeypress和onkeyup这三个事件 事件初始化 function keyDown(){} document.onkeydown = keyDown; //论 ...

  5. SaaS launch Kit成回收宝和友盟云合作纽带,帮助提升3倍上云效率

    导语:叶飞表示,全球二手手机市场未来几年将发生巨大变革, 回收宝正进行积极布局.与阿里云开展紧密技术合作,回收宝期待成为这一变革的引领者. 7月26日,在阿里云上海峰会上,阿里云了发布SaaS生态战略 ...

  6. Spring Boot 数据库连接池参数

    挑战A.I.,赢百万奖金......了解更多详情>>> Tomcat JDBC 连接池 Spring Boot 默认选择 Tomcat JDBC Pool 作为数据库连接池.Tomc ...

  7. Spring Boot:Boot2.0版本整合Neo4j

    前面介绍了Boot 1.5版本集成Neo4j,Boot 2.0以上版本Neo4j变化较大. 场景还是电影人员关系 Boot 2.0主要变化 GraphRepository在Boot2.0下不支持了,调 ...

  8. npm上面实用的第三方工具包

    npm上面实用的第三方工具包 live-server 作用:为页面提供实时刷新重载的功能,并且能提供一个http服务器 官方地址:https://www.npmjs.com/package/live- ...

  9. Dalvik 虚拟机和 Sun JVM 在架构和执行方面有什么本质区别?

    目前我理解的是: 两者共同点: 都是解释执行 byte code 都是每个 OS 进程运行一个 VM,并执行一个单独的程序 在较新版本中(Froyo / Sun JDK 1.5)都实现了相当程度的 J ...

  10. Directx11教程(67) 显示模型文件

    原文:Directx11教程(67) 显示模型文件       在前面的教程中,我们都是通过在ModelClass中直接产生顶点和索引数据,简单的三角形,立方体等等还好说,毕竟比较简单,如何显示复杂的 ...