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}\):
对于 \(\sum\limits_{i=1}^{t} \frac{(k-s-i\times x)(k-s-i\times x+1)}{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;
}

浙公网安备 33010602011771号