@雅礼集训01/10 - [email protected] matrix

目录

  • @[email protected]
  • @[email protected]
  • @[email protected]
  • @[email protected]

@[email protected]

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

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

@[email protected]

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

假如第 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)。

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

@[email protected]

常数很大,本地测试过不了全部数据。可能是 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);
}

@[email protected]

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

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

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

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10261950.html

时间: 2024-07-30 14:07:25

@雅礼集训01/10 - [email protected] matrix的相关文章

#6030. 【雅礼集训 2017 Day1】矩阵

#6030. 「雅礼集训 2017 Day1」矩阵 题目描述 有一个 n×n  的矩阵,每个位置 (i,j) 如果是 . 表示为白色,如果是 # 表示为黑色. 初始时,每个位置可以是黑色或白色的,(i,j)  位置的值会作为 ai,j 给你. 现在有一种操作,选择两个整数 i,j∈[1,n],记 (i,1),(i,2),…,(i,n) (i, 1), (i, 2)的颜色为 C1,C2,…Cn ??,将 (1,j),(2,j),…,(n,j)  的颜色赋为 C1,C2,…,Cn ??. 你的任务是

@算法 - [email&#160;protected] matrix - tree 定理(矩阵树定理)

目录 @0 - 参考资料@ @0.5 - 你所需要了解的线性代数知识@ @1 - 定理主体@ @证明 part - [email protected] @证明 part - [email protected] @证明 part - [email protected] @证明 part - 4@ @2 - 一些简单的推广@ @3 - 例题与应用@ @0 - 参考资料@ MoebiusMeow 的讲解(超喜欢这个博主的!) 网上找的另外一篇讲解 @0.5 - 你所需要了解的线性代数知识@ 什么是矩阵

雅礼集训D1T3 math [咕咕咕]

题目描述: 样例: input: 2 3 5 0.01 3 6 0.02 output: +2 +4 数据范围: 题解: 标程: #include<cstring> #include<algorithm> #include<cstdio> #include<cmath> #define fo(i,a,b) for(i=a;i<=b;i++) const int maxn=60+5; int i,j,n,m,t,k; struct ar{ double

雅礼集训——day1、day2

day1: 嗯上午考试拿了100分.第一题40,第二题60.看完题的时候我就觉得第二题的部分分是最好得到的,因为数据范围只有300,而且一眼看上去就是网络流的二分图多重匹配模型?然后就建了个网络流写了些,期望得分是70分,但是第1组数据有点劲,被卡掉了,就拿了60分.正解是map+set的贪心...并不会STL 写完T2去看T1,先用DFS乱搞了一下,结果样例都没过去,我手推了一下样例,得到了一个公式,就是从一个点出发需要加上的边数=这个点通过DFS能够遍历到的点的个数-与这个点直接相连的点的个

雅礼集训 Day6

今日得分:10+0+20=30,修改后90+90+100=280 今日题解: T1:题中所给的结构构成一个内向环套树森林,对于每棵环套树,首先树上的每个点的孩子只能留一个最大的,其他的都需要修改,处理后变为一个环上面连一些链,每个链可以断开链首或者断开环上的前驱节点,并且至少要断开一条环上的边,特判只有一个环的情况即可 T2:一个位置如果先吃掉左边的三明治,那么它左边的位置也会先吃左边,于是我们对于每一行的都搜一遍即可 T3:如果所有操作都是在1~n区间内进行,那么它的实际作用就是去掉最大值再加

loj6029 「雅礼集训 2017 Day1」市场

传送门:https://loj.ac/problem/6029 [题解] 考虑如果有一些近似连续的段 比如 2 2 2 3 3 3,考虑在除3意义下,变成0 0 0 1 1 1,相当于整体-2 又:区间增加很容易造成这种段,所以我们猜测可以暴力维护 用一棵线段树即可.(好像真的能暴力维护啊 我不知道怎么证明复杂度) # include <stdio.h> # include <string.h> # include <iostream> # include <al

雅礼集训——day3、day4

day3: 上午考试就拿了10分... T1写了个N^3的暴力,然后就拿了10分...正解是要二分时间然后找到前m个脚,然后用二分套二分求第k大.... T2看了半天感觉并不可做...然后正解居然要用斐波拉契数列的通项来求解.... T3求最小生成树的个数,我以为这道题还是可以拿50分的,然后就没有然后了.推了2小时的样例然后强行把自己劝退了...正解是要用字母树+贪心... 要学的东西又多了好多,压力好大... 下午讲的字符串算法,最后的那个回文树不是很明白...这两天得学一下字符串算法 da

「6月雅礼集训 2017 Day10」quote

[题目大意] 一个合法的引号序列是空串:如果引号序列合法,那么在两边加上同一个引号也合法:或是把两个合法的引号序列拼起来也是合法的. 求长度为$n$,字符集大小为$k$的合法引号序列的个数.多组数据. $1 \leq T \leq 10^5, 1 \leq n \leq 10^7, 1\leq K \leq 10^9$ [题解] 显然引号序列可以看做括号序列,于是我们有了一个$O(n^2)$的dp了. 设$f_{i,j}$表示到第$i$个位置,前面有$j$个左引号没有匹配,的方案数 每次,要么有

「6月雅礼集训 2017 Day7」电报

[题目大意] 有n个岛屿,第i个岛屿有有向发射站到第$p_i$个岛屿,改变到任意其他岛屿需要花费$c_i$的代价,求使得所有岛屿直接或间接联通的最小代价. $1 \leq n \leq 10^5, 1 \leq p_i,c_i \leq 10^9$ [题解] 显然最后是个大环,特判原来就是大环的情况. 考虑每个连通块最多保留多少. 树的答案可以直接dp做出来. 环的答案,根据树的答案dp出来. h[x][0/1]表示当前做到环上第i个点,环是否被切断了,的最大保留价值. 因为环必须被切断一次.所