「USACO24DEC G」Cowdepenence 题解

「USACO24DEC G」Cowdepenence 题解


知识点

根号分治。

分析

\(O(n\sqrt{n}\log_2{n})\)

我们考虑先将每种颜色分开,然后对于每种颜色分别处理。

可以类比数论分块,我们会发现当距离范围 \(x\) 处于某个区间 \([l,r]\) 时,友谊小组的最小数量 \(cnt\) 的值是相同的,而且根据数论分块的结论,本质不同的 \(cnt\) 数量不会超过 \(\sqrt{n}\)

显然 \(x,cnt\) 的关系满足单调性,所以我们可以直接暴力二分 \(x\) 的阙值,进行检验,并且对每次重复检验的 \(x\) 进行记忆化,然后差分统计答案。

unordered_map<int,int> rec;

void Solve(const Vi &vec) {
	for(int L(1),R(1),c(Check(vec,1)); L<=n; c=Check(vec,L=++R)) {
		for(int l(L+1),r(n),mid((l+r)>>1); l<=r; mid=(l+r)>>1)
			Check(vec,mid)>=c?R=mid,l=mid+1:r=mid-1;
		ans[L]+=c,ans[R+1]-=c;
	}
	rec.clear();
}

假设我们现在处理的颜色有 \(k\) 个,那么我们有两种检验方法:

  • \(O(k)\)

    直接一遍扫过去。

    int Check1(const Vi &vec,const int &lim) {
    	int cnt(0),sta(0);
    	for(const int &x:vec)if(!sta||x-sta>lim)++cnt,sta=x;
    	return cnt;
    }
    
  • \(O(\frac{n}{x}\log_2{k})\)

    我们中间不断二分下一个点。

    int Check2(const Vi &vec,const int &lim) {
    	int cnt(0),sta(vec.front());
    	while(true) {
    		++cnt;
    		int it(upper_bound(vec.begin(),vec.end(),sta+lim)-vec.begin());
    		if(it==(int)vec.size())break;
    		sta=vec[it];
    	}
    	return cnt;
    }
    

我们每次取其中复杂度小的一个。

int Check(const Vi &vec,const int &lim) {
	if(rec.count(lim))return rec[lim];
	auto cal=[&]() -> ll { return (ll)rec.size()*lim/n; };
	return rec[lim]=(cal()<20&&(1<<cal())<(int)rec.size()?Check1(vec,lim):Check2(vec,lim));
}

时间复杂度 \(O(n\sqrt{n}\log_2{n})\)

int n;
int a[N],b[N],ans[N];
Vi vec[N];

int Check1(const Vi &vec,const int &lim) {
	int cnt(0),sta(0);
	for(const int &x:vec)if(!sta||x-sta>lim)++cnt,sta=x;
	return cnt;
}

int Check2(const Vi &vec,const int &lim) {
	int cnt(0),sta(vec.front());
	while(true) {
		++cnt;
		int it(upper_bound(vec.begin(),vec.end(),sta+lim)-vec.begin());
		if(it==(int)vec.size())break;
		sta=vec[it];
	}
	return cnt;
}

unordered_map<int,int> rec;

int Check(const Vi &vec,const int &lim) {
	if(rec.count(lim))return rec[lim];
	auto cal=[&]() -> ll { return (ll)vec.size()*lim/n; };
	return rec[lim]=(cal()<20&&(1<<cal())<(int)vec.size()?Check1(vec,lim):Check2(vec,lim));
}

void Solve_Min(const Vi &vec) {
	for(int L(1),R(1),c(Check(vec,1)); L<=n; c=Check(vec,L=++R)) {
		for(int l(L+1),r(n),mid((l+r)>>1); l<=r; mid=(l+r)>>1)
			Check(vec,mid)>=c?R=mid,l=mid+1:r=mid-1;
		ans[L]+=c,ans[R+1]-=c;
	}
	rec.clear();
}

signed main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	I(n);
	FOR(i,1,n)I(a[i]),b[i]=a[i];
	sort(b+1,b+n+1),b[0]=unique(b+1,b+n+1)-b-1;
	FOR(i,1,n)a[i]=lower_bound(b+1,b+b[0]+1,a[i])-b;
	FOR(i,1,n)vec[a[i]].push_back(i);
	FOR(i,1,b[0])Solve_Min(vec[i]);
	FOR(i,1,n)O(ans[i]+=ans[i-1],'\n');
	return 0;
}

\(O(n\sqrt{n\log_2{n}})\)

发现其实相同颜色中 \(x\le \sqrt{n}\) 的部分,直接暴力求解反而更快,这部分复杂度变成了 \(O(n\sqrt{n})\)

进一步调整块长,发现如果 \(x\le \sqrt{n\log_2{n}}\) 的部分都暴力求解,复杂度就变成了 \(O(n\sqrt{n\log_2{n}})\)

void Solve(const Vi &vec) {
	FOR(i,1,Bl) {
		int tmp(Check1(vec,i));
		ans[i]+=tmp,ans[i+1]-=tmp;
	}
	for(int L(Bl+1),R(Bl+1),c(Check(vec,Bl+1)); L<=n; c=Check(vec,L=++R)) {
		for(int l(L+1),r(n),mid((l+r)>>1); l<=r; mid=(l+r)>>1)
			Check(vec,mid)>=c?R=mid,l=mid+1:r=mid-1;
		ans[L]+=c,ans[R+1]-=c;
	}
	rec.clear();
}

signed main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	I(n),Bl=ceil(sqrt(n*log2(n)));
	FOR(i,1,n)I(a[i]),b[i]=a[i];
	sort(b+1,b+n+1),b[0]=unique(b+1,b+n+1)-b-1;
	FOR(i,1,n)a[i]=lower_bound(b+1,b+b[0]+1,a[i])-b;
	FOR(i,1,n)vec[a[i]].push_back(i);
	FOR(i,1,b[0])Solve(vec[i]);
	FOR(i,1,n)O(ans[i]+=ans[i-1],'\n');
	return 0;
}

\(O(n\sqrt{n})\)

要求一个整数序列 \((a_1,a_2,\ldots,a_n)\)。已知:

  • 它是非递增的,即 \(a_k \ge a_{k+1}\)
  • \(0 \le a_k \le \frac{n}{k}\)

可以在 \(O(C)\) 时间内得到 \(a_k\) 的一个值,则可以在 \(O(n+\sqrt{n}C)\) 的复杂度内求出整个序列。

——Determine A Non-Increasing Integer Sequence of Length n with a[k]<=n/k in O(sqrt(n)) Steps - Codeforces

我们仿照上面那篇博文分析一下复杂度:

\(a_0=n+1,a_{n+1}=0\),然后开始分治:

假设现在分治区间为 \([l,r]\),则:

  • 如果 \(a_{l-1}=a_{r+1}\),则全部赋为同值。
  • 否则求出 \(a_{mid}\),然后递归 \([l,mid),(mid,r]\)

那么对于深度为 \(d\) 的一层,求值操作数量是 \(O(2^{\frac{d}{2}})\) 级别的。

所以总复杂度为:

\[\sum_{d=0}^{\lceil \log_2{n} \rceil} 2^{\frac{d}{2}} \\ \]

\(O(\sqrt{n})\)。那么我们也这么干就可以了。

//#define Plus_Cat ""
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define Vi vector<int>
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define EDGE(g,i,x,y) for(int i=(g).h[(x)],y=(g)[(i)].v;~i;y=(g)[(i=(g)[i].nxt)>0?i:0].v)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);return Main();}signed Main
using namespace std;
constexpr int N(1e5+10);

namespace IOEcat {
	//...
} using namespace IOEcat;

int n,Bl;
int a[N],b[N],ans[N];
Vi vec[N];

int Check1(const Vi &vec,const int &lim) {
	int cnt(0),sta(0);
	for(const int &x:vec)if(!sta||x-sta>lim)++cnt,sta=x;
	return cnt;
}

int Check2(const Vi &vec,const int &lim) {
	int cnt(0),sta(vec.front());
	while(true) {
		++cnt;
		int it(upper_bound(vec.begin(),vec.end(),sta+lim)-vec.begin());
		if(it==(int)vec.size())break;
		sta=vec[it];
	}
	return cnt;
}


int Check(const Vi &vec,const int &lim) {
	auto cal=[&]() -> ll { return (ll)vec.size()*lim/n; };
	return cal()<20&&(1<<cal())<(int)vec.size()?Check1(vec,lim):Check2(vec,lim);
}

void Sep(int l,int r,int vl,int vr,const Vi &vec) {
#define mid ((l+r)>>1)
	if(l>r)return;
	if(vl==vr)return ans[l]+=vl,ans[r+1]-=vr,void();
	int vm(Check(vec,mid));
	ans[mid]+=vm,ans[mid+1]-=vm,Sep(l,mid-1,vl,vm,vec),Sep(mid+1,r,vm,vr,vec);
#undef mid
}

signed main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	I(n),Bl=ceil(sqrt(n*log2(n)));
	FOR(i,1,n)I(a[i]),b[i]=a[i];
	sort(b+1,b+n+1),b[0]=unique(b+1,b+n+1)-b-1;
	FOR(i,1,n)a[i]=lower_bound(b+1,b+b[0]+1,a[i])-b;
	FOR(i,1,n)vec[a[i]].push_back(i);
	FOR(i,1,b[0])Sep(1,n,n,1,vec[i]);
	FOR(i,1,n)O(ans[i]+=ans[i-1],'\n');
	return 0;
}
posted @ 2025-05-07 21:58  Add_Catalyst  阅读(9)  评论(0)    收藏  举报