Codeforces Round #666 (Div. 2) 题解

A. Juggling Letters

题目描述:

n n n个字符串,每次操作你能将一个字符串的一个字符移动到任意字符串的任意字符,可以进行无数次操作,请问最后能将所有字符串都变成一样么。

解题思路:

看懂题目就很简单了,因为可以操作无数次,所以只要判断所有字符的个数是否是 n n n的倍数就可以了,如果所有字符的个数是否是 n n n的倍数那么一定可以达到要求。

代码:
#include <bits/stdc++.h>
using namespace std;
int main(){
	int t;
	cin>>t;
	while(t--)
	{
		int cnt[30];
		memset(cnt,0,sizeof(cnt));
		int n;char s[1005];
		cin>>n; 
		for(int i=0;i<n;i++)
		{
			scanf("%s",s);
			for(int j=0;j<strlen(s);j++)
				cnt[s[j]-'a']++;
		}
		int ans = 1;
		for(int i=0;i<26;i++)
			if(cnt[i]%n)ans=0;
		if(ans)printf("YES\n");
		else printf("NO\n");
	} 
	return 0;
} 

B. Power Sequence

题目描述:

有一个长度为 n n n的数组,有两个操作:
1、你可以将数组排序。
2、你可以将数组中的一个数 + 1 +1 +1或者 − 1 -1 1,每次操作花费 1 1 1
现在要把数组变成 a i = c i ( 0 ≤ i ≤ n − 1 ) a_i=c^i(0 \leq i \leq n-1) ai=ci(0in1), c c c可以是任意正整数,求最小花费。

解题思路:

因为 c n c^n cn的增长速度很快并且给出的数组中的数也不超过 1 0 9 10^9 109,所以我们只要枚举 c c c,当 c n c^n cn大到一定程度就退出就可以了,再大下去一定不是最优解。再把当 c = 1 c=1 c=1时单独计算。

代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int main(){
	long long n,a[maxn]; 
	cin>>n;
	long long ans = 0;
	for(int i=0;i<n;i++)
	{
		scanf("%lld",&a[i]);
		ans += a[i] - 1;
	}
	sort(a,a+n);
	for(int i=2;;i++)
	{
		long long sum = 0,s=1;
		int f=1;
		for(int j=0;j<n;j++)
		{
			sum += abs(s - a[j]);
			s*=i;
			if(s > 1e18)
			{
				f=0;break;
			}
		}
		if(f)ans=min(ans,sum);
		else break;
	} 
	cout<<ans<<endl;
	return 0;
} 

C. Multiples of Length

题目描述:

给一个长度为 n n n的数组,每次操作你可以将连续的 k k k个元素中的所有元素加上或减少 k k k的任意倍数(包括 0 0 0,并且每个元素加或减的数可以不同)。并且说了进行准确的三次操作一定可以将所有元素变为 0 0 0,要你给出这三次操作。

解题思路:

这个题目很有意思,刚看到感觉无从下手,仔细分析可以发现:我们将所有元素都乘 n n n后我们最后一部就能完成,现在我们需要通过前两补将所有 a [ i ] a[i] a[i]变成 a [ i ] ∗ n a[i]*n a[i]n,其实我们只要将所有 a [ i ] a[i] a[i]加上 a [ i ] ∗ ( n − 1 ) a[i]*(n-1) a[i](n1)就可以了( a [ i ] + a [ i ] ∗ ( n − 1 ) = a [ i ] ∗ n a[i] +a[i]*(n-1)=a[i]*n a[i]+a[i](n1)=a[i]n),那么我们第一次操作将前 ( n − 1 ) (n-1) (n1)个数完成,后一次操作只要完成最后一个数,但是我们操作的区间的长度有必须时 n − 1 n-1 n1,我们就把它前面的 ( n − 2 ) (n-2) (n2)个也算上,不过它们都 + 0 +0 +0就可以了。

代码:

n n n 1 1 1时特判

#include <bits/stdc++.h>
using namespace std;
const int inf = 1e9;
const int maxn = 1e5+5;
int main(){
	long long n,a[maxn]; 
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	if(n==1)
	{
		printf("1 1\n");
		printf("0\n");
		printf("1 1\n");
		printf("0\n");
		printf("1 1\n");
		printf("%lld\n",-a[1]);
		return 0;
	}
	printf("%d %d\n",1,n-1);
	for(int i=1;i<n;i++)
	{
		if(i>1)printf(" ");
		printf("%lld",a[i]*(n-1));
		a[i]*=n;
	}
	printf("\n");
	printf("%d %d\n",2,n);
	for(int i=2;i<=n;i++)
	{
		if(i!=n)printf("0 ");
		else {
		printf("%lld\n",a[n]*(n-1));
		a[n]*=n;	
		}
	}
	printf("%d %d\n",1,n);
	for(int i=1;i<=n;i++)
	{
		if(i>1)printf(" ");
		printf("%lld",-a[i]); 
	}
	printf("\n");
	return 0;
} 

D. Stoned Game

题目描述:

取石子游戏,有 n n n堆石子,每堆的数量是 a [ i ] a[i] a[i],没人每次可以从一堆石子中取出一个,但是不能和对手上一次取的是同一堆,不能取的失败,问:先手必胜还是必败。

解题思路:

经过分析发现,如果所有堆中石子数最大的那个堆的石子数量如果大于其它所有堆的石子总和,那么现在取的人一定胜利(因为1、我只要一直取这堆对手一定先取完别的石子,而且对手不能取这堆。2、如果现在我不能取这堆,证明上次对手取了,那个时候他已经面对这个必胜状态了)。如果不大于呢,因为我们不会将必胜态留给对手(比如:每次取最大的堆),所以大家只能一个一个的取直到取完,就是如果总石头是奇数先手必胜,否则必败。

代码:
#include <bits/stdc++.h>
using namespace std;
const int inf = 1e9;
const int maxn = 1e5+5;
int main(){
	int t;
	cin>>t;
	while(t--)
	{
		int n,sum,mx;
		sum = 0;
		mx = -1;
		cin>>n;
		for(int i=0;i<n;i++)
		{
			int x;
			cin>>x;
			mx = max(mx,x);
			sum += x; 
		}
		if(mx > (sum - mx))
			cout<<"T"<<endl;
		else if(sum % 2)
			cout<<"T"<<endl;
		else
			cout<<"HL"<<endl;
	} 
	return 0;
} 

E. Monster Invaders

题目描述:

n n n个场景,每个场景有 a [ i ] a[i] a[i]个普通怪物,一个Boss怪物,只有把这个场景中的全家部普通怪物消灭了才能打Boss。每个普通怪物血量为 1 1 1,每个Boss怪物血量为 2 2 2,玩家有 3 3 3把武器,第一把武器可以对当前场景中一个怪物造成一点伤害,花费是 r 1 r1 r1,第二把武器可以对当前场景中所有怪物各造成一点伤害,花费是 r 2 r2 r2,第三把武器可以直接杀死当前场景中一个怪物,花费是 r 3 r3 r3,并且 r 1 ≤ r 2 ≤ r 3 r1 \leq r2 \leq r3 r1r2r3。如果玩家打了一个Boss怪物,但没一枪把他杀死,那么玩家下一步操作必须移动到相邻场景(不能在开抢了),现在玩家在场景 1 1 1,每次移动只能移动到相邻场景,并且花费 d d d,问将所有场景的怪物杀死的最小花费。

解题思路:

这题写的不是很好,感觉有点乱,先放着。
对于一个场景,我们有两种消灭所有怪物的方法,方法一:直接消灭当前堆怪物,
方法二:不直接消灭,打两次解决,而打两次需要过这个场景两次,并且根据数据范围如果不算路途的花费,打两次解决的花费一定小于直接消灭的花费,那么么现在假设我要对场景 3 3 3打两次,发现反正要过场景 3 3 3两次,不如打到最后再回到这里。如图:p1

黑线和红线表示过的路径长度,发现黑色路径比较短,就是说如果我想对一个场景打两次,最好的办法就是打一下去相邻场景后马上回来,并且这样的话最后只可能在最后一个场景或者倒数第二个。
直接消灭当前堆怪物的花费是 a [ i ] ∗ r 1 + r 3 a[i]*r1+r3 a[i]r1+r3,记作 c a s t [ i ] [ 0 ] cast[i][0] cast[i][0],不直接消灭而是打一下先去相邻场景再回来的花费是 m i n ( r 2 + r 1 , r 1 ∗ ( a [ i ] + 2 ) min(r2 + r1,r1*(a[i]+2) min(r2+r1,r1(a[i]+2),记作 c a s t [ i ] [ 1 ] cast[i][1] cast[i][1]
因为前面知道了“想对一个场景打两次,最好的办法就是打一下去相邻场景后马上回来”,那么就可以线性的来做了,还有一点相邻两个场景的额外花费只需记一次,现在设 d p [ i ] [ 0 ] dp[i][0] dp[i][0]表示消灭了前 i i i个场景所有怪物并且第 i i i个场景是直接消灭的最小花费, d p [ i ] [ 1 ] dp[i][1] dp[i][1]表示消灭了前 i i i个场景所有怪物并且第 i i i个场景会去下一个场景后回来(为后一个场景提供服务,为了让下一个场景可以打两下)的最小花费。
d p [ i ] [ 0 ] dp[i][0] dp[i][0] 转移来自:
1、前一个场景也是直接消灭的,所以这个场景我也要直接消灭,就是 d p [ i − 1 ] [ 0 ] + c a s t [ i ] [ 0 ] + d dp[i-1][0]+cast[i][0]+d dp[i1][0]+cast[i][0]+d。( d d d是到前一个场景到当前场景的花费)
2、前一个场景也是通过下一个场景打两下消灭的,所以我这个场景也可以白打两下(额外花费前一个场景记录过了),就是 d p [ i − 1 ] [ 1 ] + c a s t [ i ] [ 1 ] + d dp[i-1][1]+cast[i][1]+d dp[i1][1]+cast[i][1]+d
d p [ i ] [ 1 ] dp[i][1] dp[i][1] 转移来自:
因为我要让这个场景去下一个场景再回来,所以我不用管前一个场景是否为当前场景服务,这个场景都会再回来一次,就是说当前场景可以打怪的花费是 c a s t [ i ] [ 1 ] cast[i][1] cast[i][1],总的就是 d p [ i − 1 ] [ 0 ] + c a s t [ i ] [ 1 ] + 2 ∗ d + d dp[i-1][0]+cast[i][1] +2*d +d dp[i1][0]+cast[i][1]+2d+d( 2 ∗ d 2*d 2d是来回花费)。
最后只要判断下在最后场景花费少还是在倒数第二个场景花费少。

代码:
#include <bits/stdc++.h>
using namespace std;
const int inf = 1e9;
const int maxn = 2e6+5;
long long a[maxn];
long long cast[maxn][2];
long long dp[maxn][2];
int main(){
 	long long n,r1,r2,r3,d;
	cin>>n>>r1>>r2>>r3>>d; 
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	{
		cast[i][0] = r1*a[i] + r3;//消灭第i堆直接消灭的花费 
		cast[i][1] = min(r2 + r1,r1*(a[i]+2));//不直接消灭的花费 
	}
	dp[0][0] = -d;dp[0][1] = d;//注意初始化
	for(int i=1;i<=n;i++)
	{
		dp[i][0] = min(dp[i-1][0]+cast[i][0],dp[i-1][1]+cast[i][1])+d;
		dp[i][1] = dp[i-1][0]+cast[i][1] +2*d +d;
	}
	cout<<min(dp[n][0],dp[n-1][1]+cast[n][0])<<endl;
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值