D. Cut and Stick

Posted 出尘呢

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了D. Cut and Stick相关的知识,希望对你有一定的参考价值。

D. Cut and Stick
time limit per test3 seconds
memory limit per test512 megabytes
inputstandard input
outputstandard output
Baby Ehab has a piece of Cut and Stick with an array a of length n written on it. He plans to grab a pair of scissors and do the following to it:

pick a range (l,r) and cut out every element al, al+1, …, ar in this range;
stick some of the elements together in the same order they were in the array;
end up with multiple pieces, where every piece contains some of the elements and every element belongs to some piece.
More formally, he partitions the sequence al, al+1, …, ar into subsequences. He thinks a partitioning is beautiful if for every piece (subsequence) it holds that, if it has length x, then no value occurs strictly more than ⌈x2⌉ times in it.

He didn’t pick a range yet, so he’s wondering: for q ranges (l,r), what is the minimum number of pieces he needs to partition the elements al, al+1, …, ar into so that the partitioning is beautiful.

A sequence b is a subsequence of an array a if b can be obtained from a by deleting some (possibly zero) elements. Note that it does not have to be contiguous.

Input
The first line contains two integers n and q (1≤n,q≤3⋅105) — the length of the array a and the number of queries.

The second line contains n integers a1, a2, …, an (1≤ai≤n) — the elements of the array a.

Each of the next q lines contains two integers l and r (1≤l≤r≤n) — the range of this query.

Output
For each query, print the minimum number of subsequences you need to partition this range into so that the partitioning is beautiful. We can prove such partitioning always exists.

Example
inputCopy
6 2
1 3 2 3 3 2
1 6
2 5
outputCopy
1
2
Note
In the first query, you can just put the whole array in one subsequence, since its length is 6, and no value occurs more than 3 times in it.

In the second query, the elements of the query range are [3,2,3,3]. You can’t put them all in one subsequence, since its length is 4, and 3 occurs more than 2 times. However, you can partition it into two subsequences: [3] and [2,3,3].

思考:
充分利用规律:若有影响则大于1/2,可大大简化

方法一:线段树(或倍增dp)

#include <bits/stdc++.h>
using namespace std;
int a[300005],tree[1200005];
vector<int> v[300005];
int cnt(int l,int r,int c) {
    return upper_bound(v[c].begin(),v[c].end(),r)-lower_bound(v[c].begin(),v[c].end(),l);
}
void build(int node,int st,int en) {
    if (st==en) tree[node]=a[st];
    else {
        int mid=(st+en)/2;
        build(2*node,st,mid);
        build(2*node+1,mid+1,en);
        tree[node]=(cnt(st,en,tree[2*node])>cnt(st,en,tree[2*node+1])? tree[2*node]:tree[2*node+1]);
    }
}
int query(int node,int st,int en,int l,int r) {
    if (en<l || st>r || r<l)
    return 0;
    if (l<=st && en<=r)
    return cnt(l,r,tree[node]);
    int mid=(st+en)/2;
    return max(query(2*node,st,mid,l,r),query(2*node+1,mid+1,en,l,r));
}
int main() {
    int n,q;
    scanf("%d%d",&n,&q);
    for (int i=1;i<=n;i++) {
        scanf("%d",&a[i]);
        v[a[i]].push_back(i);
    }
    build(1,1,n);
    while (q--) {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\\n",max(1,2*query(1,1,n,l,r)-(r-l+1)));
    }
}


我写的:
1.一个重大错误!!!:
//递归不要用全局变量!!!会改变数值的!!(用小数据调一下也快些)
2.//ask的范围不影响时间的
3.//线段树查询要点:递归看,若在区间内则直接有,若在区间外则递归到下层
4.证明://思想:如果在这些区间都不是众数,那总共也不可能超过1/2

//#pragma GCC optimize(3,"Ofast","inline")
//#pragma GCC optimize(2)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#include<iostream>
#include<algorithm>
//#include<string>
//#include<sstream>
#include<vector>
#include<map>
//#include<set>
//#include<ctype.h>
//#include<stack>
//#include<queue>
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
//FILE*fp=freopen("text.out","w",stdout);
#endif
using namespace std;
//#define ll long long
#define ld long double
#define pii pair<int,int>
#define piii pair<int,pii>
#define pll pair<ll,ll>
#define plll pair<ll,pll> 
#define pdd pair<double,double>
#define pdi pair<double,int>
#define pid pair<int,double>
#define vi vector <int> 
#define vii vector <vi> 
#define vl vector<ll>
#define st first
#define nd second
#define pb push_back
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
#define _forplus(i,a,b) for( register int i=(a); i<=(b); i++)
#define forplus(i,a,b) for( register int i=(a); i<(b); i++)
#define _forsub(i,a,b) for( register int i=(a); i>=(b); i--)
#define _forauto(a,b) for(auto &(a):(b))
#define _forautome(a,b,c) for(auto (a) = (b); (a) != (c); (a)++)
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
//参考:set<int>::iterator iter = vis.begin();
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define pi (acos(-1))
#define EPS 0.00000001
#define MOD 1000000007
#define fastio 	std::ios::sync_with_stdio(false);std::cin.tie(0);
//#define int ll
#define N 300005
#define ask(k,l,r) (int)(upper_bound(ALL(v[k]),r)-lower_bound(ALL(v[k]),l))
int n,q,a[N],l,r,ans,tree[N*4];//递归不要用全局变量!!!会改变数值的!!(用小数据调一下也快些) 
vector<int>v[N];
void build(int i,int l,int r){
	if(l==r){
		tree[i]=a[l];
		return;
	}
	int m=(l+r)/2;
	build(2*i,l,m);
	build(2*i+1,m+1,r);
	//ask的范围不影响时间的 
	tree[i]=(ask(tree[2*i],l,r)>ask(tree[2*i+1],l,r)?tree[2*i]:tree[2*i+1]);
}
int query(int now,int ll,int rr,int l,int r){
	if(ll>=l&&rr<=r){
		return ask(tree[now],l,r);
	}//线段树要点:递归看,若在区间内则直接有,若在区间外则递归到下层 
	if(ll>r||rr<l)return 0;
	int m=(ll+rr)/2;
	return max(query(now*2,ll,m,l,r),query(now*2+1,m+1,rr,l,r));
}
//思想:如果在这些区间都不是众数,那总共也不可能超过1/2 
int32_t main(){
	fastio
	cin>>n>>q;
	_forplus(i,1,n){
		cin>>a[i];
		v[a[i]].pb(i);
	}
	build(1,1,n); 
	_forplus(i,1,q){
		cin>>l>>r;
		//寻找众数个数 
		cout<<max(1,2*query(1,1,n,l,r)-(r-l+1))<<endl;
	}
	return 0;
}

方法二:众数多于1/2才有效,可随机化

#include<bits/stdc++.h>
using namespace std;
int n,q,l,r,a[300005];
vector<int> v[300005];
#define all(v) (v).begin(),(v).end()
#ifdef LOCAL
FILE*FP=freopen("text.in","r",stdin);
#endif
int main() {
	srand(time(NULL));
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) {
		scanf("%d",a+i);
		v[a[i]].push_back(i);
	}
	for(int i=1;i<=q;i++) {
		scanf("%d%d",&l,&r);
		int ans=0;//众数数目 
		for(int j=1;j<=40;j++) {//次数多,概率大,因为若有关,必过半, 
			int id=a[rand()*rand()%(r-l+1)+l];//利用了众数更容易被取到。两个rand()更随机 
			ans=max(ans,int(upper_bound(all(v[id]),r)-lower_bound(all(v[id]),l)));
		}
		cout<<max(1,ans+ans-(r-l+1))<<endl;
	}
	return 0;
}

一般方法:区间分块求众数
陈立杰的区间众数解题报告,不仅不必过半还可修改

以上是关于D. Cut and Stick的主要内容,如果未能解决你的问题,请参考以下文章

CodeForces - 1514D Cut and Stick(线段树/随机数)

Codeforces Round #717 (Div. 2) D. Cut

Codeforces Round #717 (Div. 2) D. Cut

ST表和倍增算法(Array Stabilization (GCD version)Codeforces Round #717 (Div. 2) D. Cut)

CF#358 D. Alyona and Strings DP

非常简短的片段:PHP word cut