Codeforces Round #573 (Div. 2)题解

Codeforces Round #573 (Div. 2)

说实在的,中国人出的题果然很(du)好(liu)。

A. Tokitsukaze and Enhancement

◇题目传送门◆

题目大意

给定一个值 x x x,有四种等级与 x x x按照如下对应方式对应:

  • x ≡ 1 ( m o d    4 ) x\equiv1(\mod4) x1(mod4),则等级为 A A A
  • x ≡ 3 ( m o d    4 ) x\equiv3(\mod4) x3(mod4),则等级为 B B B
  • x ≡ 2 ( m o d    4 ) x\equiv2(\mod4) x2(mod4),则等级为 C C C
  • x ≡ 0 ( m o d    4 ) x\equiv0(\mod4) x0(mod4),则等级为 D D D

其中等级 A A A最高,等级 D D D最低。现可以将 x x x变成 x , x + 1 , x + 2 x,x+1,x+2 x,x+1,x+2,求能达到的最高等级以及其加上的数。

分析

水题。暴力判断即可。

参考代码

#include
#include
using namespace std;
 
int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	int n;
	scanf("%d",&n);
	int ans=0;
	char ansch='E';
	for(int i=0;i<=2;i++) {
		int tmp=(n+i)%4;
		char ch;
		if(tmp==0)ch='D';
		if(tmp==1)ch='A';
		if(tmp==2)ch='C';
		if(tmp==3)ch='B';
		if((int)ch<(int)ansch) {
			ansch=ch;
			ans=i;
		}
	}
	printf("%d %c\n",ans,ansch);
	return 0;
}

B. Tokitsukaze and Mahjong

◇题目传送门◆

题目大意

给定一种麻将,麻将牌由若干1m,2m,3m,...,9m,1p,2p,3p,...,9p,1s,2s,3s,...9s组成,现在手中有三张牌,要求再找一些牌,使得手中的牌满足以下两个条件中的一个:

  • 3 3 3张牌完全相同;
  • 3 3 3张牌字母部分相同,且数字部分连续。

求最少要找的牌的数量。

分析

细节题。。。细节特别多,写的时候要特别注意。

参考代码

#include
#include
#include
#include
#include
using namespace std;
 
string s[5];
map<string,int> t;
 
int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	for(int i=1;i<=3;i++) {
		cin>>s[i];
		t[s[i]]++;
	}
	if(t[s[1]]==3) {
		puts("0");
		return 0;
	}
	int ans=5;
	for(int i=1;i<=3;i++) {
		if(t[s[i]]==2) {
			ans=min(ans,1);
		}
		string tmp=s[i];
		tmp[0]++;
		bool flag=true;
		int cnt=2;
		if(tmp[0]>'9')flag=false;
		else if(t.count(tmp))cnt--;
		tmp[0]++;
		if(tmp[0]>'9')flag=false;
		else if(t.count(tmp))cnt--;
		if(flag)ans=min(ans,cnt);
		tmp=s[i],cnt=2,flag=true;
		tmp[0]--;
		if(tmp[0]<'1')flag=false;
		else if(t.count(tmp))cnt--;
		tmp[0]--;
		if(tmp[0]<'1')flag=false;
		else if(t.count(tmp))cnt--;
		if(flag)ans=min(ans,cnt);
	}
	printf("%d\n",ans);
	return 0;
}

C. Tokitsukaze and Discard Items

◇题目传送门◆

题目大意

给定 N N N,表示从 1 1 1 N N N这些数排在一串,现将这些数按照 K K K个分成一段一段的。

给出 M M M个数字,要求从数列中删掉这些数,每次删除能够将一段中的所有要删的数删完。删除后后面的数自动向前移动,占用前面的数的位置。求删数的次数。

分析

我们发现,只要删掉了多少个数,后面的数就会向前平移多少个位置,所以我们只需记录下这个向前平移的位置长度,再在向后查询出同一段的数的个数即可。

参考代码

#include
#include
using namespace std;
 
typedef long long ll;
const int Maxm=1e5;
 
ll N,M,K;
ll p[Maxm+5];
ll o=0;
 
int BS(ll val,int lb,int ub) {
	while(lb+1<ub) {
		int mid=(lb+ub)>>1;
		if(p[mid]-o==val)
			return mid;
		if(p[mid]-o<val)
			lb=mid;
		else ub=mid; 
	}
	return lb;
}
 
int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	scanf("%lld %lld %lld",&N,&M,&K);
	for(int i=1;i<=M;i++)
		scanf("%lld",&p[i]);
	int ans=0;
	for(int i=1;i<=M;) {
		ll k=((p[i]-o)%K==0?(p[i]-o)/K-1:(ll)(p[i]-o)/K);
		ll st=k*K+1;
		int pos=BS(st+K-1,i,M+1);
		o=pos;
		i=pos+1;
		ans++;
	}
	printf("%d\n",ans);
	return 0;
}

D. Tokitsukaze, CSL and Stone Game

◇题目传送门◆

题目大意

给定 N N N堆石子,每堆有 a i a_i ai个,两名玩家可以轮流从任意一堆石子中取走 1 1 1颗石子,若某玩家不能操作,则该玩家输了;若某玩家取完石子后,有两堆石子数量相同,则这个玩家输了。求哪个玩家胜利。

分析

不难发现最终状态必定为 0 , 1 , … , ( N − 1 ) 0,1,\ldots,(N-1) 0,1,,(N1)的一个排列,若这些数的和为偶数,则后手胜利,否则先手胜利。

那么我们考虑特殊情况,在先手取走一颗后,设某堆数量为 x x x的石块出现次数为 c [ x ] c[x] c[x]次,那么:

  1. c [ 0 ] > 1 c[0]>1 c[0]>1时,先手必败,因为做不出 0 , 1 , … , ( N − 1 ) 0,1,\ldots,(N-1) 0,1,,(N1)的排列;
  2. 当某数 x x x c [ x ] > 2 c[x]>2 c[x]>2时,先手必败;
  3. 当某数 x x x c [ x ] > 1 c[x]>1 c[x]>1 c [ x − 1 ] > 0 c[x-1]>0 c[x1]>0时,先手必败;
  4. 当某两数 x , y x,y x,y c [ x ] > 1 c[x]>1 c[x]>1 c [ y ] > 1 c[y]>1 c[y]>1时,先手必败。

所以我们就只需判断完这些特殊情况后,按常规方法判断即可。

参考代码

#include
#include
#include
using namespace std;

const int Maxn=1e5;

int N,A[Maxn+5];
map<int,int> cnt;

int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	scanf("%d",&N);
	bool flag=true;
	for(int i=1;i<=N;i++) {
		scanf("%d",&A[i]);
		cnt[A[i]]++;
		flag&=(cnt[A[i]]<3);
	}
	int tmp=0;
	for(map<int,int>::iterator it=cnt.begin();it!=cnt.end();it++)
		if(it->second>=2)tmp++;
	flag&=(tmp<=1);
	for(map<int,int>::iterator it=cnt.begin();it!=cnt.end();it++)
		if(it->second==2) {
			flag&=(cnt.count(it->first-1)==0);
			if(it->first==0)flag=false;
		}
	if(flag==false) {
		puts("cslnb");
		return 0;
	}
	sort(A+1,A+N+1);
	long long s=0;
	for(int i=1;i<=N;i++)
		s+=(A[i]-(i-1));
	puts(s%2==0?"cslnb":"sjfnb");
	return 0;
}

E. Tokitsukaze and Duel

◇题目传送门◆

题目大意

给定一个 01 01 01串,每次能将连续 K K K个字符中的所有 0 0 0变成 1 1 1,或者将所有 1 1 1变成 0 0 0。谁先使所有的字符变成相同的谁胜利。求谁胜利或者平局。

分析

怎么有两个博弈题???

简单分析可以发现,先手必须在一步之内完成比赛,否则他就会输掉,而后手也必须在一步以内结束比赛,否则就是一个平局。(这想出来也很烦。。。实现几乎没难度。。。)

程序按照这样写就是了。。。

参考代码

#include
#include
#include
#include
using namespace std;

const int Maxn=1e5;

int N,K,len;
char s[Maxn+5];

int L[Maxn+5],R[Maxn+5];

int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	scanf("%d %d\n%s",&N,&K,s+1);
	for(int i=1,tmp[2]={-1,-1};i<=N;i++) {
		int t=s[i]-'0';
		L[i]=tmp[t^1];
		tmp[t]=i;
	}
	for(int i=N,tmp[2]={N+1,N+1};i>=1;i--) {
		int t=s[i]-'0';
		R[i]=tmp[t^1];
		tmp[t]=i;
	}
	bool First=false,Second=true;
	for(char op='0';op<='1'&&First==false;op++) {
		vector<int> tmp;
		for(int i=1;i<=N;i++)
			if(s[i]!=op)
				tmp.push_back(i);
		for(int l=1,r=K,tl=0,tr=-1;r<=N;l++,r++) {
			while(tl<tmp.size()&&tmp[tl]<l)tl++;
			while(tr+1<tmp.size()&&tmp[tr+1]<=r)tr++;
			if(tl==0&&tr==tmp.size()-1) {
				First=true;
				break;
			}
			int l1=tmp[tl?0:tr+1],r1=tmp[tr<tmp.size()-1?tmp.size()-1:tl-1];
			int l2=l1>1?1:min(R[l1],r1+1),r2=l2<N?N:min(L[r1],l1-1);
			Second&=(r1-l1+1<=K|r2-l2+1<=K);
		}
	}
	puts(First?"tokitsukaze":(Second?"quailty":"once again"));
	return 0;
}

F. Tokitsukaze and Strange Rectangle

◇题目传送门◆

题目大意

给定一些点(坐标为正整数),要求求出用直线 x = l , x = r , y = a ( l < r ) x=l,x=r,y=a(l<r) x=l,x=r,y=a(l<r)(其中 l , r , a l,r,a l,r,a可以是任何数)围成的开口向上区域中的点构成的集合中,不同的个数。

分析

这应该是Div.2 D难度吧。。。

考虑先画出 y = a y=a y=a这条线(相比于先构造 x = l , x = r x=l,x=r x=l,x=r这两条直线,我们不需要考虑区间问题)(因为这样的话更为简单)。

不难想出如下算法:

对于某个点 ( x , y ) (x,y) (x,y),我们找出它的左边的点且纵坐标比它大的点的数量 l l l和在它右边且纵坐标比它大的点的数量 r r r,这样我们只需讨论由这些点构成的集合数量:

  • 这个点单独组合:方案数为 1 1 1
  • 这个点与它左边的点组合:方案数为 l l l
  • 这个点与它右边的点组合:方案数为 r r r
  • 这个点与它左边和它右边的点组合:方案数为 l × r l\times r l×r

所以这个点总方案数为 1 + l + r + l × r 1+l+r+l\times r 1+l+r+l×r

但是我们会发现一些问题,如下图:
Codeforces Round #573 (Div. 2)题解_第1张图片

我们在统计右边的点的时候,会统计到这种情况,在统计到左边的点的时候,会统计到同一种情况,这样就是重复的。所以我们在查找的时候,若两点纵坐标相同,则 r r r只查询两点之间的点的数量。

为了保证时间和空间,我们采用树状数组维护区间横坐标,并采用离散化处理所有点坐标。

参考代码

#include
#include
#include
#include
using namespace std;

typedef long long ll;
const int Maxn=2e5;

struct BIT {
	int s[Maxn+5];
	int siz;
	inline int lowbit(int x){return x&(-x);}
	void add(int x,int val) {
		while(x<=siz) {
			s[x]+=val;
			x+=lowbit(x);
		}
	}
	inline int _sum(int x) {
		if(x<=0)return 0;
		int ret=0;
		while(x>0) {
			ret+=s[x];
			x-=lowbit(x);
		}
		return ret;
	}
	int sum(int l,int r) {
		if(l>r)return 0;
		return _sum(r)-_sum(l-1);
	}
};

struct Point {
	int x,y;
	bool operator < (const Point &rhs) const {return y==rhs.y?x<rhs.x:y>rhs.y;}
};

Point A[Maxn+5];
int N;
BIT cnt;
int Xsiz,Ysiz;

void Prepare() {
	vector<int> x,y;
	for(int i=1;i<=N;i++) {
		x.push_back(A[i].x);
		y.push_back(A[i].y);
	}
	sort(x.begin(),x.end());
	sort(y.begin(),y.end());
	x.resize(unique(x.begin(),x.end())-x.begin());
	y.resize(unique(y.begin(),y.end())-y.begin());
	cnt.siz=Xsiz=x.size(),Ysiz=y.size();
	for(int i=1;i<=N;i++) {
		A[i].x=lower_bound(x.begin(),x.end(),A[i].x)-x.begin()+1;
		A[i].y=lower_bound(y.begin(),y.end(),A[i].y)-y.begin()+1;
	}
}

bool is_add[Maxn+5];

int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	scanf("%d",&N);
	for(int i=1;i<=N;i++)
		scanf("%d %d",&A[i].x,&A[i].y);
	Prepare();
	sort(A+1,A+N+1);
	ll ans=0;
	for(int i=1;i<=N;i++) {
		int l=cnt.sum(1,A[i].x-1),r;
		if(A[i].y==A[i+1].y)r=cnt.sum(A[i].x+1,A[i+1].x-1);
		else r=cnt.sum(A[i].x+1,Xsiz);
		ans+=(1+l+r+1LL*l*r);
		if(is_add[A[i].x]==false)cnt.add(A[i].x,1),is_add[A[i].x]=true;
	}
	printf("%lld\n",ans);
	return 0;
}

你可能感兴趣的:(#,CodeForces)