zoukankan      html  css  js  c++  java
  • @atcoder


    @description@

    给定 L,连续至少 L 个相同的数 k 可以合并成 1 个 k+1。
    给定一个长度为 N 的序列,问该序列有多少个子区间可以通过若干次合并变成 1 个数。

    Constraints
    1≤N≤2×10^5, 2≤L≤N, 1≤Ai≤10^9

    Input
    输入形式如下:
    N L
    A1 A2 ... AN

    Output
    输出满足条件的子区间个数。

    Sample Input 1
    9 3
    2 1 1 1 1 1 1 2 3
    Sample Output 1
    22

    @solution@

    其实思路挺简单的。

    先考虑判定一个区间是否合法。
    假如区间只包含一个数,显然合法。
    假如区间只包含一种数,只有当这种数的个数 >= L 才合法。
    否则,我们可以将最小的 x 尽可能多地合并成 x + 1。如果有不能够合并的 x 则不合法;否则递归判断新的序列是否合法。

    对于不同的区间,将最小的 x 合并成 x + 1 其实可以同时做。
    于是我们区间统计的方法就出来了:

    先对于最小的 x,统计只包含 x 的合法区间数。
    然后将所有只包含 x 的极长区间(即无法在往左右延伸的区间)合并出尽量多的 x + 1。
    因为合并出来的一个数代表原序列中的一段数,所以我们还需要维护 lf 与 rf,分别表示 “一个数充当左端点的方案数” 与 “一个数充当右端点的方案数”。

    具体怎么维护 lf 与 rf 呢?
    对于一个 x 的极长区间 a1, a2, ..., ak。a[1...L-1] 显然无法充当右端点(无法合成出 x + 1),a[L...2L-1] 可以充当第一个 x + 1 的右端点,将 a[L...2L-1] 对应的 rf 加起来就可以得到新的第一个 x + 1 的 rf 了。
    同理,a[2L...3L-1] 对应第二个 x + 1 的右端点,a[3L...4L-1] 对应第三个 x + 1 的右端……
    右端点都可以求出来了,左端点就同理了。

    如果一个极长区间 < L,直接将它删除。
    因为可能 x 合成出来的 x + 1 还可以再合成,此时方案数会计算重复(而且会计算到不合法方案)。需要在 x 合成 x + 1 的过程中,减去 x + 1 合成 x + 2 产生的贡献,才能保证不重复。

    用链表实现删减。用优先队列取出最小元素。
    因为一次合并将 L 个合成 1 个,至少减少了 L-1 个元素。那么只会有 O(n) 次合并。
    所以复杂度瓶颈在优先队列,为 O(nlogn)。

    @accepted code@

    #include<cstdio>
    #include<queue>
    #include<vector>
    #include<iostream>
    using namespace std;
    #define mp make_pair
    #define fi first
    #define se second
    const int MAXN = 200000;
    typedef pair<int, int> pii;
    typedef long long ll;
    priority_queue<pii, vector<pii>, greater<pii> >que;
    int lst[MAXN + 5], nxt[MAXN + 5], N, L;
    void link(int x, int y) {lst[y] = x, nxt[x] = y;}
    bool check(int x, int y) {return nxt[x] == y;}
    ll lf[MAXN + 5], rf[MAXN + 5], f1[MAXN + 5], f2[MAXN + 5];
    vector<int>v1, v2;
    ll solve(int x) {
    	ll ret = 0, tmp = 0;
    	int lt = v2.size(), lb = lst[v2[0]], rb = nxt[v2[lt-1]];
    	for(int i=0;i<lt;i++) {
    		if( i - L + 1 >= 0 ) tmp += lf[v2[i-L+1]];
    		ret += tmp * rf[v2[i]];
    	}
    	for(int i=0;i<lt;i++)
    		f1[v2[i]] = lf[v2[i]], f2[v2[i]] = rf[v2[i]], lf[v2[i]] = rf[v2[i]] = 0;
    	int c = lt / L;
    	if( c ) {
    		for(int i=L-1;i<lt;i++) {
    			int t = (i + 1)/L - 1;
    			rf[v2[t]] += f2[v2[i]];
    		}
    		for(int i=lt-L;i>=0;i--) {
    			int t = c - (lt - i)/L;
    			lf[v2[t]] += f1[v2[i]];
    		}
    		for(int i=1;i<c;i++)
    			link(v2[i-1], v2[i]);
    		link(lb, v2[0]), link(v2[c-1], rb);
    		for(int i=0;i<c;i++)
    			que.push(mp(x + 1, v2[i]));
    		tmp = 0;
    		for(int i=0;i<c;i++) {
    			if( i - L + 1 >= 0 ) tmp += lf[v2[i-L+1]];
    			ret -= tmp * rf[v2[i]];
    		}
    	}
    	else nxt[lb] = N + 1, lst[rb] = 0;
    	v2.clear();
    	return ret;
    }
    int main() {
    	scanf("%d%d", &N, &L);
    	for(int i=1;i<=N;i++) {
    		int x; scanf("%d", &x);
    		que.push(mp(x, i)), link(i, i + 1);
    		lf[i] = rf[i] = 1;
    	}
    	ll ans = 0; link(0, 1);
    	while( !que.empty() ) {
    		int x = que.top().fi; v1.clear();
    		while( !que.empty() && que.top().fi == x )
    			v1.push_back(que.top().se), que.pop();
    		v2.clear(); v2.push_back(v1[0]);
    		for(int i=1;i<v1.size();i++) {
    			if( !check(v1[i-1], v1[i]) )
    				ans += solve(x);
    			v2.push_back(v1[i]);
    		}
    		ans += solve(x);
    	}
    	printf("%lld
    ", ans + N);
    }
    

    @details@

    为什么会有一种AGC的F题变水了的错觉

    即使想到了这个方向,也很难说能够把所有细节想清楚吧。
    嗯。应该是这样的。

  • 相关阅读:
    短信编码总结
    在Linux下用C语言实现短信收发
    sshd_config配置详解
    SSH的通讯和认证
    linux安装tacacs+服务器
    Tacacs+认证详细调研
    伪分布配置完成启动jobtracker和tasktracker没有启动
    Hadoop学习记录(7)|Eclipse远程调试Hadoop
    Hadoop学习记录(6)|Eclipse安装Hadoop 插件
    Hadoop学习记录(5)|集群搭建|节点动态添加删除
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11766956.html
Copyright © 2011-2022 走看看