CF B. Little Elephant and Array (暴力剪枝|莫队|线段树|树状数组)

链接

题意:

小象喜欢和数组玩。现在有一个数组 a a a,含有 n n n 个正整数,记第 i i i 个数为 a i a_i ai 。现在 m m m个询问,每个询问包含两个正整数 l j l_j lj​ 和 r j r_j rj; ( 1 ⩽ l j ⩽ r j ⩽ n ) (1\leqslant l_j\leqslant r_j\leqslant n) (1ljrjn),小象想知道在 A l j A_{l_j} Alj A r j A_{r_j} Arj ​ 之中有多少个数 x x x,其出现次也为 x x x

暴力剪枝:

说是暴力剪枝其实也不是真正的暴力,其实是前缀和优化再去剪枝。
首先我们看剪枝:一共 n n n个数,要求是区间内,这个数个数等于这个数,那么我们可以知道最多有 n \sqrt n n 个数符合条件。所以大于n 的数我们直接不用去看,他一定不会满足条件。 小于等于n的数提取出来,记录一下数量,数量大于等于这个数的我们用前缀和维护,然后每次查询我们判断这 n \sqrt n n 有多少符合条件。
时间复杂度是 O ( ( q + n ) n ) O((q+n)\sqrt n) O((q+n)n )
用int 就行 long long好像会暴内存

int n,q;
int a[maxn],num[maxn],vis[maxn],tree[500][maxn];
void solve()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		if(a[i]<=n){
			num[a[i]]++;
		}
	}
	int cnt=0;
	for(int i= 1 ;i<=n;i++)
	{
		if(num[i]>=i){
			vis[++cnt]=i;
			for(int j=1;j<=n;j++){
				tree[cnt][j]=tree[cnt][j-1]+(a[j]==i);
			}
		}
	}
	while(q--){
		int l,r;
		scanf("%d%d",&l,&r);
		int ans=0;
		for(int i=1;i<=cnt;i++){
			if(tree[i][r]-tree[i][l-1] == vis[i]){
				ans++;
			}
		}
		printf("%d\n",ans);
	}
}

莫队:

我们利用莫队进行离线的查询。
首先还是像上面那样的剪枝,大于n的数肯定不会有贡献,所以不用管,用莫队是因为,我们对每一段进行处理,但是我们发现这些区间有很多是重复的,那么我们就可以减少对这部分的处理。所以我们对每一段进行排序,然后处理这段,之后下一次处理利用到前一段,将其扩缩成当前这个区间即可。计算答案的条件,如果增加一段,加上这一段中数,之后这个数x的个数变成x那么答案将+1,如果个数变成x+1那么答案将-1,如果需要减掉一段数,之后某个数x的个数变成了x那么答案将+1,如果个数变成x-1那么答案将-1.
时间复杂度 O ( q l o g q + n n ) O(qlog_q+n\sqrt n) O(qlogq+nn )

这是没有分块的优化:3800ms左右 差点超时 (也可以说是纯暴力了时间还没上面那个优)

int n,m,res;
int a[maxn],ans[maxn],num[maxn];
struct node {
	int l,r;
	int id;
}q[maxn];
bool cmp(node a,node b){
	if(a.l!=b.l) return a.l<b.l;
	return a.r<b.r;
}
void Insert(int x){
	if(a[x] > n) return ;
	++num[a[x]];
	if(num[a[x]]==a[x]) ++res;
	if(num[a[x]]==a[x]+1) --res;
}
void Delete(int x){
	if(a[x]>n) return ;
	if(num[a[x]]== a[x]+1) res++;
	if(num[a[x]]==a[x]) res--;
	num[a[x]]--;
}
void solve(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		int x =q[i].l,y=q[i].r,id=q[i].id;
		while(r<y) Insert(++r);
		while(l>x) Insert(--l);
		while(r>y) Delete(r--);
		while(l<x) Delete(l++);
		ans[id]= res;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}

基本的莫队:


int n,m,res,k;
int a[maxn],ans[maxn],num[maxn];
struct node {
	int l,r;
	int id;
}q[maxn];
bool cmp(node a,node b){
	
	if(a.l/k==b.l/k) return a.r<b.r;
	return a.l/k<b.l/k;
}
void Insert(int x){
	if(a[x] > n) return ;
	++num[a[x]];
	if(num[a[x]]==a[x]) ++res;
	if(num[a[x]]==a[x]+1) --res;
}
void Delete(int x){
	if(a[x]>n) return ;
	if(num[a[x]]== a[x]+1) res++;
	if(num[a[x]]==a[x]) res--;
	num[a[x]]--;
}
void solve(){
	scanf("%d%d",&n,&m);
	k=sqrt(n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		int x =q[i].l,y=q[i].r,id=q[i].id;
		while(r<y) Insert(++r);
		while(l>x) Insert(--l);
		while(r>y) Delete(r--);
		while(l<x) Delete(l++);
		ans[id]= res;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}

树状数组:

首先了解下树状数组,可维护区间和,我们这个题是找区间贡献值的。所以可行,只不过需要加工。
怎么加工那?
首先我们树状数组查询答案,是 s[r]-s[l-1],
如果我们有一个数x是这样的
1 2 3 4 5 6 ..... y
x x x x x x ..... x
这样的话我们从最后一个x看,他产生贡献的左端点是y-x+1,因为我们记录的是差那么y-x这个位置上我们为-1即可,然后我们对新生成的数组进行差分,得到y-x位置上为-1,y-x+1位置上为1.说是差分,也可以理解为记录y对应的左端点,然后我们看前一个状态 右端点为y-1那么同样的 y-1-x位置上为-1,y-x位置上为1,我们从 右端点为y-1到y 其实是y-1-x位置上-1变成0,所以需要+1, y-x位置上1变成-1,所以需要-2,y-x+1位置上,从0变成1所以需要+1.
这样构建树状数组即可查询。

vector <pii> q[maxn];
vector <int> g[maxn];
int n,m;
int a[maxn],ans[maxn],tree[maxn];

void update(int pos,int num){
 	while(pos <= n){
 		tree[pos]+=num;
 		pos+=lowbit(pos);
 	}
}

int query (int pos){
	int sum = 0;
	while (pos){
		sum+=tree[pos];
		pos-=lowbit(pos);
	}
	return sum;
}

void solve(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);		
	}
	for(int i=1;i<=m;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		q[r].push_back({l,i});
	}
	for(int i=1;i<=n;i++){
		int x = a[i];
		if(x>n) continue;
		g[x].push_back(i);
		int size = g[x].size();
		if(size >=x){
			update(g[x][size-x],1);
			if(size>=x+1) update(g[x][size-x-1],-2);
			if(size>=x+2) update(g[x][size-x-2],1);
		}
		for(int j=0;j<q[i].size();j++){
			pii p=q[i][j];
			ans[p.y]=query(i)-query(p.x-1);
		}
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 您可以使用C++中的文件输入流(ifstream)来打开一个文件并读取其内容。下面是一个简单的示例代码: ```c++ #include <iostream> #include <fstream> #include <string> int main() { std::ifstream inputFile("C:\\Users\\86139\\Documents\\c++\\elephant.off"); std::string line; if (inputFile.is_open()) { while (getline(inputFile, line)) { std::cout << line << std::endl; } inputFile.close(); } else { std::cout << "Unable to open file" << std::endl; } return 0; } ``` 这个示例程序将打开指定路径的文件(elephant.off),并将文件的每一行输出到控制台。如果文件无法打开,则程序将输出“Unable to open file”。 ### 回答2: 要打开地址为"C:\Users\86139\Documents\c \elephant.off"的文件c,你可以按照以下步骤进行操作: 1. 首先,打开文件资源管理器。你可以通过点击任务栏中的文件夹图标或按下"Windows键 + E"组合键来打开文件资源管理器。 2. 在文件资源管理器的地址栏中,将地址"C:\Users\86139\Documents\c \elephant.off"粘贴或手动输入。 3. 然后按下回车键或点击文件资源管理器界面上的前往按钮。 4. 如果路径存在并且文件"c"确实位于该路径下,文件资源管理器将会显示出该文件。 5. 双击该文件"c",默认情况下,会尝试用默认的应用程序打开该文件。 6. 如果没有默认应用程序与该文件关联,你可以右键点击文件"c",选择"打开方式",然后从子菜单中选择一个合适的应用程序来打开文件。 请注意,以上步骤中的地址"C:\Users\86139\Documents\c \elephant.off"在Windows系统中可能因不同的用户名等因素而有所不同。请确保输入的地址是准确无误的,并且对应的文件"c"确实存在于该路径下。 ### 回答3: 要打开地址为"C:\Users\86139\Documents\c \elephant.off"的文件 c ,你可以按照以下步骤操作: 1. 打开我的电脑(或者双击桌面上的计算机图标)。 2. 在文件浏览器的地址栏中输入"C:\Users\86139\Documents",并按下回车键,进入该目录。 3. 在该目录下寻找c文件夹并打开。 4. 在"c"文件夹中找到名为"elephant.off"的文件。 5. 双击"elephant.off"文件即可打开该文件。 6. 如果你安装了与文件关联的程序,系统会自动使用默认的程序打开文件,否则你可以选择一个适当的程序来打开该文件。 请注意,打开文件c的具体步骤可能会因操作系统和安装的软件而有所不同。如果以上步骤无法成功打开文件,请确保你有适当的权限,并检查文件路径是否正确。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值