MX 暑假集训 8.6

T1: [UOI 2023] An Array and Addition Again

题意

给定一个长度为 \(100\) 的数组 \(a\),下标从 \(1\) 开始,初始时 \(a_{100}=1\),其余位置均为 \(0\),每次操作你需要给出整数 \(p\) 满足 \(1\le p<100\),然后 \(a_p\leftarrow a_p+a_{p+1}\),初始时给定 \(n\),你需要在 \(300\) 次操作内将 \(a_1\) 变成 \(n\),构造方案。

\(1\le n\le 10^{18}\)

solution

考虑先使这个数组所有位置元素的和为 \(n\),然后做后缀和。

容易想到二进制拆分,这个数组不为 \(0\) 的所有位置满足单调不增且元素均为 \(2^k\),一个例子是 \(a=[0,0,0,\dots,8,8,4,4,2,1]\)

从后往前填,令当前需要填入位置为 \(x\)\(a_{x+1}=2^k\),并且当前后缀能够表示 \(n\) 二进制下低位的一些 \(1\),最低位未被表示的 \(1\)\(2^d\),那么如果 \(k+1=d\),那么在当前位置执行两次操作,则当前位置变成 \(2^{k+1}\),正好表示了 \(2^d\),后面递归构造即可,否则同样在 \(x\) 填入 \(2^{k+1}\),然后在 \(x-1\) 执行一次操作,这样 \(x\)\(x-1\) 位置的和就构成了 \(2^{k+1}\),然后在 \(x-2\) 的位置执行两次操作,那么和就变成了 \(2^{k+2}\),同理继续向前构造,构造出段的权值和为 \(2^d\) 即可。

发现这样最劣的情况可能是:\(a=[63,63,62,62,61,61,\dots,3,3,2,2,1,1]\),发现这样需要占用 \(2\log V\) 个位置,不可接受。

考虑优化一下,发现若 \(d=k+2\),那么只需要在位置 \(x\) 进行四次操作即可,这样只需要占用一个位置,并且用四次操作使得 \(k\leftarrow k+2\),一定不劣。

加上这个优化后,发现在两个相同的数前面位置上的数一定只出现一次,所以最多会占用 \(1.5\log V\) 个位置,可以接受。

\(k\) 最多增加 \(\log V\) 次,每次增加最坏情况下会消耗 \(2.5\) 次操作,所以最后的次数为 \(2.5\log V+99\) 次,可以通过。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=100+10;
constexpr int inf=1e9+10;

inline int calc(ll x) {
	ll k=(x&-x);
	return __builtin_ctzll(k);
}

vector<int> ans;
inline void out(int x) {ans.push_back(x);}

inline void solve() {
	ll n;
	scanf("%lld",&n);
	int d=calc(n),now=0,flag=0;
	if (d==0) {
		n^=(n&-n);
		d=calc(n);
	}
	else flag=-1;
	F(i,99,1) {
		if (!n) break;
		if (flag==-1) out(i),flag=1;
		else if (flag==0&&now+1!=d) {
			if (now+2==d) out(i),out(i),out(i),out(i),now+=2;
			else out(i),out(i),now++,flag=-1;
		}
		else out(i),out(i),now++;
		if (now+flag==d) {
			n^=(n&-n);
			d=calc(n);
			flag=0;
		}
		if (!n) break;
	}
	F(i,99,1) out(i);
	printf("%d\n",(int)(ans.size()));
	for (int x:ans) printf("%d ",x);
	putchar(10);
	ans.clear();
}

int main() {
	int C,T;
	scanf("%d%d",&T,&C);
	while (T--) solve();
	return 0;
}

T2: [CEOI 2021] Diversity

题意

一个序列的权值定义为其所有子区间不同元素个数之和,给定一个长度为 \(n\) 的序列,\(q\) 次询问,每次给定 \(l,r\),求将 \([l,r]\) 内所有元素重排后得到的序列权值最小值。

\(1\le n\le 3\times 10^5,1\le q\le 5\times 10^4\)

solution

\(10\) 分钟把单组 \(O(n\log n)\) 的暴力打了,优化用了 \(3\) 小时。

贪心,显然会将同一种元素放在一个段内,所以我们只关心最后每种元素出现次数所组成的数组。

我们令这个数组为 \(a\),长度为 \(m\),那么我们需要重排 \(a\),使得 \(\sum\limits_{i=1}^{m}\sum\limits_{j=i+1}^{m}(j-i+1)\times a_i\times a_j+\sum\limits_{i=1}^{m}\frac{a_i\times (a_i+1)}{2}\) 最小,前半部分为跨组贡献,后半部分为组内贡献,显然组内贡献固定,所以只需要最小化跨组贡献。

发现将 \(a\) 从小到大排序,从左到右依次将当前元素分到最终数组的左边和右边,即第一个元素放到最终数组的第一个位置,第二个元素放到最后一个位置,第三个元素放到第二个位置,第四个元素放到倒数第二个位置,以此类推,这个分配的方式看起来非常优秀。

经过打表发现这样做是正确的,证明似乎有点复杂,笔者不会。

由于我们需要维护的东西与元素出现次数相关,这个数据范围启发我们想到莫队,但是似乎只能 \(O(1)\) 移动指针,不能配合对数相关的数据结构在移动指针时动态维护。

考虑移动指针时只维护数组 \(a\)

对于数组 \(a\) 内的一个元素 \(x\),令其最终数组 \(x\) 前面元素的和为 \(s\)\(k=r-l+1\),那么贡献也可以写成 \(\frac{k\times (k+1)}{2}-\frac{s\times (s+1)}{2}-\frac{(k-s-x)\times(k-s-x+1)}{2}\),其意义为对于所有与当前颜色段有交的子区间都造成 \(1\) 的贡献。

发现这个东西可以根号分治一下,设定阈值 \(B\),对于数组 \(a\) 内不超过 \(B\) 的所有元素 \(x\),同时处理所有值为 \(x\) 造成的贡献,令前面的元素和为 \(s\)\(k=r-l+1\),需要放入连续 \(t\)\(x\),那么由上面的贡献,\(\sum \frac{k\times (k+1)}{2}\) 可以 \(O(1)\) 快速计算,只需要处理 \(\sum\limits_{i=1}^{t}\frac{(s+(i-1)\times x)(s+(i-1)\times x+1)}{2}+\sum\limits_{i=1}^{t} \frac{(k-s-i\times x)(k-s-i\times x+1)}{2}\)

发现这两个式子都可以展开成 \(\frac{k_1\times t+k_2\times \sum\limits_{i=1}^{t}i+k_3\times \sum\limits_{i=1}^{t}i^2}{2}\) 的形式。

具体地,对于 \(\sum\limits_{i=1}^{t}\frac{(s+(i-1)\times x)(s+(i-1)\times x+1)}{2}\)

\[k_1=s^2-2\times s\times x+s+x^2-x \]

\[k_2=2\times s\times x+x-2\times x^2 \]

\[k_3=x^2 \]

对于 \(\sum\limits_{i=1}^{t} \frac{(k-s-i\times x)(k-s-i\times x+1)}{2}\)

\[k_1=k^2-2\times k\times s+k+s^2-s \]

\[k_2=2\times s\times x-x-2\times k\times x \]

\[k_3=x^2 \]

于是我们可以预处理 \(\sum\limits_{i=1}^{t}i\)\(\sum\limits_{i=1}^{t}i^2\)\(O(1)\) 求出每种元素的贡献。

对于 \(a\) 中大于 \(B\) 的元素,最多有 \(\frac{n}{B}\) 个,暴力处理即可。

对于大于阈值 \(B\) 的元素,需要进行一次排序使它们有序,根号平衡可以得到最终复杂度为 \(O(n\sqrt q+q\sqrt {n\log n})\)

可以用链表或基数排序优化到 \(O(n\sqrt q+q\sqrt n)\),不过不优化同样可以通过。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=3e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m;
int a[N],cnt[N];

constexpr int B=1000;
ll s1[N],s2[N],ans[N];
inline ll calc1(__int128 s,__int128 x,__int128 t) {return (long long)((s*s-2*s*x+s+x*x-x)*t+(2*s*x+x-2*x*x)*s1[t]+x*x*s2[t])/2;}
inline ll calc2(__int128 s,__int128 x,__int128 t,__int128 k) {return (long long)((k*k-2*k*s+k+s*s-s)*t+(2*s*x-x-2*k*x)*s1[t]+x*x*s2[t])/2;}

int mp[N],t[N];
struct ques{int l,r,id;}q[N];
vector<int> num;

inline bool cmp(ques x,ques y) {
	int idx=(x.l-1)/B+1,idy=(y.l-1)/B+1;
	if (idx^idy) return idx<idy;
	return (idx&1)?(x.r<y.r):(x.r>y.r);
}

inline void movein(int x) {t[mp[x]]--;mp[x]++;t[mp[x]]++;}
inline void remove(int x) {t[mp[x]]--;mp[x]--;t[mp[x]]++;}

inline __int128 calc(int val,int num,int len,int &lenL,int &lenR,int &tL,int &tR) {
	int L=num/2,R=num-L;
	if (tL==tR) swap(L,R);
	__int128 ret=0;
	ret+=calc1(lenL,val,L)+calc2(lenL,val,L,len);
	ret+=calc1(lenR,val,R)+calc2(lenR,val,R,len);
	lenL+=val*L;lenR+=val*R;tL+=L;tR+=R;
	return ret;
}

inline ll work(int len) {
	int lenL=0,lenR=0,tL=0,tR=0;
	__int128 sum=0,lens=1ll*len*(len+1)/2;
	f(i,1,B) if (t[i]) sum+=t[i]*lens-calc(i,t[i],len,lenL,lenR,tL,tR);
	vector<int> tmp;
	for (int x:num) if (mp[x]>B) tmp.push_back(mp[x]);
	sort(tmp.begin(),tmp.end());
	for (int x:tmp) sum+=lens-calc(x,1,len,lenL,lenR,tL,tR);
	return (long long)(sum);
}

int main() {
	read(n);read(m);
	f(i,1,n) read(a[i]);
	f(i,1,n) s1[i]=s1[i-1]+i;
	f(i,1,n) s2[i]=s2[i-1]+1ll*i*i;
	f(i,1,n) cnt[a[i]]++;
	f(i,1,3e5) if (cnt[i]>600) num.push_back(i);
	f(i,1,m) {
		int l,r;read(l);read(r);
		q[i]={l,r,i};
	}
	sort(q+1,q+1+m,cmp);
	int L=1,R=1;movein(a[1]);
	f(i,1,m) {
		while (L>q[i].l) movein(a[--L]);
		while (R<q[i].r) movein(a[++R]);
		while (L<q[i].l) remove(a[L++]);
		while (R>q[i].r) remove(a[R--]);
		ans[q[i].id]=work(R-L+1);
	}
	f(i,1,m) printf("%lld\n",ans[i]);
	return 0;
}

T3: 01tree

posted @ 2026-08-23 11:10  Ff472130  阅读(11)  评论(0)    收藏  举报