@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. python基础--常用的模块(collections、time、datetime、random、os、sys、json、pickle)

    collection模块: namedtuple:它是一个函数,是用来创建一个自定义的tuple对象的,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素.所以我们就可以 ...

  2. json原生解析

    身为新手,在运用网络解析json数据的时候,发现先会用Gson等框架解析json,然后就懒起来学原生解析了,这下在看别人写的demo的时候就尴尬了,一块块的,不懂写什么,气氛十分尴尬. 不多说,先来条 ...

  3. 光(mirror room)

    /* 光线只有遇上边界或堵塞的格子才会改变方向,所以改变方向的位置是有限的,光线的方向又最多只有四种,所以光线在循环之前改变方向的次数是O(n+m+k)级别的.我们可以模拟光线的移动.已知光线位置和光 ...

  4. 错觉-Info:视错觉与UI元素间的可能

    ylbtech-错觉-Info:视错觉与UI元素间的可能 1.返回顶部 1. 视觉原理在当下红火的机械视觉中是必不可少的,那在我们日常工作的UI产品设计中又有什么可能性的呢?今天,我从“视错觉”这个角 ...

  5. HTML 5+ SDK 更新日志

    http://ask.dcloud.net.cn/article/103 离线打包 SDK App 最新Andorid平台SDK下载新版本Android SDK使用aar方式发布,部分资源和jar包整 ...

  6. Freckles (最小生成树)

    #include<iostream> #include<cstring> #include<stdio.h> #include<queue> #incl ...

  7. CSS的盒子模型(Box Model)

    盒子模型(Box Model)是 CSS 的核心,现代 Web 布局设计简单说就是一堆盒子的排列与嵌套,掌握了盒子模型与它们的摆放控制,会发现再复杂的页面也不过如此. 然而,任何美好的事物都有缺憾,盒 ...

  8. Node.js模拟发起http请求从异步转同步的5种方法

    使用Node.js模拟发起http请求很常用的,但是由于Node模块(原生和第三方库)提供里面的方法都是异步,对于很多场景下应用很麻烦,不如同步来的方便.下面总结了几个常见的库API从异步转同步的几种 ...

  9. WebWork(在主线程创建子进程)

    WebWork浅谈 前言: 都知道JS是单线程语言,最让人头疼的莫过于在网络正常的情况下经常出现页面的假死, 以及在进行大量的for循环计算时会导致线程阻塞,由于要进行大量的计算JS后面的运行会被阻隔 ...

  10. CF274D

    Lenny had an n × m matrix of positive integers. He loved the matrix so much, because each row of the ...