三晋七校第一届新生赛(同步赛)

C

\(x=i\times j\)\(f(x)=p=(i\times j)^2=x^2\)\(i,j\geq 1\)),\(f(x)\)的所有取值构成可重集和\(S\),同时\(S\)单调不减的,现在求\(S\)中前\(k\)小的元素之和。

因为\(k\)比较大,显然我们不能直接暴力枚举。我们尝试模拟一下会发现:

x=1,(i,j)组合1种
x=2,(i,j)组合2种
x=3,(i,j)组合2种
......
x=v,(i,j)组合等于cnt[fac[v]]
也就是x=v的(i,j)组合数等于x的因数个数

\(g(x)=\sum_{i=1}^x{cnt[i]}\),其中\(cnt[i]\)表示\(i\)的因数个数。我们需要找到最大的那个\(x\),满足\(g(x)\leq k\),这个可以通过二分答案,然后每次\(check\)使用分块解决,时间复杂度为\(O(\sqrt{V}\log {V})\)

找到上界后,我们需要解决\(\sum_{i=1}^{x}{i^2\times cnt[i]}\),也就是\(\sum_{i=1}^{x}\sum_{d=1}^{i}{i^2}\times [d\mid i]\),如果这样写表示每次枚举数\(num\),然后枚举其因数,时间复杂度为\(O(V\times \sqrt{V})\),显然会爆掉。

我们交换一下枚举顺序:\(\sum_{d=1}^{x}\sum_{k=1}^{\lfloor\frac{x}{d}\rfloor}{(kd)^2}=\sum_{d=1}^{x}d^2\sum_{k=1}^{\lfloor\frac{x}{d}\rfloor}{k^2}\),令\(m=\lfloor\frac{x}{d}\rfloor\),那么\(\sum_{k=1}^{m}{k^2}=\frac{n\times (n+1)\times(2\times n+1)}{6}\)

我们可以根据\(d\)\(\lfloor\frac{x}{d}\rfloor\)进行分块,也就是\(\lfloor\frac{x}{l}\rfloor=\lfloor\frac{x}{r}\rfloor\),然后对于相同的\(m\),令\(h(x)=\sum_{i=1}^x{i^2}\),式子变为\(\sum{(h(r)-h(l-1)\times h(m))}\)

注意要及时取模就好,最后我们再加上\((k-g(x))\times x^2\)就好。

#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef  long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 998244353;
//求[1,n]的约数个数之和
int inv_6;
int f(int n){
	int sum=0;
	for(int l=1,r;l<=n;l=r+1){
		r=n/(n/l);
		sum+=n/l*(r-l+1);
	}
	return sum;
}
int ksm(int b,int p){
	int res=1;
	while(p){
		if(p&1) res=res*b%mod;
		b=b*b%mod;
		p>>=1;
	}
	return res;
}
//g(n)为[1,n]所有数平方和
int g(int n){
	return n%mod*(n%mod+1)%mod*(n%mod*2%mod+1)%mod*inv_6%mod;
}
void solve() {
	int k;
	cin >> k;
	inv_6=ksm(6,mod-2);
	int lo=1,hi=1e12;
	int lim=1;
	//求
	auto check=[&](int mid)->bool{
		return f(mid)<=k;
	};
	while(lo<=hi){
		int mid=(lo+hi)/2;
		if(check(mid)){
			lim=mid;
			lo=mid+1;
		}else hi=mid-1;
	}
//	cout << lim << endl;
	//找到了上限,对前面求和
	int ans=0;
	for(int l=1,r;l<=lim;l=r+1){
		r=lim/(lim/l);
		int m=lim/l;
		ans=(ans+g(m)*((g(r)-g(l-1)+mod)%mod)%mod)%mod;
	}
	int r=k-f(lim);
	ans=(ans+r*(lim%mod+1)%mod*(lim%mod+1)%mod)%mod;
	cout << ans << endl;
}
/*

*/
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int t = 1;
//	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

H

最大化\(mex\oplus gcd\),很经典的讨论就是枚举\(mex\),我们只需要讨论\(mex=0,1\)\(mex>1\)的三种情况即可。

这道题有一个前置知识:

给定一个序列\(a\),现在有不可重集合\(S\)以及\(a\)的子集\(A\),那么\(s=(\sum_{v\in A}{v})\in S\),也就是说任选一个\(a\)的子集,其子集元素和\(sum\)\(S\)中的元素,求\(a\)的子集合最大不能表示的数\(mex\)

这个可以通过排序+贪心来解决,我们先对\(a\)进行排序,之后假设现在能够表示\([0,x]\),那么可以讨论现在枚举的数\(a_i\)

  • 如果\(a_i>x+1\),那么\(mex=x+1\)
  • 否则可表示范围为\([0,x+a_i]\)

这个时间复杂度是\(O(n\log{n})\)的。

sol

\(mex=0\)时,\(ans=sum[r]-sum[l-1]\)

\(mex=1\)时,\(ans=(sum[r]-sum[l-1])\oplus 1\)

\(mex\geq 2\)时,\(gcd=1\),我们只需要最大化\(mex\)就行,\(ans=mex\oplus 1\)

最后答案肯定在三种情况中的一种,前面只需要维护前缀和以及0的个数即可。

对于第三个,因为有\(q\)个询问,如果每次排序然后他贪心选择的话,时间复杂度为\(O(qn\log{n})\),显然不行。那么根据我们的前置知识,我们可以考虑使用数据结构来优化这个。

这里有一个很神奇的结论,就是这里\(mex\)的变化次数最多\(log{V}\)次,说是和斐波拉契数列的变化同阶。所以使用可持久化线段树的总复杂度为\(n\log^2{V}\)

法一:可持久化线段树

我们枚举最大覆盖值\(x\),那么每次查询在\([l,r]\)中,值小于等于\(x+1\)的元素之和\(res\),如果\(res=x\),那么最大覆盖值无法再增大,否则\(x=res\)。肯定有的是\(res\geq x\),所以当\(res=x\)时,我们退出。

#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef  long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 1e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
int root[N],tot;
int lc[N*32],rc[N*32],sum[N*32];
int a[N];
vector<int> v;
int n,m;
int build(int l,int r){
	int id=++tot;
	if(l==r) return id;
	int m=l+r>>1;
	lc[id]=build(l,m);
	rc[id]=build(m+1,r);
	return id;
}
int update(int prev,int l,int r,int pos,int add){
	int id=++tot;
	lc[id]=lc[prev];
	rc[id]=rc[prev];
	sum[id]=sum[prev]+add;
	if(l==r) return id;
	int m=l+r>>1;
	if(pos<=m) lc[id]=update(lc[prev],l,m,pos,add);
	else rc[id]=update(rc[prev],m+1,r,pos,add);
	return id;
}
int query(int x,int y,int l,int r,int ql,int qr){
	if(ql>qr) return 0;
	if(ql<=l&&r<=qr) return sum[y]-sum[x];
	int m=l+r>>1;
	int res=0;
	if(ql<=m) res+=query(lc[x],lc[y],l,m,ql,qr);
	if(qr>m) res+=query(rc[x],rc[y],m+1,r,ql,qr);
	return res;
}

void solve() {
	cin >> n;
	vector<int> sm(n+1),zero(n+1);
	for(int i=1;i<=n;i++){
		cin >> a[i];
		v.push_back(a[i]);
		sm[i]=sm[i-1]+a[i];
		zero[i]=zero[i-1]+(a[i]==0?1:0);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	auto get_id=[&](int x){
		return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
	};
	vector<int> id(n+1);
	for(int i=1;i<=n;i++){
		id[i]=get_id(a[i]);
	}
	int rsize=v.size();
	root[0]=build(1,rsize);
	for(int i=1;i<=n;i++){
		root[i]=update(root[i-1],1,rsize,id[i],a[i]);
	}
	cin >> m;
	while(m--){
		int l,r;
		cin >> l >> r;
		if(zero[r]-zero[l-1]==0){
			cout << sm[r]-sm[l-1] << endl;
			continue;
		}
		int s=0;
		while(1){
			//查询区间[l,r]小于等于need的值的和
			int pos=upper_bound(v.begin(),v.end(),s+1)-v.begin();
			int res=query(root[l-1],root[r],1,rsize,1,pos);
			if(res==s) break;
			s=res;
		}
		int mex=s+1;
		int ans=sm[r]-sm[l-1];
		if(mex>=1) ans=max(ans,1^(sm[r]-sm[l-1]));
		if(mex>1) ans=max(ans,1^mex);
		cout << ans << endl;
	}
}
/*

*/
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int t = 1;
//	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

法二:离线莫队+树状数组

我们对原问题进行离线,之后用树状数组维护前缀和,单次修改为\(\log{n}\),单次查询为\(\log{n}\),那么总时间复杂度应该为\(O(q\log{n}+n\sqrt{n}\log{n})\),按道理后面那个完全会超呀。但是这道题就是可以过,这就是\(mex\)的神奇性质嘛。

但是还是非常极限了时间879ms,所以还是用可持久化线段树好一点。

#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef  long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
struct Fenwick{
	vector<int> sum;
	int n;
	Fenwick(int n){this->n=n;sum.resize(n+1);}
	int lowbit(int x){return x&-x;}
	void update(int pos,int k){for(int i=pos;i<=n;i+=lowbit(i)) sum[i]+=k;}
	int query(int pos){int res=0;for(int i=pos;i;i-=lowbit(i)) res+=sum[i];return res;}
};
void solve() {
	int n,m;
	cin >> n;
	vector<int> a(n+1),zero(n+1),sum(n+1);
	for(int i=1;i<=n;i++){
		cin >> a[i];
		sum[i]=sum[i-1]+a[i];
		zero[i]=zero[i-1]+(a[i]==0);
	}
	vector<int> b=a;
	sort(b.begin(),b.end());
	b.erase(unique(b.begin(),b.end()),b.end());
	int rsize=b.size();
	vector<int> id(n+1);
	auto get_id=[&](int x){
		return lower_bound(b.begin(),b.end(),x)-b.begin()+1;
	};
	for(int i=1;i<=n;i++){
		id[i]=get_id(a[i]);
	}
	cin >> m;
	vector<array<int,3>> qu(m+1);
	for(int i=1;i<=m;i++){
		cin >> qu[i][0] >> qu[i][1];
		qu[i][2]=i;
	}
	int B=sqrt(n)+1;
	sort(qu.begin()+1,qu.end(),[&](array<int,3> x,array<int,3> y){
		auto [l1,r1,id1]=x;
		auto [l2,r2,id2]=y;
		if((l1-1)/B!=(l2-1)/B) return (l1-1)/B<(l2-1)/B;
		else return r1<r2;
	});
	Fenwick tr(rsize);
	auto del=[&](int pos){
		tr.update(id[pos],-a[pos]);
	};
	auto add=[&](int pos){
		tr.update(id[pos],a[pos]);
	};
	auto get=[&](int l,int r){
		int res=sum[r]-sum[l-1];
		if(zero[r]-zero[l-1]==0){
			return res;
		}
		int s=0;
		while(1){
			int pos=upper_bound(b.begin(),b.end(),s+1)-b.begin();
			int sm=tr.query(pos);
			if(sm==s) break;
			s=sm;
		}
		int mex=s+1;
		res=max(res,res^1);
		if(mex>1) res=max(res,mex^1);
		return res;
	};
	int cl=1,cr=0;
	vector<int> ans(m+1);
	for(int i=1;i<=m;i++){
		auto[l,r,id]=qu[i];
		while(l<cl) add(--cl);
		while(cr<r) add(++cr);
		while(l>cl) del(cl++);
		while(r<cr) del(cr--);
		ans[id]=get(l,r);
	}
	for(int i=1;i<=m;i++) cout << ans[i] << endl;
}
/*

*/
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int t = 1;
//	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

最后讨论一下为什么这里求\(mex\)的次数最多\(\log{V}\)次。

看一个例子:

初始:[0,-1]
加入0:[0,0]
加入1:[0,1]
加入2:[0,3]
加入4:[0,7]
加入8:[0,15]

很明显了,如果当前覆盖区间是\([0,x]\),那么下一次我们找的是\(x+1\),那么下一次覆盖范围为\([0,2\times x+1]\),这个就能够看出来了。

A

给定\(n\)个数排成一行,每5个数一组,将前面4个丢弃,保留第5个数,直到剩下的数小于4个,问最后剩下几个数,分别是多少。

模拟一下可以知道,当前是\(n\),那么剩下的数为\(n/5+n\%5\),我们记下每一轮的\(Q_j,r_j\),这样我们可以倒推:

如果当前剩下\(m\)个数,那么对于\(idx\in[1,m]\),可以分为下面两种情况:

  • 如果\(idx\le Q_j\),那么\(idx\)是整数分组中的,那么前一轮的位置为\(5\times idx\)
  • 如果\(idx>Q_j\),那么\(idx\)是最后余下的几个数中的,那么前一轮的位置为\(5\times Q_j+(idx-Q_j)\)

所以我们直接按照这个模拟就行,时间复杂度为\(O(\log{n})\)

#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef  long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
void solve() {
	int n;
	cin >> n;
	vector<int> num,Q;
	num.push_back(n);
	while(num.back()>=4){
		int v=num.back();
		int q=v/5;
		Q.push_back(q);
		int r=v%5;
		num.push_back(q+(r==4?0:r));
	}
	vector<int> ans;
	for(int i=1;i<=num.back();i++){
		int idx=i;
		for(int j=Q.size()-1;j>=0;j--){
			if(idx<=Q[j]) idx=idx*5;
			else idx=Q[j]*5+(idx-Q[j]);
		}
		ans.push_back(idx);
	}
	cout << ans.size() << endl;
	for(int i:ans) cout << i << " ";
	cout << endl;
}
/*

*/
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int t = 1;
//	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}
posted @ 2025-11-28 13:55  alij  阅读(5)  评论(0)    收藏  举报