EDU 126
A
题意:
给定两个数组\(A\)和\(B\),可以对于任意\(i\)交换\(a_i\)和\(b_i\),最小化\(\sum_{i=1}^{n-1}|a_i-a_{i+1}|+|b_i-b_{i+1}|\)的值
\(n\leq 25,a_i,b_i\leq 10^9\)
题解:
只要求\(\sum_{i=1}^{n-1}min\{|a_i-a_{i+1}|+|b_i-b{i+1}|,|a_i-b_{i+1}|+|b_i-a_{i+1}|\}\)
读者自证不难
考虑如果这次交换了,对下一次没有任何影响,因为假如在考虑\(x,x+1\)时,我们把\(x+1\)位置上的\(a_{x+1},b_{x+1}\)交换了,那么只要把\(a_{x+2},b_{x+2}\)再换以下,就和原数列等价了,所以没有后效性,直接贪心。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=5e5+10,mod=1e9+7,inf=INT_MAX;
int n,ans;
int a[N],b[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n;ans=0;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
for(int i=1;i<n;++i)
{
ans+=min(abs(a[i]-a[i+1])+abs(b[i]-b[i+1]),abs(a[i]-b[i+1])+abs(b[i]-a[i+1]));
}
cout<<ans<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
10
4
5
6
7
8
9
10
11
12
13
*/
B
题意:
给定一个数\(x\),你可以进项两个操作:
求最小操作次数,让\(x\)成为\(32768\)的倍数
\(0\leq x<32768,1\leq T\leq 32768\)
题解:
\(32768=2^{15}\)
考虑让\(x\)二进制最低位在\(15\)位及以上
考虑树状数组的某个操作,\(lowbit(x)\)可以得到\(x\)的二进制下最低位。
那么一直乘\(2\)把这个最低位干到\(15\)位去
要么通过\(+1\)把这个最低位干掉
特判\(x=0\)
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=5e5+10,mod=1e9+7,inf=INT_MAX;
int n;
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n;
if(!n)
{
cout<<"0 ";
continue;
}
int ans=0,ret=inf;
while(lowbit(n)!=n)
{
int tmp=lowbit(n),s=ans;
while(tmp<(1<<15)) tmp<<=1,++s;
ret=min(ret,s);
ans+=lowbit(n);
n+=lowbit(n);
}
while(n<(1<<15)) n<<=1,++ans;
ret=min(ret,ans);
cout<<ret<<' ';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
10011
*/
C
题意:
一条街上有\(n\)棵树,每棵树高度是\(h_i\),你想让他们都长到相同高度。
第奇数天可以把某棵树高度\(+1\),偶数天\(+2\),问最少要多少天。
题解:
设最高的树高度是\(dep\)
(很明显)最后所有树的高度要么是\(dep\),要么是\(dep+1\)
分别讨论,然后二分答案(所需要的天数)。
至于怎么检查答案,二分出天数之后可以知道自己能用多少个\(+1\)和多少个\(+2\)
先用\(+1\)把树的高度和目标高度奇偶性不同的树垫上
然后用\(+1\)和\(+2\)随便垫,优先\(+2\),最后两个都不超标就行。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=5e5+10,mod=1e9+7,inf=INT_MAX;
int n,ans;
int a[N],b[N];
inline bool check(int x,int y)
{
int s1=(x+1)/2,s2=x/2;
for(int i=1;i<=n;++i)
{
b[i]=y-a[i];
if(b[i]&1) --s1,--b[i];
}
for(int i=1;i<=n;++i)
{
int tmp=min(b[i]/2,s2);
b[i]-=2*tmp;
s2-=tmp;
s1-=b[i];
}
return s1>=0&&s2>=0;
}
inline int work(int x)
{
int l=0,r=1e15;
while(l<=r)
{
if(check(mid,x)) r=mid-1;
else l=mid+1;
}
return r+1;
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n;ans=inf;
for(int i=1;i<=n;++i) cin>>a[i];
sort(a+1,a+n+1);
ans=min(work(a[n]),work(a[n]+1));
cout<<ans<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
10
4
5
6
7
8
9
10
11
12
13
*/
D
题意:
给定一个长度为\(n\)的全零的序列\(A\)和序列\(B\),每次操作可以选择一段长度为\(m\)的区间,让第一个位置\(+1\),第二个位置\(+2\)……最后一个位置\(+m\)。
问最少多少次操作才能对于所有\(i\),满足\(a_i\geq b_i\)
\(n\leq 3*10^5,b_i\leq 10^{12}\)
题解:
肯定是倒着想,因为区间最后一个位置加的最多,优先让最后面的位置满足要求,前面不够再另加。
也就是先让\([a_{n-m+1},a_n]\)这段范围加数,加到\(a_n\geq b_n\)为止,然后再考虑倒数第二个位置。
不过对于\(i<m\)的地方,要特殊判断,因为没法用\(m\)给这些位置加数,只能以\(1\)为起点。
下面是怎么进行区间加\(1,2,3,…,m\)
反正可以线段树区间加等差数列
考虑一个差分数列,假如一开始全是零:\(0,0,0,0,0……\)
然后给第一个位置\(+1\):\(1,0,0,0,0……\)
把这个序列做一次前缀和:\(1,1,1,1,1……\)
再做二阶前缀和:\(1,2,3,4,5……\)
搞定,维护一个差分序列,然后求二阶前缀和。
其实二阶前缀和有点麻烦,所以我们可以直接维护一阶前缀和,然后再求这个一节前缀和的前缀和。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=5e5+10,mod=1e9+7,inf=INT_MAX;
int n,m,ret;
int a[N],b[N],c[N];
int tr[N];
struct segment_tree
{
int ans[N<<2],tag[N<<2];
inline void pushdown(int l,int r,int p)
{
ans[ls(p)]+=(mid-l+1)*tag[p];
ans[rs(p)]+=(r-mid)*tag[p];
tag[ls(p)]+=tag[p];
tag[rs(p)]+=tag[p];
tag[p]=0;
}
inline void update(int tl,int tr,int l,int r,int p,int k)
{
//cout<<l<<' '<<r<<' '<<tl<<' '<<tr<<"!!"<<endl;
if(tl<=l&&r<=tr)
{
ans[p]+=k*(r-l+1);
tag[p]+=k;
return;
}
if(tag[p]) pushdown(l,r,p);
if(tl<=mid) update(tl,tr,l,mid,ls(p),k);
if(tr>mid) update(tl,tr,mid+1,r,rs(p),k);
ans[p]=ans[ls(p)]+ans[rs(p)];
}
inline int query(int tl,int tr,int l,int r,int p)
{
if(tl<=l&&r<=tr) return ans[p];
if(tag[p]) pushdown(l,r,p);
int sum=0;
if(tl<=mid) sum+=query(tl,tr,l,mid,ls(p));
if(tr>mid) sum+=query(tl,tr,mid+1,r,rs(p));
return sum;
}
}T;
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int TT;TT=1;
while(TT--)
{
cin>>n>>m;ret=0;
for(int i=1;i<=n;++i)
{
cin>>b[i];
c[i]=b[i];
}
for(int i=n;i-m+1>=1;--i)
{
int sum=T.query(1,i,1,n,1);
if(sum<c[i])
{
int tmp=(c[i]-sum-1)/m+1;
T.update(i-m+1,i,1,n,1,tmp);
if(i+1<=n) T.update(i+1,i+1,1,n,1,-m*tmp);
ret+=tmp;
}
}
for(int i=m-1;i>=1;--i)
{
int sum=T.query(1,i,1,n,1);
//cout<<i<<' '<<sum<<"!!"<<endl;
if(sum<c[i])
{
int tmp=(c[i]-sum-1)/i+1;
T.update(1,m,1,n,1,tmp);
if(m+1<=n) T.update(m+1,m+1,1,n,1,-m*tmp);
ret+=tmp;
}
}
cout<<ret<<'\n';
//3 4 5
//5-3+1
}
}
}
signed main()
{
red::main();
return 0;
}
/*
10
4
5
6
7
8
9
10
11
12
13
*/
E
题意:
有\(3*n\)的矩阵,每个位置上是空或者满。
\(m\)次询问,每次问第\(l\)列到第\(r\)列有多少个空着的连通块。
\(n\leq 5*10^5,m\leq 3*10^5\)
题解:
非常逆天。
先前缀和求到\(i\)为止,\(1\sim i\)这三行中有多少个位置空着。
然后考虑删除重复计算的连通块的贡献。
如果这个格子和上一行的格子联通,那贡献肯定没了,要减!
如果这个格子和左边的格子联通,贡献不一定要减:考虑一个\(2*2\)的联通格子,一开始贡献是\(4\),减去和上面联通的情况,减\(2\),如果再减去和左边联通的情况,又减\(2\),就减没了。
但是我们可以用并查集维护一下,如果我在和左边格子合并的时候,本来就在一起,就不减,否则就减他丫滴。
把和上面联通的贡献记在当前位置,做一个前缀和\(cnt1\)
把和左边联通的贡献记在左边,做前缀和\(cnt2\)
现在前缀和相减就行了嘛?并不是!
因为如果最左边是:空满空,你不好说这两个空是一个还是两个。
要找这个“空满空”右边第一个不是空满空的位置决定。
这个可以预处理然后\(O(1)\)找到。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=1e6+10,mod=1e9+7,inf=INT_MAX;
int n,m;
int a[N];
char s[4][N];
int pre[N];
int cnt1[N],cnt2[N];
int nxt[N];
int ret[N];
int f[N];
inline int find(int k){return f[k]==k?k:f[k]=find(f[k]);}
inline bool merge(int x,int y)
{
x=find(x),y=find(y);
if(x==y) return 0;
f[x]=y;
return 1;
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=3*n;++i) f[i]=i;
for(int i=0;i<3;++i) cin>>(s[i]+1);
for(int i=1;i<=n;++i)
{
pre[i]=pre[i-1];
for(int k=0;k<3;++k) pre[i]+=(s[k][i]=='1');
//cout<<pre[i]<<' ';
}
//cout<<endl;
for(int i=1;i<=n;++i)
{
for(int k=0;k<3;++k)
{
if(k<2&&s[k][i]=='1'&&s[k+1][i]=='1'&&merge(k*n+i,k*n+n+i))
{
++cnt1[i];
}
if(i>1&&s[k][i]=='1'&&s[k][i-1]=='1'&&merge(k*n+i,k*n+i-1))
{
++cnt2[i-1];
}
}
}
for(int i=1;i<=n;++i) cnt1[i]+=cnt1[i-1],cnt2[i]+=cnt2[i-1];
for(int i=n;i>=1;--i)
{
bool flag=(s[0][i]=='1'&&s[1][i]=='0'&&s[2][i]=='1');
if(flag) nxt[i]=nxt[i+1]+1;
else nxt[i]=0;
}
cin>>m;
for(int i=1;i<=m;++i)
{
int l,r;
cin>>l>>r;
int nxt101=l+nxt[l];
if(nxt101>r)
{
cout<<2<<'\n';
continue;
}
int tot=pre[r]-pre[nxt101-1];
int cnt=(cnt1[r]-cnt1[nxt101-1])+(cnt2[r-1]-cnt2[nxt101-1]);
int ret=tot-cnt;
//cout<<r<<' '<<l-1<<' '<<pre[r]<<' '<<pre[l-1]<<"!!!\n";
if(nxt101!=l)
{
if(s[0][nxt101]=='1'&&s[1][nxt101]=='1'&&s[2][nxt101]=='1');
else if(s[0][nxt101]=='0'&&s[2][nxt101]=='0') ret+=2;
else ++ret;
}
cout<<ret<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
12
100101011101
110110010110
010001011101
1
8 11
5
11101
10110
11101
1
1 4
*/
F
题意:
在\(a_0\sim a_n\)这\(n+1\)个位置上有传送门\((a_0=0,a_i<a_{i+1})\),在两个传送门之间传送的代价是\(dis^2\)。
问最少加几个传送门,让从\(a_0\)到\(a_n\)的代价不超过\(m\)?
\(n\leq 2*10^5,m\leq 10^{18}\)
题解:
为了最小化代价,肯定不会跳过某个传送门。
所以在每个空隙之间加传送门互不影响。
假如在长度为\(x\)的空隙之间加\(k\)个传送门,那么最优的方案是让分出的\(k+1\)个空隙长度尽可能相等(证明略。)
设\(f(x,k)\)是在长度为\(x\)的空隙之间加\(k\)个传送门后的最小花费。
那么根据经验有单调性:
有一个并不显然的做法:二分数值\(x\),表示\(f(x,k)-(x,k+1)>=x\)
然后\(check\)每个空隙的花费加起来是不是小于\(m\)
这样可以花尽量少的次数,尽量多地减少花费。
但是我们最后二分出的结果并不是最终结果。
因为可能有些地方需要做到\(x\),有些地方不需要做到\(x\),只要做到\(x+1\),那些不需要做到\(x\)的地方多花了一次。
我们最后先按照\(x+1\)求出来的最小花费假如是\(val\),那么意味着需要有\(\lceil\frac{m-val}{x}\rceil\)次补上,应该加上。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
const int N=1e6+10,mod=1e9+7,inf=INT_MAX;
int n,m;
int a[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>a[i];
}
cin>>m;
auto getval = [&](int x,int y)
{
int v=x/(y+1);
int c=x%(y+1);
return v*v*(y+1-c)+(v+1)*(v+1)*c;
};
auto check = [&] (auto x)
{
int cnt=0,val=0;
//cout<<x<<endl;
for(int i=0;i<n;++i)
{
int l=1,r=a[i+1]-a[i]-1;
while(l<=r)
{
if(getval(a[i+1]-a[i],mid-1)-getval(a[i+1]-a[i],mid)>=x) l=mid+1;
else r=mid-1;
}
//cout<<l-1<<' ';
cnt+=l-1,val+=getval(a[i+1]-a[i],l-1);
}
//cout<<endl;
return pair(cnt,val);
};
int l=0,r=1e18;
while(l<=r)
{
//cout<<l<<' '<<r<<' '<<mid<<"!!"<<endl;
auto [cnt,val]=check(mid);
if(val<=m) l=mid+1;
else r=mid-1;
}
auto [cnt,val]=check(l);
//cout<<cnt<<' '<<val<<"!!"<<endl;
cout<<cnt+(val-m+l-2)/(l-1)<<'\n';
}
}
signed main()
{
red::main();
return 0;
}
/*
*/