NOI Online #2 提高组

NOI Online #2 提高组

A 涂色游戏

题目描述

你有 1 0 2 0 10^20 1020个格子, 他们从0开始编号, 初始时所有格子都还未染色, 现在你按如下规则对它们进行染色:
1.编号是p1倍数的格子(包括0号格子, 下同)染成红色。
2.编号是p2倍数的格子染成蓝色。
3,编号既是p1倍数也是p2倍数的格子,染成红色或蓝色。
其中p1, p2给定, 若格子编号是p1或p2的倍数则它必须被染色。
在忽略掉所有未染色的格子后, 你不希望存在k个连续的格子颜色相同, 因为你认为在这种染色方案是无聊的。现给定p1, p2, k;你想知道是否有一种染色方案是不无聊的。

题目思路

首先明白这个涂色的格子是一个循环。
我们不妨设有a, b两种颜色, 分别对应p1, p2, 而且p1 > p2;
肯定连续的最长格子是b颜色的, 因为p2小, 颜色密度大, 涂的多。
同时一个循环长度是lcm(p1, p2);然后后面的就和这个循环里的一摸一样。
一个循环里容易得知有p1/gcd(p1, p2)个b格子, p2/gcd(p1, p2)个a格子。
但lcm那里要填a颜色的格子, 因为两边都是b格子。所以b格子数量要-1.
设上面格子数分别为n1(a), n2(b).
则最长连续数就为n1/n2, 或n1/n2+1.(取决于n1和n2是否为倍数)
与k判断即可。
注意:k = 1时, 肯定是No
如果p1 == p2, 则肯定是yes

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <cstdio>
#include <string>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
long long gcd(long long a,long long b)
{return b==0?a:gcd(b,a%b);}
long long T,p1,p2,k,n1,n2,now_gcd,n;
int main()
{
	//freopen("color.in","r",stdin);
	//freopen("color.out","w",stdout);
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld %lld %lld",&p1,&p2,&k);
		if(k<2)
		{
			printf("No\n");
			continue;
		}
		if(p1==p2)
		{
			printf("Yes\n");
			continue;
		}
		if(p1<p2)
		p1^=p2^=p1^=p2;
		now_gcd=gcd(p1,p2);
		n1=p1/now_gcd-1;
		n2=p2/now_gcd;		
		if(n1%n2!=0)
			n=(n1/n2)+1;
		else
			n=n1/n2;
		if(n>=k)
		printf("No\n");
		else
		printf("Yes\n");
	}
	return 0;
}

B 子序列问题

题目描述

给定一个长度为n的正整数序列 A 1 , A 2 , ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ , A n A_1, A_2,·······, A_n A1,A2,,An.定义一个函数 f ( l , r ) f(l,r) f(l,r)表示:序列中下标在 [ l , r ] [l,r] [l,r]范围内的子区间中, 不同的整数个数, 换句话说, f ( l , r ) f(l,r) f(l,r)就是集合 A l , A l + 1 , ⋅ ⋅ ⋅ ⋅ ⋅ , A r {A_l, A_{l+1},·····,A_r} Al,Al+1,,Ar的大小, 这里的集合是不可重集, 即集合中的元素互不相等。
现在, 请你求出 ∑ l = 1 n ∑ r = l n ( f ( l , r ) ) 2 \sum_{l=1}^n \sum_{r = l}^n (f(l,r))^2 l=1nr=ln(f(l,r))2。由于答案可能很大, 请对 1 0 9 + 7 10^9+7 109+7取模。

题目思路

暴力怎么做, 两个cigema双重枚举O(n2)。接着, set维护值O(logn), 总时间复杂度O(n2logn)
考虑枚举f的值, 并算他出现了几次, 加到答案里。
但是, 想了好久, 也没有想出来, 因为随机性太大, 找不着一个很明显的特征
于是我们又开始考虑每个位置对答案的贡献, 发现:当位置i这个数在[l,r]里第一次出现时, 才有贡献。
所以我们设一个数组b, b[i]表示a[i]上一次出现的位置。如果是第一次, 则b[i] = 0;
这样以后, 位置i上的数, 贡献的区域为 l ∈ ( b [ i ] , i ] , r ∈ [ i , n ] l \in (b[i], i] , r \in [i, n] l(b[i],i],r[i,n]这些区间里。
但是, 题目需要求的是平方, 所以我们转化成一个区间中, 任选两个合法位置的方案数, 在反过来求, 枚举两个位置(i,j)算他们能做出贡献的区间数为左端点数量乘上右端点 = (i - max(b[i], b[j])) * (n - j + 1)(i >= b[j])
复杂度O(n^2)
在考虑如何优化,
首先, 如果我们枚举j的话, 那么我们就转而求成了 ∑ i = 1 j m a x ( p r e [ i ] , p r e [ j ] ) ; \sum_{i = 1} ^ {j} max(pre[i], pre[j]); i=1jmax(pre[i],pre[j]);那么维护最大值其实非常好维护, 两个树状数组就可以达到O(logn)的时间复杂度。
所以, 总的时间复杂度就为O(nlogn)
完结撒花
细节请看代码

//problem:sequence
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=1e6,MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int n,a[MAXN+5],vals[MAXN+5],cnt_v,pre[MAXN+5],pos[MAXN+5],tosub[MAXN+5];
vector<int>vec[MAXN+5];

struct FenwickTree{
	int sum[MAXN+5],num[MAXN+5];
	void point_add(int p,int ds,int dn){
		p++;
		for(;p<=n;p+=(p&(-p)))add(sum[p],ds),add(num[p],dn);
	}
	pii prefix_query(int p){
		p++;
		int rs=0,rn=0;
		for(;p;p-=(p&(-p)))add(rs,sum[p]),add(rn,num[p]);
		return mk(rs,rn);
	}
	FenwickTree(){}
}T;

int main() {
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;++i)cin>>a[i],vals[i]=a[i];
	sort(vals+1,vals+n+1);
	cnt_v=unique(vals+1,vals+n+1)-(vals+1);
	for(int i=1;i<=n;++i){
		a[i]=lob(vals+1,vals+cnt_v+1,a[i])-vals;
	}
	for(int i=1;i<=n;++i){
		pre[i]=pos[a[i]];
		pos[a[i]]=i;
		vec[pre[i]].pb(i);
	}
	int ans=0;
	/*
	for(int i=1;i<=n;++i){
		for(int j=pre[i]+1;j<=i;++j){
			add(ans,(ll)(j-max(pre[i],pre[j]))*(n-i+1)%MOD*(i==j?1:2)%MOD);
		}
	}
	*/
	for(int i=1,sumpre=0;i<=n;++i){
		if(i>1){
			int x=(ll)i*(i-1)/2%MOD;
			sub(x,sumpre);
			pii q=T.prefix_query(pre[i]);
			add(x,q.fi);
			sub(x,(ll)q.se*pre[i]%MOD);
			x=(ll)x*(n-i+1)*2%MOD;
			sub(x,tosub[i]);
			add(ans,x);
		}
		add(ans,(ll)(i-pre[i])*(n-i+1)%MOD);
		
		add(sumpre,pre[i]);
		T.point_add(pre[i],pre[i],1);
		
		for(int j=0;j<SZ(vec[i]);++j){
			int ii=vec[i][j];
			int x=(ll)i*(i+1)/2%MOD;
			sub(x,sumpre);
			pii q=T.prefix_query(pre[ii]);
			add(x,q.fi);
			sub(x,(ll)q.se*pre[ii]%MOD);
			x=(ll)x*(n-ii+1)*2%MOD;
			add(tosub[ii],x);
		}
	}
	cout<<ans<<endl;
	return 0;
}

完美。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值