2020牛客暑期多校训练营(第三场)题解

文章目录

      • A. Clam ans Fish
      • B. Classical String Problem
      • C. Operation Love
      • D. Points Construction Problem
      • E. Two Matchings
      • F. Fraction Construction
      • H. Sort the Strings Revision
      • L. Problem L is the Only Lovely Problem

A. Clam ans Fish

n n n 个鱼塘,每个鱼塘是四种鱼塘中的一种,这四种分别是啥都没、有鱼饵、有鱼、都有,每次可以选择四种操作之一:不动、拿鱼、拿鱼饵、用鱼饵钓鱼(可以在没鱼的鱼塘里钓),求最后的最大鱼数。

有饵没鱼的肯定拿饵,有鱼没饵的肯定拿鱼,啥都没有的肯定尝试钓鱼,都有的肯定拿鱼,因为不知道鱼饵后面能不能有机会钓。

然后后面还剩了 k k k 个饵,可以考虑只要前 k 2 \dfrac k 2 2k 个,在遇到后面的 k 2 \dfrac k 2 2k 个饵时,由于是多余的,就不拿了,操作换成钓鱼。

代码如下:

#include 

int T,n;char s[2000010];

int main()
{
	scanf("%d",&T);while(T--)
	{
		scanf("%d %s",&n,s+1);
		int sum1=0,sum2=0;
		for(int i=1;i<=n;i++)
		switch(s[i])
		{
			case '0':
				if(sum1)sum1--,sum2++;
				break;
			case '1':
				sum1++;
				break;
			case '2':
				sum2++;
				break;
			case '3':
				sum2++;
				break;
		}
		printf("%d\n",sum2+sum1/2);
	}
}

B. Classical String Problem

给出一个字符串,有三种操作,1、将前 k k k 个字符移到后面,2、将后 k k k 个字符移到前面,3、询问第 k k k 个字符是谁。

前两个操作其实可以看成字符串的整体左移和右移,所以记录一下字符串的开头在哪里可以搞询问了。

代码如下:

#include 
#include 

char s[2000010];
int n,m;

int main()
{
	scanf("%s",s+1);n=strlen(s+1);
	scanf("%d",&m);
	char id[2];int x,now=1;while(m--)
	{
		scanf("%s %d",id,&x);
		if(id[0]=='M'){
			now-=x;
			if(now<1)now+=n;
			if(now>n)now-=n;
		}else{
			x=x-now+1;
			if(x<1)x+=n;
			printf("%c\n",s[x]);
		}
	}
}

C. Operation Love

给出一个手掌,问这是右手还是左手。

我的做法是先找到手掌底部的两个点,只有他们的距离是 9 9 9,很好找,分别记为第 i i i i + 1 i+1 i+1 个点。

然后再判断一下第 i − 1 i-1 i1 个点与 i i i 的距离,看看是 6 6 6 还是 8 8 8,这样能判断大拇指位置。

然后再用叉积判一下点是顺时针给的还是逆时针给的即可。

代码如下:

#include 
#include 
#define eps 0.1

int T;
struct point{
	double x,y;
	double det(point B){return x*B.y-y*B.x;}
	point operator -(const point B){return (point){x-B.x,y-B.y};}
}a[21];
int pre(int x){return x==1?20:x-1;}
int next(int x){return x==20?1:x+1;}
double dis(point x,point y){return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));}

int main()
{
	scanf("%d",&T);while(T--)
	{
		for(int i=1;i<=20;i++)scanf("%lf %lf",&a[i].x,&a[i].y);
		for(int i=1;i<=20;i++)if(fabs(dis(a[i],a[next(i)])-9.0)<eps){
			
			double x=dis(a[i],a[pre(i)]);int ans;
			if(fabs(x-6.0)<eps)ans=1;else ans=0;
			if((a[next(i)]-a[i]).det(a[next(next(i))]-a[i])<0)ans^=1;
			if(ans==1)printf("right\n");else printf("left\n");
			break;
		}
	}
}

D. Points Construction Problem

要求将平面上 n n n 个点染成黑色,其他都是白色,存在恰好有 m m m 对点 (X,Y),满足点 X , Y X,Y X,Y 相邻,且一个为黑一个为白。

考虑将这 n n n 个点一行放 n \sqrt n n 个堆积起来,此时的答案是最小的。

然后逐渐将最上面那行的最右边那个放到最下面那行的最右边,每次移动一个可以使答案 + 2 +2 +2,直到所有点都在一条直线上并且都相邻。

然后再从左到右考虑第 i i i 个点,将第 i i i ~ n n n 个点整体右移一位,即让 i i i i − 1 i-1 i1 中间空出一位,这样又可以使答案 + 2 +2 +2

在做上面这个过程时,当答案等于 m m m 时,输出即可。

代码如下:

#include 
#include 
#include 
#include 
using namespace std;
#define maxn 60

int T,n,m,ans;
struct point{int x,y;}s[maxn];
bool v[maxn][maxn];
void add(int x,int y)
{
	if(v[x-1][y])ans--;else ans++;
	if(v[x+1][y])ans--;else ans++;
	if(v[x][y-1])ans--;else ans++;
	if(v[x][y+1])ans--;else ans++;
	v[x][y]=true;
}
void print()
{
	printf("Yes\n");
	for(int i=1;i<=n;i++)printf("%d %d\n",s[i].x,s[i].y);
}

int main()
{
	scanf("%d",&T);while(T--)
	{
		scanf("%d %d",&n,&m);ans=0;
		if(m%2){printf("No\n");continue;}
		memset(v,false,sizeof(v));
		int len=sqrt(n),x=1,y=1;
		for(int i=1;i<=n;i++)
		{
			s[i]=(point){x,y};add(x,y);
			y++;if(y>len)x++,y=1;
		}
		if(m<ans||m>4*n){printf("No\n");continue;}
		if(m==ans){print();continue;}
		x=1,y=len+1;
		for(int i=n;i>len;i--)
		{
			if(s[i].y!=1)ans+=2;
			s[i]=(point){x,y};y++;
			if(ans==m)break;
		}
		if(ans==m){print();continue;}
		x=1,y=1;
		for(int i=1;i<=n;i++){
			s[i]=(point){x,y};y++;
			if(ans<m)y++,ans+=2;
		}
		print();
	}
}

E. Two Matchings

1 1 1 ~ n n n 的一个排列 p p p 是“配对的”当且仅当 ∀ i , p i ≠ i , p p i = i \forall i,p_i\neq i,p_{p_i}=i i,pi=i,ppi=i,现在有一个长度为 n n n 的序列 A A A,定义一个排列的代价为 ∑ i = 1 n ∣ a i − a p i ∣ / 2 \sum_{i=1}^n |a_i-a_{p_i}|/2 i=1naiapi/2,现在要找两个完全不同的“配对的”排列使得总代价最小。

“配对的”排列其实就是将 1 1 1 ~ n n n 这个排列每一位都与其他位进行交换,且每一位仅交换一次,如 3 , 4 , 1 , 2 3,4,1,2 3,4,1,2 就是一个“配对的”排列, 1 1 1 3 3 3 交换了, 2 2 2 4 4 4 交换了,下面称交换的两位是相互配对的。

那么一个排列的代价其实就是将每个配对的 A i A_i Ai 之差加起来,那么显然最小的一个方案就是将 A A A 进行排序,然后相邻的两个进行配对。

考虑第二小的方案,由于两个方案不能有任何一个配对相同,所以此时只能考虑相邻的 4 4 4 个进行相互配对和相邻 6 6 6 个进行相互配对。相邻 8 8 8 个以上的配对就不需要考虑了,因为他们拆成 4 4 4 个和 6 6 6 个的一定不会更劣,然后做一次dp即可。

相邻 4 4 4 个的最优配对方案为 ( 1 , 3 ) , ( 2 , 4 ) (1,3),(2,4) (1,3),(2,4),相邻 6 6 6 个的最优配对方案为 ( 1 , 3 ) , ( 2 , 5 ) , ( 4 , 6 ) (1,3),(2,5),(4,6) (1,3),(2,5),(4,6)

代码如下:

#include 
#include 
#include 
using namespace std;
#define maxn 200010
#define ll long long

int T,n,a[maxn];
ll f[maxn],ans;
ll calc1(int x){return a[x+2]-a[x]+a[x+3]-a[x+1];}
ll calc2(int x){return a[x+2]-a[x]+a[x+5]-a[x+3]+a[x+4]-a[x+1];}

int main()
{
	scanf("%d",&T);while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)scanf("%d",&a[i]),f[i]=2147483647;
		sort(a+1,a+n+1);ans=0;
		for(int i=1;i<=n;i+=2)ans+=a[i+1]-a[i];
		for(int i=0;i<=n;i++){
			if(i+4<=n)f[i+4]=min(f[i+4],f[i]+calc1(i+1));
			if(i+6<=n)f[i+6]=min(f[i+6],f[i]+calc2(i+1));
		}
		printf("%lld\n",ans+f[n]);
	}
}

F. Fraction Construction

找两个分数 c d , e f \dfrac c d,\dfrac e f dc,fe,满足 c d − e f = a b \dfrac c d-\dfrac e f=\dfrac a b dcfe=ba,且 d , f < b d,fd,f<b

先将 a , b a,b a,b 化简为 a ′ , b ′ a',b' a,b,然后转化一下上面的柿子:
c d − e f = a ′ b ′ c f − e d d f = a ′ b ′ \frac c d-\frac e f=\frac {a'} {b'}\\ \dfrac {cf-ed} {df}=\frac {a'} {b'} dcfe=badfcfed=ba

下面不妨令 a ′ = c f − e d , b ′ = d f a'=cf-ed,b'=df a=cfed,b=df

假如 b ′ < b b'b<b,那么存在一组解 c = a ′ + b ′ , d = b ′ , f = 1 , e = 1 c=a'+b',d=b',f=1,e=1 c=a+b,d=b,f=1,e=1

如果 b ′ = b b'=b b=b,那么 d d d 就不能等于 b ′ b' b 了,因为 d d d 要小于 b b b

考虑枚举 d , f d,f d,f,因为要让 c f − e d = a ′ cf-ed=a' cfed=a 有解,所以 gcd ⁡ ( d , f ) ∣ a ′ \gcd(d,f)|a' gcd(d,f)a,而 gcd ⁡ ( a ′ , b ′ ) = 1 \gcd(a',b')=1 gcd(a,b)=1,所以 gcd ⁡ ( d , f ) = 1 \gcd(d,f)=1 gcd(d,f)=1

枚举出 d , f d,f d,f 后,直接扩欧求 c , e c,e c,e 即可。

代码如下:

#include 
#include 
#include 
using namespace std;
#define ll long long

ll gcd(ll x,ll y){return !y?x:gcd(y,x%y);}
void exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){x=1;y=0;return;}
	exgcd(b,a%b,y,x);y-=a/b*x;
}
ll T,a,b,c,d,e,f;

int main()
{
	scanf("%lld",&T);while(T--)
	{
		scanf("%lld %lld",&a,&b);d=-1;
		ll p=gcd(a,b);if(p>1){printf("%lld %lld 1 1\n",a/p+b/p,b/p);continue;}
		for(ll i=2;i*i<=b;i++)if(b%i==0&&gcd(i,b/i)==1){d=i,f=b/i;break;}
		if(d==-1){printf("-1 -1 -1 -1\n");continue;}
		exgcd(f,d,c,e);e=-e;
		while(c<=0||e<=0)c+=d,e+=f;c*=a;e*=a;
		printf("%lld %lld %lld %lld\n",c,d,e,f);
	}
}

H. Sort the Strings Revision

你有 n + 1 n+1 n+1 个长度为 n n n 的字符串, s 0 s_0 s0 的第 i i i 位是数字 i   m o d   10 i\bmod 10 imod10 s i ( i > 0 ) s_i(i>0) si(i>0) s i − 1 s_{i-1} si1 将第 p i − 1 p_{i-1} pi1 位的数字换成 d i − 1 d_{i-1} di1 得到,保证 p p p 0 0 0 ~ p − 1 p-1 p1 的一个排列,现在要你将 s 0 s_0 s0 ~ s n s_n sn 进行排序,输出它们的排名。

由于 p p p 0 0 0 ~ p − 1 p-1 p1 的一个排列,说明每个位置只会被替换一次,而由于是按字典序排序,所以在前面的位会比后面的位影响要大,当更改了 0 0 0 位置后,就可以确定更改前的串与更改后的串的大小关系,所以可以考虑分治,每次处理区间内最前面的位置。

求区间最值可以用笛卡尔树,代码如下:

#include 
#include 
#include 
using namespace std;
#define maxn 2000010
#define inf 999999999

int T,n,seed,A,B,MOD;
int p[maxn],d[maxn],rk[maxn],id=0;
int zhan[maxn],t,ls[maxn],rs[maxn];
void build(){
	zhan[t=1]=0;
	for(int i=1;i<n;i++){
		while(t&&p[zhan[t]]>p[i])t--;
		if(t)ls[i]=rs[zhan[t]],rs[zhan[t]]=i;
		else ls[i]=zhan[1];
		zhan[++t]=i;
	}
}
void solve(int l,int r,int pos){
	if(l>=r||p[pos]==inf){for(int i=l;i<=r;i++)rk[i]=id++;return;}
	if(p[pos]%10>d[pos])solve(pos+1,r,rs[pos]),solve(l,pos,ls[pos]);
	else solve(l,pos,ls[pos]),solve(pos+1,r,rs[pos]);
}
#define mod 1000000007
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}

int main()
{
	scanf("%d",&T);while(T--)
	{
		scanf("%d",&n);
		scanf("%d %d %d %d",&seed,&A,&B,&MOD);
		for(int i=0;i<n;i++)p[i]=i;
		for(int i=1;i<n;i++){
			swap(p[seed%(i+1)],p[i]);
			seed=(1ll*seed*A+B)%MOD;
		}
		scanf("%d %d %d %d",&seed,&A,&B,&MOD);
		for(int i=0;i<n;i++){
			d[i]=seed%10;
			seed=(1ll*seed*A+B)%MOD;
		}
		for(int i=0;i<n;i++)if(p[i]%10==d[i])p[i]=inf;
		build();id=0;solve(0,n,zhan[1]);
		int ans=0;for(int i=0;i<=n;i++)ans=(ans+1ll*rk[i]*ksm(10000019,i)%mod)%mod;
		printf("%d\n",ans);
	}
}

L. Problem L is the Only Lovely Problem

判断一个字符串开头是否为 lovely,不在乎大小写。

送分题,代码如下:

#include 

char s[1000010];

int main()
{
	scanf("%s",s+1);
	for(int i=1;i<=6;i++)if(s[i]>='A'&&s[i]<='Z')s[i]=s[i]-'A'+'a';
	if(s[1]=='l'&&s[2]=='o'&&s[3]=='v'&&s[4]=='e'&&s[5]=='l'&&s[6]=='y')printf("lovely");
	else printf("ugly");
}

你可能感兴趣的:(随笔小结)