Atcoder 432 A-F 总结+题解

总结

A,B,C,E

考场上了,很快就打出了正解。没什么问题。

D

考场上没有写出来后来发觉是简单的思路,但是精妙的做法。

F

挺难的。搜正常的题解基本没搜到。最后还是补出来了。希望能给出来一个大家都看得懂的一个正解。

题解

A

照题意模拟即可。

代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
#define int long long
#define endl '\n'
using namespace std;
const int maxn=2e5+5;
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int a[4];cin>>a[1]>>a[2]>>a[3];
	sort(a+1,a+1+3);
	cout<<a[3]<<a[2]<<a[1];
	return 0;
}

B

照题意模拟即可。

注意到不能有前导零,所以咱们直接选一个最小的值当做开头,然后再依次放最小的即可。

代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
#define int long long
#define endl '\n'
using namespace std;
const int maxn=2e5+5;
int cnt[10];
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	string s;cin>>s;
	int n=s.size();
	s=' '+s;
	char minn='z';
	for(int i=1;i<=n;i++)
	{
		if(s[i]>'0') minn=min(minn,s[i]);
		cnt[s[i]-'0']++;
	}
	cout<<minn;
	cnt[minn-'0']--;
	for(int i=0;i<=9;i++) for(int j=1;j<=cnt[i];j++) cout<<i;
	return 0;
}

C

挺有意思的一道题目。

可以先假设全部都选择重量较小的糖果。再把重量全部补齐。如果无法补齐,则无解。如果可以补齐,则看补齐之后一起能够上升多少即可。

代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
#define int long long
#define endl '\n'
using namespace std;
const int maxn=2e5+5;
int a[maxn],b[maxn];
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int n,x,y,ans=0;cin>>n>>x>>y;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n);
	if(a[1]*y<a[n]*x)
	{
		cout<<-1;
		return 0;
	}
	int c=y-x;
	for(int i=1;i<=n;i++)
	{
		if((a[n]*x-a[i]*x)%c)
		{
			cout<<-1;
			return 0;
		}
		b[i]=(a[n]*x-a[i]*x)/c;
	}
	for(int i=1;i<=n;i++)
	{
		if(b[i]>a[i])
		{
			cout<<-1;
			return 0;
		}
		ans+=b[i];
	}
	cout<<ans+n*(a[1]-b[1]);
	return 0;
}

D

由于他每一次切割的会都会发现。一定是从某一个连通块内切出来一部分,然后再让原先的连通块切开的两个部分去移动。

由于这样操作可能会使某一个连通块变得不是矩形。所以我们可以先将所有矩形的部分处理出来,再判断任意两个矩形之间是否为同一个联通块。

而对于标记每个矩形,我们将其左下角(\((sx,sy)\))和右上角的右上方一个点(\((tx,ty)\))的坐标记录下来。(即若点 \((x,y)\) 满足 \(sx\le x<tx \and sy\le y< ty\) 则该点位于矩形内)

在此,我们假设。一个矩形的表示方式为 \((sx,sy,tx,ty)\),含义同上。

那么对于任意一种。其分割方式我们可以分成下面两种情况。

  • 上下移动(题目中的风暴X),会分出两个矩形(设原矩形为 \((sx,sy,tx,ty)\))如下
    • \((sx,sy-b,\min(tx,a),ty-b)\)
    • \((\max(a,sx),sy+b,tx,ty+b)\)
  • 左右移动(题目中的风暴Y),会分出两个矩形(设原矩形为 \((sx,sy,tx,ty)\))如下
    • \((sx-b,sy,tx-b,\min(ty,a))\)
    • \((sx+b,\max(sy,a),tx+b,ty)\)

按照上述方法把所有矩形弄出来之后。用一个广搜或者深搜去判断其是否为同一个连通块,并计算其连通块大小即可。

代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
#define int long long
#define endl '\n'
using namespace std;
const int maxn=2e5+5;
struct node
{
	int sx,sy,tx,ty;
};
bool check(node a,node b)
{
	int sx=max(a.sx,b.sx),sy=max(a.sy,b.sy);
	int tx=min(a.tx,b.tx),ty=min(a.ty,b.ty);
	if(sx>tx||sy>ty||(sx==tx&&sy==ty)) return 0;
	return 1;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int n,x,y;cin>>n>>x>>y;
	vector<node> v;
	v.push_back({0,0,x,y});
	while(n--)
	{
		vector<node> nv; 
		char op;int a,b;cin>>op>>a>>b;
		if(op=='Y')
			for(auto [sx,sy,tx,ty]:v)
			{
				if(sy<a) nv.push_back({sx-b,sy,tx-b,min(ty,a)});
				if(ty>a) nv.push_back({sx+b,max(sy,a),tx+b,ty});
			}
		else
			for(auto [sx,sy,tx,ty]:v)
			{
				if(sx<a) nv.push_back({sx,sy-b,min(tx,a),ty-b});
				if(tx>a) nv.push_back({max(a,sx),sy+b,tx,ty+b});
			}
		v=nv;
	}
	vector<int> ans,vis(v.size()+3,0);
	for(int i=0;i<v.size();i++)
	{
		if(vis[i]) continue;
		vis[i]=1;
		queue<node> q;
		q.push(v[i]);
		int siz=0;
		while(!q.empty())
		{
			auto [sx,sy,tx,ty]=q.front();
			siz+=(tx-sx)*(ty-sy);
			for(int i=0;i<v.size();i++)
			{
				if(!vis[i]&&check(q.front(),v[i])) q.push(v[i]),vis[i]=1;
			}
			q.pop();
		}
		ans.push_back(siz);
	}
	sort(ans.begin(),ans.end());
	cout<<ans.size()<<endl;
	for(auto x:ans) cout<<x<<' ';
	return 0;
}

E

(个人感觉比上一题简单)

不难发现,这道题它的具体的权值都很小。只有 \(5\times 10^5\) 所以考虑权值线段树或权值树状数组。

在做这些之前,先把特殊情况给他去掉。因为题目其实可以看成每一次给你一个区间。如果说该点小于期间,则取这个区间的左端点,如果该点大于这个区间,则取这个端区间的右端点,求所有这个值取完之后的和。

那么如果说左端点大于右端点的情况我们直接输出 \(n\times 左端点的值\) 即可

对于一般的情况,我们可以永权值线段树/树状数组。任意一段权值区间的个数以及总和。

那么对于。在该区间以左的我们求出它的个数,并对这个个数乘上左端点的权值即可。在该右端点以右的同理。

而对于中间的我直接去处理出来这一段之和即可。

代码

#include<bits/stdc++.h>
#define inf 5e5+1
#define int long long
#define endl '\n'
using namespace std;
const int maxn=5e5+5;
int a[maxn];
struct SGT
{
	int sum[maxn<<2];
	void pushup(int x)
	{
		sum[x]=sum[x*2]+sum[x*2+1];
	}
	void update(int x,int l,int r,int p,int val)
	{
		if(l==r)
		{
			sum[x]+=val;
			return;
		}
		int mid=(l+r)>>1;
		if(p<=mid) update(x*2,l,mid,p,val);
		else update(x*2+1,mid+1,r,p,val);
		pushup(x);
	}
	int query(int x,int l,int r,int lt,int rt)
	{
		if(l>rt||r<lt) return 0;
		if(l>=lt&&r<=rt) return sum[x];
		int mid=(l+r)>>1;
		return query(x*2,l,mid,lt,rt)+query(x*2+1,mid+1,r,lt,rt);
	}
}sum,cnt;
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int n,q;cin>>n>>q;
	for(int i=1;i<=n;i++)
	{
		int x;cin>>x;
		a[i]=x;
		sum.update(1,0,inf,x,x);
		cnt.update(1,0,inf,x,1);
	}
	while(q--)
	{
		int op,x,y;cin>>op>>x>>y;
		if(op==1)
		{
			sum.update(1,0,inf,a[x],-a[x]);
			cnt.update(1,0,inf,a[x],-1);
			a[x]=y;
			sum.update(1,0,inf,a[x],a[x]);
			cnt.update(1,0,inf,a[x],1);
		}
		else
		{
			if(x>=y)
			{
				cout<<x*n<<endl;
				continue;
			}
			int a=sum.query(1,0,inf,x,y);
			int b=cnt.query(1,0,inf,y+1,inf);
			int c=cnt.query(1,0,inf,0,x-1);
			cout<<a+b*y+c*x<<endl;
		}
	}
	return 0;
}

F

无解的情况是显然的。我们只需要将所有的球合起来。如果说他对 \(n\) 取模不为零。则说明无解。否则的平均分一定为这个数除以 \(n\)


这道题有一个trick:对于任意一个需要连接出来使得操作次数最少的一种方案。必然可以选择一个点(或集合)作为中介点。将所有所需的值传给终结点,再由终结点发配。这样子做一定是最优的。

有了这个结论,我们就可以考虑。怎么样的点(或集合)可以成为中介点?

显然这种集合一定是可以通过内部交换使得内部达成我所需的平均值。只有这种集合才可以作为中介点。

显然,我希望这样子的终结点尽可能少,因为终结点内部是只能依赖自己的。

我们可以使用一个状压DP。求出我要选出这些点,按照一定的顺序的情况下,最少出现终结点的次数是多少次。由于转移的时候我会牵涉到枚举最后一次选的点是哪个点,所以顺便我们记录一下我们的路径及最后选的点是哪个。

然后,对于每一次,我们将那些不属于中介点的那些点给他互相串起来。那么这个时候。的方案已经是最优的,而至于终结点内部,我可以用循环继续去计算。

最后我们将方案输出即可。

代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
#define int long long
#define endl '\n'
using namespace std;
const int maxn=21;
vector<int> x,y,z,a,dp,sumb,pre;
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int n,sum=0;cin>>n;
	a.resize(n);
	for(int i=0;i<n;i++) cin>>a[i],sum+=a[i];
	if(sum%n!=0)
	{
		cout<<-1<<endl;
		return 0;
	}
	sum/=n;
	dp.resize(1ll<<n);
	pre.resize(1ll<<n);
	sumb.resize(1ll<<n);
	dp[0]=0,sumb[0]=0;
	pre[0]=-1;
	for(int i=1;!(i>>n);i++)
	{
		dp[i]=n+1;
		pre[i]=-1;
		sumb[i]=0;
		for(int j=0;j<n;j++) if(i&(1ll<<j)) sumb[i]+=a[j]-sum;
		int pls=(sumb[i]==0?0:1);
		for(int j=0;j<n;j++)
			if(i&(1ll<<j))
			{
				int now=dp[i-(1ll<<j)]+pls;
				if(dp[i]>now)
				{
					dp[i]=now;
					pre[i]=j;
				}
			}
	}
	int now=(1ll<<n)-1;
	while(now)
	{
		vector<pair<int,int> > v;
		while(1)
		{
			int use=pre[now];
			v.push_back({-a[use],use});
			now-=(1ll<<use);
			if(sumb[now]==0) break;
		}
		sort(v.begin(),v.end());
		int give=a[v[0].second]-sum;
		for(int i=1;i<v.size();i++)
		{
			x.push_back(v[i-1].second);
			y.push_back(v[i-0].second);
			z.push_back(give);
			give+=(a[v[i].second]-sum);
		}
	}
	cout<<z.size()<<endl;
	for(int i=0;i<x.size();i++) cout<<x[i]+1<<' '<<y[i]+1<<' '<<z[i]<<endl;
	return 0;
}
posted @ 2025-11-16 22:33  Engle_Chen  阅读(0)  评论(0)    收藏  举报