B. Make Them Equal
题目描述
解法
这道题需要发掘题目的特殊性质,你发现移动的石子必须是 \(i\) 的倍数,那么我们可以把所有石子移动到 \(1\),然后再分配,这整个过程的操作数不能超过 \(3n\)
我们是知道最后每个位置的石子有多少的,如果 \(\sum a_i\%n\not=0\) 就直接无解。
现在的问题是把所有的石子移动到 \(1\),需要 \(i\) 位置上的石子数是 \(i\) 的倍数才可以全部移动,所以我们可能会先把 \(1\) 的石子移给他然后再全部拿回来。注意到一个重要条件:\(a_i\geq 1\),并且模数是 \(i\),所以可以从左往右扫,扫到 \(i\) 的时候 \(1\) 和 \(i\) 石子总和一定是够 \(i\) 的,所以就可以把所有石子移到 \(1\),步数消耗 \(2n\)
最后消耗 \(n\) 步*分配即可。
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 300005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,a[M];
struct node
{
int x,y,z;
};vector<node> b;
void work()
{
n=read();m=0;b.clear();
for(int i=1;i<=n;i++)
{
a[i]=read();
m+=a[i];
}
if(m%n) {puts("-1");return ;}
m/=n;
for(int i=2;i<=n;i++)
{
if(a[i]%i)
b.push_back(node{1,i,i-a[i]%i});
b.push_back(node{i,1,(i+a[i]-1)/i});
}
for(int i=2;i<=n;i++)
b.push_back(node{1,i,m});
printf("%d\n",b.size());
for(int i=0;i<b.size();i++)
printf("%d %d %d\n",b[i].x,b[i].y,b[i].z);
}
signed main()
{
T=read();
while(T--) work();
}
C. XOR Inverse
题目描述
解法
这道题又是异或又是逆序对的直接把我整傻了,但是不要被吓到,我们考虑两个数什么时候会成为逆序对。
其实就是看是否满足 \(a_i\oplus x>a_j\oplus x\and i<j\),既然是位运算我们用二进制的思维去考虑:最高位决定大小关系。设 \(a_i,a_j\) 第一个不同的数位是 \(t\),那么它们一定就会在 \(x\) 的 \(t\) 这一位决出胜负,如果 \(a_i\) 这一位为 \(1\),\(a_j\) 这一位为 \(0\),那么 \(x\) 这一位为 \(0\) 的时候一定有逆序对否则一定没有逆序对。
那么每一对数是否产生逆序对就是在某一位上决定的,对于每一位是独立的,所以可以只要统计出这些数对第一个不同的数位,就可以逐位确定 \(x\) 了,统计的过程可以用 \(\tt trie\) 树实现,时间复杂度 \(O(n\log n)\)
#include <cstdio>
const int M = 300005;
const int N = 40*M;
#define ll long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,cnt,rt,jzm,a[M],ls[N],rs[N];ll zxy,s[N],b[31][2];
void ins(int d,int &x,int v)
{
if(!x) x=++cnt;
if(d==0)
{
s[x]++;
return ;
}
if(v&(1<<(d-1)))
{
b[d-1][1]+=s[ls[x]];
ins(d-1,rs[x],v);
}
else
{
b[d-1][0]+=s[rs[x]];
ins(d-1,ls[x],v);
}
s[x]=s[ls[x]]+s[rs[x]];
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
int x=read();
ins(30,rt,x);
}
for(int i=30;i>=0;i--)
{
if(b[i][0]<=b[i][1]) zxy+=b[i][0];
else zxy+=b[i][1],jzm+=(1<<i);
}
printf("%lld %d\n",zxy,jzm);
}
E. Split
题目描述
解法
\(\tt div1\) 的神题,让我重新认识了什么叫 \(dp\) 优化。
首先设计暴力 \(dp\),设 \(dp[i][j]\) 表示考虑 \(a_1\sim a_i\),结尾的 \(b_{2i}\) 是 \(j\),最大相邻相同的数对有多少个(最后减一下得到答案),这样做复杂度直接上天。这时候普通的竞赛选手(比如我)可能就去想别的状态定义了,好像这东西也优化不动啊。但现在不妨观察一下这个状态转移的特点:
- \(dp\) 的过程是分层的,我们把 \(dp[i]\) 这一层称为 \(cur\),\(dp[i+1]\) 这一层称为 \(nxt\),令 \(x=a_{i+1}\)
- \(nxt[i]\geq \max(cur)\),因为总可以找到一种方法从上一层的最大值转移过来。
- \(\max(nxt)-\min(nxt)\leq 2\),因为每次转移最多增加两对相邻相同数,这是本题的关键,能简化情况。
- \(\max(nxt)-\min(nxt)=2\) 要求 \(a_i\) 为偶,并且 \(nxt[a_i/2]\) 是这层 \(dp\) 值中唯一的最大值。
- 对于某些 \(i\) 我们能找到 \(cur[x-i]+1\) 转移到 \(nxt[i]\) 的方法。
- 如果 \(x\) 是偶数,与其在转移是分开讨论他,不如在转移结束之后给 \(nxt[x/2]\) 加 \(1\)
- 虽然每一层的转移 \(dp\) 值会有差异,但是经过一次转移之后所有 \(dp\) 值都会到一个新的基准值。
根据上面的观察,我们可以维护下面的东西来代替 \(dp\) 过程:
- \(zero\) 表示这一层所有 \(dp\) 值的基准线,也就是这一层的最小值。
- \(one\) 表示一个集合维护满足下列条件的下标 \(i\):\(cur[i]=zero+1\)
- \(two\) 表示这一层值为 \(zero+2\) 的下标,如果没有则为 \(-1\),有则为 \(a_i/2\)
现在考虑如何维护。第一种情况,上一层存在 \(two\),那么这一层的基准一定选他了(注意这里是不考虑 \(x/2\) 多出来的贡献 \(1\) 才能这么贪心),直接清空上一层维护的 \(one\),看 \(x-two\) 能不能加入 \(one\) 里面。
第三种情况,\(two,one\) 都不存在,那么可以把 \([1,\min(a_i,x)-1]\) 加入 \(one\) 中,因为这里我们是加入的一整段所以我们选择以区间的形式来维护 \(one\),看能不能保证复杂度。
第二种情况,上一层 \(one\) 非空,那么这一层的基准线一定选他了。然后要考虑这一层的 \(one\) 是怎么样的,上一层的元素 \(A\) 到这一层就变成了 \(x-A\),所以我们要求 \(A\leq x\),也就是我们要删除一段后缀。但是 \(A\rightarrow x-A\) 这个类似以 \(x\) 为轴的旋转操作有点难搞,可以考虑懒惰的旋转,手玩一下发现规律是 \(A,x-A,y-x+A,z-y+x-A...\),可以维护旋转值 \(shift\) 和 \(A\) 前面的符号 \(sign\),每次 \(shift=shift-x,sign\times-1\),那么每次就要根据 \(sign\) 的正负判断删除的是前缀还是后缀。
然后特殊考虑 \(x/2\) 这一位,首先从 \(one\) 里面去找他,如果找到了就是 \(two\),如果找不到就把他加入 \(one\) 中。
#include <cstdio>
#include <iostream>
#include <set>
using namespace std;
const int M = 500005;
#define make make_pair
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,a[M];
void work()
{
n=read();
set<pii> one;set<pii>::iterator it;
int ze=0,two=-1,sh=0,f=1,ans=2*n;
for(int i=1;i<=n;i++)
{
int x=read();a[i]=x;
if(two!=-1)
{
one.clear();sh=0;f=1;ze+=2;//based on TWO
if(x>two) one.insert(make(x-two,x-two));
}
else if(!one.empty())
{
ze++;//based on ONE
if(f==-1)
{
//sh-idx>=x -> idx<=sh-x is illegal
int lim=sh-x;
while(!one.empty() && one.begin()->se<=lim)
one.erase(one.begin());//delete the whole segment
if(!one.empty() && one.begin()->fi<=lim)//delete the part of the segment
{
it=one.begin();int tmp=it->se;
one.erase(it);
one.insert(make(lim+1,tmp));
}
}
else
{
//sh+idx>=x -> idx>=x-sh is illegal
int lim=x-sh;
while(!one.empty() && one.rbegin()->fi>=lim)
one.erase(--one.end());
if(!one.empty() && one.rbegin()->se>=lim)
{
int tmp=one.rbegin()->fi;
one.erase(--one.end());
one.insert(make(tmp,lim-1));
}
}
sh=x-sh;
f*=-1;//we rotate lazily
}
else//every element is the same
{
f=-1;sh=x;
int lim=min(a[i-1]-1,x-1);
if(1<=lim) one.insert(make(1,lim));
}
//here consider x/2
two=-1;
if(x%2==0)
{
int y=x/2,val=(y-sh)/f,pd=0;//we reflect the value in ONE
it=one.lower_bound(make(val+1,(int)-1e18));//attention
if(it!=one.begin())
{
it--;
if(it->fi<=val && val<=it->se) pd=1;
}
if(pd) two=x/2;//we find TWO
else one.insert(make(val,val));//otherwise add it into ONE
}
}
if(two!=-1) ans-=ze+2;
else if(!one.empty()) ans-=ze+1;
else ans-=ze;
printf("%lld\n",ans);
}
signed main()
{
T=read();
while(T--) work();
}