JZOJ5951. 【NOIP2018模拟11.04】锋芒毕露

5 篇文章 0 订阅
2 篇文章 0 订阅

题意:

数据范围:


真— 100 100 100%: N &lt; = 2 ∗ 1 0 5 N&lt;=2*10^5 N<=2105
时限3.5S,开O2。

Analysis:

很容易想到怎么处理相交,相包含的情况,但是如果要去掉相包含,则显得非常困难。
如果用数据结构维护,却找不到一个策略去优化,这时候不妨套路地想想平衡规划。
我们设一个阈值 K K K,出现次数大于 K K K的颜色为集合 Q Q Q,出现次数小于等于 K K K的颜色为集合 P P P
显然 Q Q Q大小不会超过 N K \frac{N}{K} KN。进一步分析:
首先我们可以算出不同颜色对的圆的总数,然后减去不合法的。
第一种情况:不相交的圆。
我们可以枚举圆的右端点,然后算出有多少不同颜色的圆在其右边,注意减去相同颜色。这个可以 O ( N ) O(N) O(N)
第二种情况:相含
根据颜色所属集合划分情况:
1. Q . . P . . P . . Q Q..P..P..Q Q..P..P..Q
Q Q Q个数少,我们可以去枚举 Q Q Q,然后对于每一个 Q Q Q做。对于第 i i i P P P,设它前面的当前 Q Q Q个数为 p r e i pre_i prei,后面的当前 Q Q Q s u f i suf_i sufi,那么它的答案会是: ∑ j = 1 i − 1 p r e i ∗ s u f i \sum_{j=1}^{i-1}pre_i*suf_i j=1i1preisufi。复杂度 O ( N 2 K ) O(\frac{N^2}{K}) O(KN2)
2. ∗ . . Q . . Q . . ∗ *..Q..Q..* ..Q..Q..
表示中间为 Q Q Q,两边为任何颜色的答案。
一样的, Q Q Q个数少,我们枚举每一个 Q Q Q,然后统计答案。若当前颜色出现了第 i i i次,设 s i s_i si表示当前 Q Q Q 1   i 1~i 1 i中出现了 s i s_i si次。那么答案为: ∑ j = 1 i − 1 ( s i − s j ) ∗ ( s i − s j − 1 ) 2 \sum_{j=1}^{i-1}\frac{(s_i-s_j)*(s_i-s_j-1)}{2} j=1i12(sisj)(sisj1)。把式子拆开之后,发现我们只需要维护一个 s s s的前缀和,和 s s s的平方前缀和即可。复杂度同上一种情况。
3. P . . P . . P . . P P..P..P..P P..P..P..P
发现 P P P出现次数比较少,我们可以对于每一位,枚举前面的同颜色 P P P算答案。
我们记一个位置的贡献为,以其为左端点有多少相同颜色的圆。那么每一次枚举前面的相同颜色 P P P,用树状数组查询一下这个区间内有多少对圆,减掉和当前颜色相同的对数,并在其位置上加 1 1 1。复杂度 O ( N K log ⁡ N ) O(NK\log{N}) O(NKlogN)

K K K取多少时优呢,解一下等式即可得到,当 K K K N log ⁡ N \sqrt{\frac{N}{\log{N}}} logNN 时最优。
总复杂度: O ( N N log ⁡ N ) O(N\sqrt{N\log{N}}) O(NNlogN )
能过了这个题也是比较玄学。

Code:

# include<cstdio>
# include<cstring>
# include<algorithm>
# include<cmath>
# include<vector>
using namespace std;
# define pb push_back
const int N = 7e5 + 5;
const int mo = 19260817;
const int inv = mo - mo / 2;
typedef long long ll;
vector <int> pos[N];
int a[N],Q[N],t[N],vis[N];
int s[N],s1[N],num[N],las[N];
int n,ans,h;
inline int inc(int x,int y)
{ return x + y >= mo ? x + y - mo : x + y; }
inline int dec(int x,int y)
{ return x - y < 0 ? x - y + mo : x - y; }
inline void add(int x)
{ for (int i = x ; i <= n ; i += i & (-i)) ++t[i]; }
inline int qry(int x)
{
	int ret = 0;
	for (int i = x ; i ; i -= i & (-i)) ret = inc(ret,t[i]);
	return ret;
}
int main()
{
	freopen("everytime.in","r",stdin);
	freopen("everytime.out","w",stdout);
	scanf("%d",&n); int lim = sqrt(n / min(n,15));
	for (int i = 1 ; i <= n ; ++i) scanf("%d",&a[i]),++t[a[i]];
	for (int i = 1 ; i <= n ; ++i)
	if (t[i] > lim) Q[++h] = i,vis[i] = 1;
	memset(t,0,sizeof(t));
	for (int i = n ; i ; --i) num[i] = inc(num[i + 1],t[a[i]]),++t[a[i]];
	int sum = 0;
	for (int i = 1 ; i <= n ; ++i)
	{
		int c = (ll)t[i] * (t[i] - 1) / 2 % mo;
		ans = inc(ans,(ll)c * sum % mo),sum = inc(sum,c);
	}
	for (int i = 1 ; i <= n ; ++i)
		--t[a[i]],ans = dec(ans,(ll)s[a[i]] * dec(num[i + 1],(ll)t[a[i]] * (t[a[i]] - 1) / 2 % mo) % mo),++s[a[i]];
	memset(t,0,sizeof(t));
	for (int i = 1 ; i <= h ; ++i)
	{
		int now = 0;
		for (int j = n ; j ; --j) if (a[j] == Q[i]) s[j] = s[j + 1] + 1; else s[j] = s[j + 1];
		for (int j = 1 ; j <= n ; ++j)
		if (!vis[a[j]])
		{
			ans = dec(ans,(ll)num[las[a[j]]] * s[j + 1] % mo);
			num[j] = inc(num[las[a[j]]],now),las[a[j]] = j;
		}else if (a[j] == Q[i]) ++now;
		memset(las,0,sizeof(las));
	}
	for (int i = 1 ; i <= h ; ++i)
	{
		memset(s,0,sizeof(s)),memset(s1,0,sizeof(s1)),memset(num,0,sizeof(num));
		int now = 0;
		for (int j = 1 ; j <= n ; ++j)
		if (a[j] != Q[i])
		{
			int c = dec(inc((ll)num[a[j]] * now * now % mo,s1[a[j]]),inc((ll)num[a[j]] * now % mo,2ll * now * s[a[j]] % mo));
			c = (ll)inc(c,s[a[j]]) * inv % mo;
			ans = dec(ans,c),++num[a[j]],s[a[j]] = inc(s[a[j]],now),s1[a[j]] = inc(s1[a[j]],(ll)now * now % mo);
		}else ++now;
	}
	for (int i = 1 ; i <= n ; ++i)
	if (!vis[a[i]])
	{
		int all = pos[a[i]].size();
		for (int j = 0 ; j < all ; ++j)
		{
			int c = dec(dec(qry(i),qry(pos[a[i]][j] - 1)),(ll)(all - j) * (all - j - 1) / 2 % mo);
			ans = dec(ans,c),add(pos[a[i]][j]);
		}
		pos[a[i]].pb(i);
	} printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值