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=x+1\\ x=2*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\)个传送门后的最小花费。

那么根据经验有单调性:

\[f(x,k)-f(x,k+1)\geq f(x,k+1)-f(x,k+2) \]

有一个并不显然的做法:二分数值\(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;
}
/*


*/

posted @ 2022-04-11 19:52  lovelyred  阅读(47)  评论(0)    收藏  举报