Codeforces Round #681 (Div. 2)题解+补题

A. Kids Seating
题目链接https://codeforces.ml/contest/1443/problem/A
题目大意
在1~4∗n中选出n个数,使得任意一个数对(a,b)不满足:gcd(a,b)=1或者a|b或b|a。
题目分析
找规律题,从4 * n - 2一直到2 * n依次减2得到的n个数一定满足题目条件
正解程序

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>

using namespace std;
typedef long long ll;
ll T,n;
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld",&n);
		ll times=4*n-2;
		for(ll i=1;i<=n;i++)
		{
			printf("%lld ",times);
			times-=2;
		}
		printf("\n");
	}
	
	return 0;
}

B.Saving the City
题目链接https://codeforces.ml/contest/1443/problem/B
题目大意
给定一个01字符串,1表示地雷,0表示空地。引爆一个地雷的代价是a,引爆后左右两个地方如果有地雷也会爆炸。还可以在空地埋上地雷,每埋一个的代价是b。问引爆所有地雷的最小代价。
题目分析
1.首先明白,连续的1是没有意义的,点爆任意一个1相当于点爆这连续的所有的1。于是我们把字符串简化为…010…010…这样的字符串。
2.现在假设对于第i个地雷来说,前面i-1个地雷已经引爆了,代价为sum,这时候有仅两种选择:

  • i-1i中间的t个0变成1,此时引爆的代价为sum+t*b
  • 单独点爆第i个地雷,此时引爆的代价为sum+a

3.所以我们只需要选择min(t*b,a)
4.不要忘记一定要把第一个点爆,所以如果存在地雷的话,其实代价应该是a
正解代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>

using namespace std;
typedef long long ll;
ll T,a,b,count1=0;
char str[100010],New[100010];
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		count1=0;
		scanf("%lld%lld",&a,&b);
		scanf("%s",str);
		ll len=strlen(str);
		ll t=0,flag=-1;
		char ch='.';
		//简化字符串
		for(ll i=0;i<len;i++)
		{
			if(str[i]=='1')
			{
				flag=i;
				break;
			}
		}
		for(ll i=flag;i<len;i++)
		{
			if(str[i]=='1' && ch=='1')
				;
			else if(str[i]=='1')
				New[count1++]=str[i];
			else
				New[count1++]=str[i];
			ch=str[i];
		}
		New[count1]='\0';
		ll ans=0;
		if(flag==-1)
			ans=0;
		else
			ans=a;
		for(ll i=1;i<count1;i++)
		{
			if(New[i]=='1')
			{
				ans+=min(t*b,a);
				t=0;
			}
			else
				t++;
		}
		printf("%lld\n",ans);
	}
	
	return 0;
}

C. The Delivery Dilemma
题目链接https://codeforces.ml/contest/1443/problem/C
题目大意
一个人要买n道菜,他可以自己去拿和叫买卖送,送外卖是平行进行的,而自己拿是独立进行的。告诉你每道菜自己去拿花的时间和外卖花的时间,求买选全部菜所需的最少时间。
题目分析
1.因为送外卖是平行的,所以如果我们选择其中m个送外卖,则耗时为m个时间中最大的那个,剩下的都被最大的覆盖了。
2.所以我们只需要枚举送外卖的最大时间,小于此时间的被覆盖,大于此时间的就人工运输即可。
3.为了提高效率,我们直接对其进行排序,并维护人工运输的前缀和。
正解代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>

using namespace std;
typedef long long ll;
const ll maxn=200010;
struct node
{
	ll sel;
	ll tra;
}num[maxn];
ll T,n,pre[maxn];
bool cmp(node a,node b)
{
	return a.tra<b.tra;
}
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		memset(pre,0,sizeof(pre));
		scanf("%lld",&n);
		for(ll i=1;i<=n;i++)//外卖的时间
			scanf("%lld",&num[i].tra);
		for(ll i=1;i<=n;i++)//自己拿的时间
			scanf("%lld",&num[i].sel);
		sort(num+1,num+1+n,cmp);
		for(ll i=1;i<=n;i++)
			pre[i]=pre[i-1]+num[i].sel;
		ll ans=1e18;
		for(ll i=0;i<=n;i++)
			ans=min(ans,max(num[i].tra,pre[n]-pre[i]));
		printf("%lld\n",ans);
	}
	
	return 0;
}

D. Extreme Subtraction
题目链接https://codeforces.ml/contest/1443/problem/D
题目大意
给你一个数字,你可以执行以下操作无数次:
1、选择前若干个数,每个数都减一
2、选择后若干个数,每个数都减一
问能否将每个数都变成0
题目分析
1.对于操作一,每次都是操作前缀,所以前面的数减一的次数一定不小于后面的数。把每个数进行操作一的次数列出来,就会形成一个非递增的序列fir。
2.对于操作一,每次都是操作后缀,所以后面的数减一的次数一定不小于前面的数。把每个数进行操作一的次数列出来,就会形成一个非递减的序列sec。
3.那么fir[i]+sec[i]=num[i](原序列)。对于序列fir,fir[i-1]≥fir[i],对于序列sec,sec[i-1]≤sec[i],所以用num和fir将sec换元,可以得到:num[i-1]-fir[i-1]≤num[i]-fir[i]fir[i]≤num[i]-num[i-1]+fir[i-1]
4.综上0≤fir[i]≤min(fir[i-1],num[i]-num[i-1]+fir[i-1])。因为num是定值,所以对于每个fir要尽可能取最大,能划为0的区间尽可能长。所以我们从前往后扫,如果此时fir[i]小于0了,说明就不满足了。如果扫完都没有不满足,那么必然成立。
正解程序

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>

using namespace std;
typedef long long ll;
const ll maxn=30010;
ll num[maxn],T,n;
ll fir[maxn];
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld",&n);
		for(ll i=1;i<=n;i++)
			scanf("%lld",&num[i]);
		fir[1]=num[1];
        bool flag=false;
        for(int i=2;i<=n;++i)
        {
            fir[i]=min(fir[i-1],num[i]-num[i-1]+fir[i-1]);
            if(fir[i]<0)
            {
                flag=true;
                break;
            }
        }
        if(flag)
        	printf("NO\n");
        else
        	printf("YES\n");
	}
	
	return 0;
}

E. Long Permutation
题目链接https://codeforces.ml/contest/1443/problem/E
题目大意
对于一个1~n的排列,每次有两个操作:
1、询问这个排序[l,r]之间的数字和是多少
2、将这个排列执行x次“下一个排列”
题目分析
1.根据数据范围,改变的排列最多是后15位,所以前面的序列是不变的,直接求前缀和。
2.因为x次“下一个排列”一定是比现在大的,所以把排列从1排序,根据现在的排列的序号,用取余的方法得到每一个数。(排列组合的知识)
3.要从不会增加只会减少的1~n中取数,每个数用一次,所以我们用vector来维护这个动态的过程。
正解代码

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <vector>

using namespace std;
typedef long long ll;
const ll maxn=200010;
vector<ll> st,ans;
ll q,n,now=0;
ll pre[maxn]={0},mul[maxn];

void nextone(ll x)
{
    ll cnt=0,t=1;
    st.clear();
    for(ll i=max(n-14,1LL);i<=n;++i)
    {
        st.push_back(i);
        ++cnt;
    }
    ans.clear();
    for(ll i=0;i<cnt;++i)
    {
        ll pos=x/mul[cnt-t];
        x%=mul[cnt-t];
        t++;
        ans.push_back(st[pos]);
        st.erase(st.begin()+pos);
        if(x==0)
        	break;
    }
    for(int i=0;i<st.size();i++)
        ans.push_back(st[i]);
}
int main()
{
	scanf("%lld%lld",&n,&q);
	mul[0]=1;
	for(ll i=1;i<=15;i++)
		mul[i]=mul[i-1]*i;
	for(ll i=1;i<=n;i++)
		pre[i]=pre[i-1]+i;
	for(ll i=1;i<=q;i++)
	{
		ll flag,l,r,x;
		ll sum=0;
		scanf("%lld",&flag);
		if(flag==1)
		{
			scanf("%lld%lld",&l,&r);
			nextone(now);
    		if(r>=n-14)
    			for(ll j=max(0LL,l-max(n-14,1LL));j<=r-max(n-14,1LL);j++)
    				sum+=ans[j];
    		if(l<n-14)
    			sum+=pre[min(r,n-15)]-pre[l-1];
    		printf("%lld\n",sum);
		}
		else
		{
			scanf("%lld",&x);
			now+=x;
		}
	}
	
	return 0;
}

F. Identify the Operations
题目链接https://codeforces.ml/contest/1443/problem/F
题目大意:
对于一个数组a,每次你可以移出一个数,然后将他相邻的其中一个数放入另一个数组尾部(开始为空),问有几种移出的方法使最后放入的那个数组和给定的数组b一样。
题目分析
1。很显然,对于一个在b中的数来说,此时a序列中与它相邻的两个数中一定有不在b中的数。有两个则有两种选择,一个则有一种选择,零个则不存在答案。
2.而当某个数y从a中被取走时,它一定满足相邻的数xyz有不在b中的数,假设为z。而如果它旁边的x在b中,那么这个数就继承了z这个能被取出的状态,因为此时序列变成了xz,z是不在b中的。
3.所以我们发现,当某个数被取出后,它的效果和原本不在b中的数是一样的,所以我们用一个数组来标记这个工程。
4.遍历一遍b,检查此数在a中左右两侧是否为合法状态,计算答案。
正解程序

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>

using namespace std;
typedef long long ll;
const ll maxn=2e5+10;
const ll mod=998244353;
ll a[maxn],pos[maxn],b[maxn];
bool used[maxn];
ll T,n,k;;
int main()
{	
	scanf("%lld",&T);
	while(T--)
	{
		memset(used,false,sizeof(used));
		ll n,k;
		scanf("%lld%lld",&n,&k);
		for(ll i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
			pos[a[i]]=i;
		}
		for(ll i=1;i<=k;i++)
		{
			scanf("%lld",&b[i]);
			used[pos[b[i]]]=true;
		}
		ll ans=1;
		for(ll i=1;i<=k;i++)
		{
			ll times=0;
			if(pos[b[i]]>1 && !used[pos[b[i]]-1]) 
				times++;
			if(pos[b[i]]<n && !used[pos[b[i]]+1])
				times++;
			ans*=times;
			if(ans==0)
				break;
			ans%=mod;
			used[pos[b[i]]]=false;
		}
		printf("%lld\n",ans);
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值