Logo lxn 的博客

博客

924提高组模拟赛

...
lxn
2025-12-01 12:57:44

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-09-24 14:36:09

T2 优美区间

区间问题,区间[l,r]都是ai的倍数,就是最小公倍数是a[i]

方法1.区间最小公倍数可以进行区间合并。$st$表可以解决。 然后再通过二分答案找到最大长度。 最后枚举最大长度,输出答案。时间复杂度$o(nlog^n)$。

方法2.找到一个数$a[i]$可扩展的左侧区间和右侧区间。 怎样快速的找到?

例如:4 4 2 6 8 6 9 3 9 12 8

当$a_i$向右扩展到最后一个可扩展的数$a_j$后,$a_{i+1}..a_j$没有必要再扩展。直接跳到$j+1$即可。所以一个数被向同一个放心扩展的时候,不管是作为除数还是被除数,只需要访问1次。时间复杂度$o(n)$.

方法一参考代码:

#include <bits\/stdc++.h>
#define pb push_back
using namespace std;

const int N=3e5+50,LOG_N=20; 

int gcd(int a,int b){ return !b?a:gcd(b,a%b); }
int n,f[N][LOG_N],g[N][LOG_N],lg[N],a[N];
vector<int> ans;
int qf(int l,int r){
	int k=lg[r-l+1];
	return min(f[l][k],f[r-(1<<k)+1][k]);
}
int qg(int l,int r){
	int k=lg[r-l+1];
	return gcd(g[l][k],g[r-(1<<k)+1][k]);
}
bool check(int len){
	int i;
	for(i=1;i+len-1<=n;++i){
		if(qf(i,i+len-1)==qg(i,i+len-1))return true;
	}
	return false;
}
int main(int argc,char *argv[]){
	int i,k,mid;
	scanf("%d",&n); 
	for(i=1;i<=n;++i){
		scanf("%d",&a[i]); 
		f[i][0]=g[i][0]=a[i]; 
	}
	lg[0]=-1; 
	for(i=1;i<=n;++i)lg[i]=lg[i>>1]+1;
	for(k=1;k<20;++k){
		for(i=1;i+(1<<k)-1<=n;++i){
			f[i][k]=min(f[i][k-1],f[i+(1<<(k-1))][k-1]);
			g[i][k]=gcd(g[i][k-1],g[i+(1<<(k-1))][k-1]);
		}
	}
	int l=1,r=n+1;
	while(r-l>1){
		mid=(l+r)>>1;
		if(check(mid))l=mid;
		else r=mid;
	}
	for(i=1;i+l-1<=n;++i)
		if(qf(i,i+l-1)==qg(i,i+l-1))ans.pb(i); 
	printf("%d %d\n",(int)ans.size(),l); 
	for(auto t:ans) printf("%d ",t); puts(""); 
	return 0; 
}

方法二参考代码:

#include<iostream>
#include<cstdio>
#include<queue>

using namespace std;
const int N = 300030;

int n,c[N],l[N],r[N],cnt,maxn;
queue<int>q;

int main()
{
	scanf("%d",&n);
	for(int i=1;i <= n;i++)
		scanf("%d",&c[i]);
	int x = 1;
	for(int i=1;i <= n;i = x){
		while(x <= n && c[x]%c[i] == 0) x++;
		r[i] = x-1;
	}
	x = n;
	for(int i=n;i >= 1;i--){
		if(r[i]){
			x = i;
			while(x >= 1 && c[x]%c[i] == 0) x--;
			l[i] = x+1;
			i = x+1;
		}
	}
	for(int i=1;i <= n;i++){
		if(!l[i])
			continue;
		if(r[i]-l[i]+1 == maxn)
			q.push(l[i]) , cnt++;
		if(r[i]-l[i]+1 > maxn){
			maxn = r[i]-l[i]+1 , cnt = 1;
			while(!q.empty())
				q.pop();
			q.push(l[i]);
		}
			
	}
	printf("%d %d\n",cnt,maxn);
	while(!q.empty())
		printf("%d ",q.front()) , q.pop();
	puts("");
	return 0;
}

T3

题目分析

设lowbit(x^y)=2^t

则x的t-1位和y的t-1位相同。

不断把数分组,找到对应的位数。

30\% 的数据,$o(n^2)$暴力枚举。

10\% $a_i \leq1$, 分为两个集合,0集合和1集合,ij分别属于这两个集合才可行。

10\% $a_i \leq3$, 怎么做?

分为两种情况:

$lowbit(a_i,a_j)==1$的情况

0集合和1集合,ij分别属于这两个集合才可行。 $lowbit(a_i,a_j)==10$的情况 分别处理两个集合:{10,00},{11,01}

扩展到所有情况:

lowbit(x,y) 把x和y都分解成二进制 奇数(二进制末位是1),偶数(二进制末位是0)

lowbit(x^y)=1

把奇数放到左边,假设有x个, 把偶数放到右边,假设有y个, xy2 对 lowbit起来是等于1。

lowbit(x^y)=2 (10)

x和y全奇 或者 全偶

假如x和y全奇数

二进制倒数第二位一个是0一个是1

倒数第二位是0 倒数第二位是1

1 2 3 4 5 001 010 011 100 101

001 010 011 100 101

322 * 1 = 12

011 001         010 100 101

122 * 2 = 8      112 * 2=4

101 001 112 * 4 = 8

方法一:分治, 二元组一个在左边,另一个在右边,这样的贡献是很容易统计的 对于每一个数,它只在最多31层中被分治到,总时间复杂度是$o(n*31)$

方法二:找出后t-1位一样的分成一组,再把这组内第t位为1和0的个数都找出来。计算乘积。也可以用trie树,但注意用的时候要从低位开始建树,因为我们要从低位找相同前缀。

方法一参考代码:


#include<bits\/stdc++.h>
#define MOD 1000000007
#define MAXN 999999999
#define LL long long
using namespace std;

LL a[100005],b[100005],ans;
LL n;

void work(LL m,LL t) {
	LL l,r,p,i;
	if(m<=1||t>=30) return;
	l=0;
	for(i=1; i<=m; i++)
		if(a[i]&(1<<t)) {
			l++;
			b[l]=a[i];
		}
	r=l;
	for(i=1; i<=m; i++)
		if(!(a[i]&(1<<t))) {
			r++;
			b[r]=a[i];
		}
	for(i=1; i<=m; i++) a[i]=b[i];
	ans=(ans+(l*(m-l)%MOD)%MOD*(1<<t)%MOD)%MOD;\/\/l*r
	work(l,t+1);
	p=0;
	for(i=l+1; i<=r; i++) {
		p++;
		a[p]=a[i];
	}
	work(p,t+1);
}

int main() {

	int i;

	scanf("%lld",&n);
	for(i=1; i<=n; i++)
		scanf("%lld",&a[i]);
	work(n,0);
	ans=(ans*2)%MOD;
	printf("%lld",ans);
	return 0;
}

方法二参考代码:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include<bitset>
using namespace std;
typedef long long ll;
const int N=100009;
const ll mod=1e9+7;
int nex[N*31][2],m,n,cnt=0,flag;
ll num[N*31][2],ans=0;
int a[N],le[N],ri[N];
inline void insert(int x) {
	int l,p,y=x;
	int now=0;
	for(int i=0; i<=30; i++) {
		p=1&(x>>i);
		if(!nex[now][p]) 
			nex[now][p]=++cnt;
		num[now][p]++; 	
		now=nex[now][p];
	} 
}
void count(int u,ll t){

	if(num[u][0]&&num[u][1])
		ans=(ans+num[u][0]%mod*num[u][1]%mod*(1<<t))%mod;
		
	if(nex[u][0]){
		count(nex[u][0],t+1);
	}
	if(nex[u][1]){
		count(nex[u][1],t+1);
	}
	
} 

int main() {
	int x=0; 
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		insert(a[i]);
	}
		
	count(0,0); 
	ans=ans*2%mod;
	printf("%d\n",ans);

	return 0;
}

T4逆序对

题目分析

对于当前枚举的L,R若满足条件,则对于当前的L,当R取R~N时都满足条件。
对于当前的枚举的L,R若不满足条件,则对于当前的L,当R取L+1~R时都不能满足条件。

于是就转变成了一个滑动窗口问题,双指针维护,用树状数组算出左右指针移动对当前总值的贡献 双指针,随着l的左移,r不断左移,单调的。

两个树状数组, 一个是y:处理 r...n后缀的情况,r不断增大。 一个是x:处理 1..l,前缀的情况,l不断增大。


#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

using namespace std;


#define LL "%lld"


#define lb(x) ((x)&(-(x)))

const int maxn=100010;

int n,z[maxn],y[maxn],x[maxn];

long long k;

bool cmp(int a,int b) {
	return z[a]<z[b];
}

void insert(int *z,int p,int d) {
	for (; p<=n; p+=lb(p))
		z[p]+=d;
}

int query(int *z,int p) {
	int ans=0;
	for (; p; p-=lb(p))
		ans+=z[p];
	return ans;
}

int main() {

	scanf("%d" LL,&n,&k);
	for (int a=1; a<=n; a++)
		scanf("%d",&z[a]),y[a]=a;
	sort(y+1,y+n+1,cmp);
	x[y[1]]=1;
	for (int a=2; a<=n; a++)
		if (z[y[a]]==z[y[a-1]]) x[y[a]]=x[y[a-1]];
		else x[y[a]]=x[y[a-1]]+1;
	for (int a=1; a<=n; a++)\/\/完成a离散化后的数据 
		z[a]=x[a];
	memset(x,0,sizeof(x));
	memset(y,0,sizeof(y));
	long long nowans=0;
	int p=n;
	while (p>=1) {\/\/第一遍逆序对 
		nowans+=query(y,z[p]-1);\/\/逆序做,查询比他小的数。nowans是所有逆序对的个数。 
		insert(y,z[p],1);
		p--;
	}
	p++;

\/\/存储当前到l的逆序对情况 
	long long ans=0;
	int l,r=1;
	for (l=1; l<=n; l++) {
		if (r==l) {\/\/,不能有l==r,因此要删掉r,同时减去对应的逆序对数目。 
			nowans-=l-1-query(x,z[r])+query(y,z[r]-1);\/\/减去和1.r中 1..r-1和人构成的逆序对个数,减去和r点和后面数据构成的逆序对个数query(y,z[r]-1) 
			insert(y,z[r],-1);\/\/删掉r 
			r++;
		}
		nowans+=l-1-query(x,z[l])+query(y,z[l]-1);\/\/1..l,r..n构成的逆序对总数 
		insert(x,z[l],1);
		while (nowans>k && r<=n) {\/\/个数太多,r要后移。 
			nowans-=l-query(x,z[r])+query(y,z[r]-1);
			insert(y,z[r],-1);
			r++;
		}
		if (nowans<=k) ans+=n-r+1;\/\/r 到n都符合条件。 
	}
	printf(LL "\n",ans);

	return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。