Codeforces Round #624 (Div. 3)补题

上场恰好掉到了1600,只能打星,血亏……
这场签到手太慢了,C题数组开小了RE了一发,D题范围小了fst了……

本场关键字

分类讨论、并查集、前缀和、暴力、逆序数、树状数组

A. Add Odd or Subtract Even(700)

分类讨论,手慢了

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1000000007;
//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	ll t,a,b;
	cin>>t;
	while(t--)
	{
		cin>>a>>b;
		if(a<b)
		{
			if((b-a)&1)
				cout<<1<<endl;
			else
				cout<<2<<endl;//拆成两个
		}
		else if(a>b)
		{
			if((a-b)&1)//奇数且
				cout<<2<<endl;
			else
				cout<<1<<endl;
		}
		else
			cout<<0<<endl;
	}
	return 0;
}

B. WeirdSort(1200)

思路

并查集+ m u l t i s e t multiset multiset
因为下标在同一组的元素可以任意交换,所以很容易想到并查集。
再因为排序要把小的元素尽量往前排,所以选用 m u l t i s e t multiset multiset,每次贪心地取出集合中的第一个元素。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105,inf=0x3f3f3f3f,mod=1000000007;
//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
int a[maxn],p[maxn],fa[maxn];
int findfa(int x)
{
	if(x!=fa[x])
		fa[x]=findfa(fa[x]);
	return fa[x];
}
bool Merge(int x,int y)
{
	int a=findfa(x),b=findfa(y);
	if(a==b)
		return 0;
	if(a<b)
	{
		fa[b]=a;
	}
	else{
		fa[a]=b;
	}
	return 1;
}
int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	ll t,n,m;
	cin>>t;
	while(t--)
	{
		cin>>n>>m;
		vector<int>res;
		multiset<int>st[maxn];
		multiset<int>::iterator it;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
			fa[i]=i;
		}
		for(int i=1;i<=m;i++)//合并可交换的位置
		{
			cin>>p[i];
			Merge(p[i],p[i]+1);
		}
		for(int i=1;i<=n;i++)//插入对应集合
		{
			st[findfa(i)].insert(a[i]);
		}
		for(int i=1;i<=n;i++)//取出所属集合中的最小值
		{
			it=st[findfa(i)].begin();
			res.push_back(*it);//res为排序后数组
			st[findfa(i)].erase(it);
		}
		int pre=0,flag=1;
		for(auto &i:res)
		{
			if(i<pre)
			{
				flag=0;
				break;
			}
			pre=i;
		}
		cout<<(flag?"YES":"NO")<<endl;
	}
	return 0;
}

C. Perform the Combo(1300)

题目很长

题意

没玩过格斗游戏的童鞋最好去看洛谷的翻译。
一个萌新在练习格斗游戏的连续技,必须要不间断输入指令集 s s s才能成功完成。
这个萌新练习失败了 m m m次,第 i i i次在 p i p_i pi个指令上断连。
并且他最终成功打出了这套连续技。
请输出这个萌新对于每个指令输入了多少次。

思路

前缀和
对每个指令 p i p_i pi都求一个前缀和,将 m m m次断连和一次成功的操作通过前缀和 O ( m ) O(m) O(m)求出,注意不要在循环内写 m e m s e t memset memset
很神奇,我也不知道为什么1e4会fst,要开2e4

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5,inf=0x3f3f3f3f,mod=1000000007;
//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
char s[maxn];
int st[30],sum[maxn][30];
ll ans[30];
int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int t,n,m,p;
	cin>>t;
	while(t--)
	{
		cin>>n>>m>>s+1;
		memset(st,0,sizeof(st));
//		memset(ans,0,sizeof(ans));
		for(int i=1;i<=n;i++)
		{
			int ch=s[i]-'a';
			sum[i][ch]=sum[i-1][ch]+1;
			for(int j=0;j<26;j++)
				if(j!=ch)
					sum[i][j]=sum[i-1][j];
		}
		for(int i=0;i<26;i++)
			ans[i]=sum[n][i];
		while(m--)
		{
			cin>>p;
			for(int i=0;i<26;i++)
				ans[i]+=sum[p][i];
		}
		for(int i=0;i<26;i++)
			cout<<ans[i]<<' ';
		cout<<endl;
	}
	return 0;
}

D. Three Integers(1900)

题意

给你 a , b , c a,b,c a,b,c三个数,你可以将他们加 1 1 1或减 1 1 1操作任意次,操作一次花费 1 1 1,求最终 a a a整除 b b b b b b整除 c c c的最小花费,并输出 a , b , c a,b,c a,b,c

思路

emm,一言难尽,赛中正确做法秒得😣,但是因为复杂度估计错误自我怀疑演了一个小时,直觉以为是 O ( n 3 ) O(n^3) O(n3),其实只有 O ( n 7 4 ) O(n^{\frac{7}{4}}) O(n47)的复杂度。
这个想法真的很暴力……在[1,2e4]范围内枚举出 a a a,然后用 a a a枚举出 a a a的倍数 b b b,再枚举 b b b的倍数 c c c,计算当前 a , b , c a,b,c a,b,c的代价,记录代价最小的一组。
AC之后心态崩了……

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e4+5,inf=0x3f3f3f3f,mod=1000000007;
//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	ll t,a,b,c,ans[3],rec;
	cin>>t;
	while(t--)
	{
		cin>>a>>b>>c;
		rec=LLONG_MAX;
		for(int x=1;x<maxn;x++)//枚举a
		{
			for(int i=1;x*i<maxn;i++)
			{
				ll y=x*i;//
				for(ll j=1;y*j<maxn;j++)
				{
					ll z=y*j;
					ll now=abs(x-a)+abs(y-b)+abs(z-c);
					if(now<rec)
					{
						rec=now;
						ans[0]=x;
						ans[1]=y;
						ans[2]=z;
					}
				}
			}
		}
		cout<<rec<<endl;
		for(int i=0;i<3;i++)
			cout<<ans[i]<<' ';
		cout<<endl;
	}
	return 0;
}

E. Construct the Binary Tree(2400)

图论构造题,判定有思路,实现不会,看了题解也不会

题意

给出 n n n d d d,要求构造一个 n n n个节点的二叉树,节点1为根,要使所有节点到根的距离之和为d。

思路

先判定是否有解,显然当 n n n为一条链时深度之和最大;当 n n n个点构成完全二叉树时深度和最小。
在这个范围区间即有解,先将 n n n个节点组成一条链,逐步将深度最浅的叶子节点向上移,若遇见此节点上方为满二叉树,则将此节点打上 b a d bad bad标记,继续调整,复杂度 O ( n d ) O(nd) O(nd)
详见代码QwQ……

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1000000007;
//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
int fa[maxn];
int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	ll t,n,d;
	cin>>t;
	while(t--)
	{//最大:链,最小:完全二叉树
		cin>>n>>d;
		ll mi=0;
		for(ll i=1,now=n-1;now>0;i++)
		{//now为剩余节点数量
			if(now>=(1<<i))
			{
				mi+=i*(1<<i);
				now-=(1<<i);
			}
			else{
				mi+=now*i;
				break;
			}
		}
		if(d>n*(n-1)/2||d<mi)
			cout<<"NO"<<endl;
		else{
			cout<<"YES"<<endl;
			vector<int>fa(n),dep(n);//构造出一条链,下标[0,n-1]
			iota(fa.begin(),fa.end(),-1);//递增赋值
			iota(dep.begin(),dep.end(),0);
			int tot=n*(n-1)/2;//链的深度和
			vector<int>cnt(n,1),bad(n);
			cnt[n-1]=0;//cnt[i]表示i节点的儿子数量
			while(tot>d)
			{//尽力在上部构造完全二叉树
				int v=-1,p=-1;
				for(int i=0;i<n;i++)//找深度最浅的叶子节点v
					if(!bad[i]&&cnt[i]==0&&(v==-1||dep[v]>dep[i]))
						v=i;
				for(int i=0;i<n;i++)//找一个深度比v浅的,但与v最近的节点作为父节点
					if(cnt[i]<2&&dep[i]<dep[v]-1&&(p==-1||dep[i]>dep[p]))
						p=i;
				if(p==-1)
				{//上部为满二叉树,这个节点不能再向上蹿了
					bad[v]=1;
					continue;
				}
				cnt[fa[v]]--;
				dep[v]--;//v上移一个单位
				cnt[p]++;
				fa[v]=p;
				tot--;//深度之和--
			}
			for(int i=1;i<n;i++)
				cout<<fa[i]+1<<' ';
			cout<<endl;
		}
	}
	return 0;
}

F. Moving Points(2100)

非常好的一道数据结构题,但是我不会……

题意

点上面的链接去洛谷看……很清晰……

思路

因为所有点的初始位置 x x x都不同,很明显对于任意两点 i i i j j j,设初始位置 x i < x j x_i<x_j xi<xj,若 v i > v j v_i>v_j vi>vj,则 d ( i , j ) = 0 d(i,j)=0 d(i,j)=0,否则 d ( i , j ) = x j − x i d(i,j)=x_j-x_i d(i,j)=xjxi
所以先按照 x x x排序,这样在第 i i i个点之前的点初始位置一定小于 x i x_i xi,此时查询在点 i i i之前的点有多少个点的速度大于 v i v_i vi,记个数为 m m m,这些点的初始位置之和为 s u m sum sum,则插入点 i i i产生的贡献为 m ∗ x i − s u m m*x_i-sum mxisum
对所有的速度 v v v进行离散化,用两个树状数组,一个按照求逆序数的方式维护小于 v i v_i vi的点的个数,另一个维护目前小于 v i v_i vi的点的初始位置 x x x的和。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5,inf=0x3f3f3f3f,mod=1000000007;
//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
struct BinaryIndexedsTree
{
	int n;
	ll c[maxn];
	inline int lowbit(int x)
	{
		return x & (-x);
	}
	void build(int *a,int n)//a是原数组,n是元素个数
	{
		this->n=n;
		memset(c,0,sizeof(c));
		for(int i=1;i<=n;i++)
			add(i,a[i]-a[i-1]);//构建差分c数组
	}
	void add(int k,ll val)//修改:a[k]加上val,直接在c数组上操作
	{
		while(k<=n)
		{
			this->c[k]+=val;
			k+=lowbit(k);
		}
	}
	void range_update(int l, int r, ll val)//区间更新
	{
		add(l,val);//把l之后所有的点都更新一遍
		add(r+1,-val);//因为r之后的点是不用更新的,但是多更新了,所以要每个点都-val;
	}
	ll query(int k)//查询:1~k的区间和
	{
		ll sum=0;
		while(k)
		{
			sum+=this->c[k];
			k-=lowbit(k);
		}
		return sum;
	}
} bit1,bit2;
struct point
{
	ll x,v;
} a[maxn];
vector<ll> mp;
void disc(int n)//对速度进行离散化
{
	for(int i=1;i<=n;i++)
		mp.push_back(a[i].v);
	sort(mp.begin(),mp.end());
	mp.erase(unique(mp.begin(),mp.end()),mp.end());
}
ll id(ll x)
{
	return lower_bound(mp.begin(),mp.end(),x)-mp.begin()+1;
}
int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i].x;
	for(int i=1;i<=n;i++)
		cin>>a[i].v;
	sort(a+1,a+n+1,[](const point &a,const point &b){
			return a.x<b.x;//按x排序
		});//返回小于vi的数量,以及符合的点的x和
	disc(n);//对速度进行离散化
	ll ans=0;
	bit1.n=bit2.n=n;
	for(int i=1;i<=n;i++)
	{
		int now=id(a[i].v);
		ans+=a[i].x*bit1.query(now)-bit2.query(now);
		bit1.add(now,1);//逆序数的求法
		bit2.add(now,a[i].x);//按v的大小次序求一个x前缀和...
	}
	cout<<ans<<endl;
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值