CodeForces 740 div 2(A-E)

codeforces 740div2

A. Simply Strange Sort

题目思路:

给一个数组 a a a,下标为奇数时,满足 a i > a i + 1 a_i>a_{i+1} ai>ai+1时,更改 a i 和 a i + 1 a_i和a_{i+1} aiai+1的位置, a n s + + ans++ ans++,并对所有下标为奇数的序列重复这种操作。下标为偶数时也是同样操作。操作顺序起点从 1 1 1 n n n,直到整个序列是一个有序序列就停止,输出 a n s ans ans的值(如果序列已经有序则为 0 0 0)。这道题第一次做有好多地方没有理解清楚,开始打了个冒泡~~(第一印象确实很像)~~,莽错了好几发。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N=1e3+10;
int a[N];
int main()
{
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>a[i];
        }
        long long ans=0;
        bool ok=0;
        for(int i=1;i<n;i++){
            if(a[i]>a[i+1]){
                ok=1;
                break;
            }
        }
        while(ok){
            ans++;
            int p;
            if(ans%2==1)p=1; //确定初始起点
            else p=2;
            for(int i=p;i<n;i+=2){  //进行替换
                if(a[i]>a[i+1]){
                    swap(a[i],a[i+1]);
                }
            }
            ok=0;
            for(int i=1;i<n;i++){ //判断
                if(a[i]>a[i+1]){
                    ok=1;
                    break;
                }
            }
        }
        cout<<ans<<endl;
    }
}

B. Charmed by the Game

题目思路:

首先本题有个概念需要理解——破发,对于一个球类游戏,A是发球局,但是B赢了本场比赛,那么称

B完成了破发。

然后给定 a a a, b b b,分别为A赢的局数和B赢的局数,开始谁发球都可以,但每局结束必须换发,问最后有多少个可能的破发局,并输出所有情况。

此题需要设一个方程,总局数 s u m = a + b sum=a+b sum=a+b,对于A来说, a = x + p a − p b , b = y + p b − p a a=x+p_a-p_b,b=y+p_b-p_a a=x+papb,b=y+pbpa,其中 x x x a a a的发球局数 = a + b 2 或 者 a + b + 1 2 [ 取 决 于 谁 先 发 球 ] =\frac{a+b}{2}或者\frac{a+b+1}{2}[取决于谁先发球] =2a+b2a+b+1[] p a p_a pa a a a赢的破发局数。 y y y b b b的发球局数 = s u m − x =sum-x =sumx p b p_b pb b b b赢的破发局数。两式相加可得到 a + b = x + y a+b=x+y a+b=x+y,这样,就可以枚举每一个 p a p_a pa,就可以得到 p b = b − y + p a p_b=b-y+p_a pb=by+pa。同时注意谁先发球都可以,如果 a a a先发球,那么 p a ∈ [ 0 , s u m + 1 2 ] p_a∈[0,\frac{sum+1}{2}] pa[0,2sum+1],否则 p a ∈ [ 0 , s u m 2 ] p_a∈[0,\frac{sum}{2}] pa[0,2sum],最后输出答案就可以了。

这题不确定量的关系比较复杂,有时设方程的思想能够理清思路

代码:

#include<bits/stdc++.h>
using namespace std;
set<int> s;
 //这里用集合的操作,既可以满足顺序,也可以去重。
int main()
{
	int _;
	scanf("%d",&_);
	while(_--){
		int a,b,p,q;
		cin>>a>>b;
		p=(a+b+1)/2;q=(a+b)/2;//a先手比b的发球局多以局
		for(int i=0;i<=p;i++){
			int y=(a+i-p);
			if(y<=q&&y>=0){  //y的局数有限制
				s.insert(i+y);
			}
		}
		p=(a+b)/2;q=(a+b+1)/2;  //a后手
		for(int i=0;i<=p;i++){
			int y=(a+i-p);
			if(0<=y&&y<=q)s.insert(i+y);
		}
		cout<<s.size()<<endl;
		for(auto i:s){
			cout<<i<<" ";
		}
		cout<<endl;
		s.clear();
	}
}

C. Deep Down Below

题目思路:

n n n个洞口,每个洞口里有 k k k个怪物,排成一行,一个战士拥有一个初始值战斗力 p p p,当战斗力大于怪兽生命值时才能打过,每打过一个怪兽就加 1 1 1点战斗力,每个洞必须把所有怪物都打过才能进入下一个洞。问战士初始战斗力的最小值是多少?

其实这道题我第一次做的时候很清楚是二分,但是二分的值出了问题。对于每个洞 i i i可以简化成一个值 a i a_i ai,代表通过这个洞的最小战斗力,也就等于洞中的最大生命-位置+2,然后直接对最小战斗力的值进行二分, j u d g e judge judge函数是从所有洞最小战斗力从小到大进行判断,能通过就小一点,不能通过就大一点。相当于最大值最小的二分、

代码:

#include<bits/stdc++.h>
using namespace std;
 
typedef unsigned long long ull;
 
const int N=1e5+10;
struct node{
    ull st,ed;
    bool operator < (const node& k){
        if(st==k.st){
            return ed>k.ed;
        }
        return st<k.st;
    }
}s[N];
int n;
bool judge(ull k){
    ull all=k;
    for(int i=1;i<=n;i++){
        if(all>=s[i].st){
            all+=s[i].ed;
        }
        else return 0;
    }
    return 1;
}
int main()
{
    int t;
    cin>>t;
    while(t--){
        int p;
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>p;
            s[i].ed=p;
            int x,mx=0;
            for(int j=1;j<=p;j++){
                cin>>x;
                if(mx<x-j+2)mx=x-j+2;
            }
            s[i].st=mx;
        }
        sort(s+1,s+1+n);;
        int l=1,r=1e9+10,mid;
        while(l<=r){
            mid=l+r>>1;
            if(judge(mid)){
                r=mid-1;
            }
            else l=mid+1;
        }
        cout<<l<<endl;
    }
}

D. Up the Strip

题目思路:

给定 n n n, m m m。从 1 1 1 n n n,每个数可以减去一个数,但减完必须大于等于1。也可以除以一个数。步骤的执行顺序不同那么两方案也不同,问一共有多少种方法使得 n n n经过变换后成为1。考虑方案数很多,要求答案模 m m m

对于这个题,可以很直接的写出暴力搜索算法, d p [ i ] = ∑ j = 1 i − 1 d p [ i − j ] + ∑ j = 2 i d p [ i / j ] dp[i]=\sum_{j=1}^{i-1}dp[i-j]+\sum_{j=2}^{i}dp[i/j] dp[i]=j=1i1dp[ij]+j=2idp[i/j] ,时间复杂度 O ( n 2 ) O(n^2) O(n2)考虑到 2 ≤ n ≤ 2 ⋅ 1 0 5 2\le n\le2·10^5 2n2105,需要优化。第一个部分比较容易优化, d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] + d p [ i − 3 ] + . . . d p [ 1 ] dp[i]=dp[i-1]+dp[i-2]+dp[i-3]+...dp[1] dp[i]=dp[i1]+dp[i2]+dp[i3]+...dp[1],可以提前维护前缀和 t o t + = d p [ i ] tot+=dp[i] tot+=dp[i],那么 d p [ i + 1 ] + = t o t dp[i+1]+=tot dp[i+1]+=tot,这样第一部分就优化成了 O ( n ) O(n) O(n)

第二部分的优化有两种方式:

1.整除分块优化

由于整除时向下取整的特性,就可以分块来优化。可以优化成 O ( n ) O(\sqrt{n}) O(n ),这样总的复杂度就 O ( n n ) O(n\sqrt{n}) O(nn ),对于 2 ⋅ 1 0 5 2·10^5 2105的时间复杂度是可以通过的。

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
ll dp[N];
ll n,m,sum,tot=1; 
int main()
{
	int id=1;
	cin>>n>>m;
	dp[1]=1;
	for(int i=2;i<=n;i++){
		dp[i]=(dp[i]+tot)%m;
		id=2;
		while(i/id>=1){
			int q=i/id;
			int r=i/q;
			dp[i]=((dp[i]+(dp[q]*(r-id+1))%m))%m;
			//dp[i]=(dp[i]+dp[i/id])%m;
			id+=(r-id+1);
		}
		tot=(tot+dp[i])%m;
	}
	cout<<dp[n]<<endl;
}
2.调整递推的更新顺序

由于 i / j i/j i/j会向下取整,分析到这个范围是 [ i ∗ j , i ∗ j + j − 1 ] [i*j,i*j+j-1] [ij,ij+j1],令 k ∈ [ i ∗ j , i ∗ j + j − 1 ] k∈[i*j,i*j+j-1] k[ij,ij+j1],则每一个 d p [ k ] dp[k] dp[k]

都要加上 d p [ i ] dp[i] dp[i]。这样的区间操作可以在 O ( l o g n ) O(logn) O(logn)的时间内完成,那么从时间复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn),可以通过 n ≤ 4 ∗ 1 0 6 n\le4*10^6 n4106的数据范围。不过实现上有两种方式,第一种是线段树,第二种是维护前缀和。维护前缀和就是设置前缀和数组 a [ i ] a[i] a[i],令 a [ i ∗ j ] + = d p [ i ] a[i*j]+=dp[i] a[ij]+=dp[i], a [ i ∗ j + j ] − = d p [ i ] a[i*j+j]-=dp[i] a[ij+j]=dp[i],同时答案更新是 d p [ i ] + = a [ i ] dp[i]+=a[i] dp[i]+=a[i],再加上第一部分维护的前缀和 t o t tot tot就好了。那么维护前缀和时为什么是 O ( n l o g n ) O(nlog n) O(nlogn)的时间复杂度呢。对于每一个 i i i,更新时需要满足 i ∗ j ≤ n i*j\le n ijn,那么当 i = 1 i=1 i=1时,枚举次数为 n n n,当 i = 2 i=2 i=2,为 n / 2 n/2 n/2,当 i = 3 i=3 i=3,为 n / 3 n/3 n/3,当 i = n i=n i=n时,为 1 1 1,那么总和为:

( n + n / 2 + n / 3 + n / 4 + ⋅ ⋅ ⋅ 1 ) = n ( 1 + 1 / 2 + 1 / 3 + ⋅ ⋅ ⋅ 1 / n ) = n ∫ 1 x d x = n l o g n (n+n/2+n/3+n/4+···1)= n(1+1/2+1/3+···1/n) =n\int \frac{1}{x} dx = nlogn (n+n/2+n/3+n/4+1)=n(1+1/2+1/3+1/n)=nx1dx=nlogn.

代码:
#include<bits/stdc++.h>
using namespace std;
const int N=4e6+10;
typedef long long ll;
ll dp[N],a[N],n,m,tot;
int main()
{
	cin>>n>>m;
	dp[1]=1;
	for(ll i=1;i<=n;i++){
		a[i]=(a[i]+a[i-1])%m;
		dp[i]=((dp[i]+tot)%m+a[i])%m;
		for(ll j=2;j*i<=n;j++){
			ll l=j*i,r=j*i+j-1;
			if(r>n+1)r=n+1;
			//r=min(r,n+1);
			a[l]=(a[l]+dp[i])%m;
			a[r+1]=(a[r+1]-dp[i]+m)%m;
		}
		tot=(tot+dp[i])%m;
	}
	cout<<dp[n]<<endl;
}

E. Bottom-Tier Reversals

题目思路:

给一个序列 a = [ a 1 , a 2 , ⋅ ⋅ ⋅ , a n ] a=[a_1,a_2,···,a_n] a=[a1,a2,,an],保证 n n n为奇数, n ≤ 2021 n\le2021 n2021规定每次只能对奇数做一个反转操作,即设奇数为 i i i,那么 [ 1 , i ] [1,i] [1,i]要进行反转,并且反转次数不能超过 5 n 2 \frac{5n}{2} 25n。输出每个序列要进行怎么样的反转才能使得整个序列从小到大有序,不能的输出-1。

其实很像紫书上的一道构造题,介绍构造入门的时候用的例题。不过区别在与此题之只能反转奇数并且还有反转次数限制。首先判断是否可以完成反转,可以观察到再反转的时候,奇数还是跑到奇数的位置上,偶数还是在偶数的位置上,那么就可以断定,如果就必须满足对于每一个 i i i,都有 i % 2 = a [ i ] % 2 i\%2=a[i]\%2 i%2=a[i]%2,否则就没有答案。那么剩下都是有答案的,且要在反转到奇数的同时把相邻的偶数也反转到。首先要倒着来,找到等于 i i i a [ j ] a[j] a[j],将 [ 1 , j ] [1,j] [1,j]进行一次反转,第二步,找到等于 i − 1 i-1 i1 a [ p ] a[p] a[p],反转 p − 1 p-1 p1,此时 a [ j ] , a [ p ] a[j],a[p] a[j],a[p]相邻,此时再把 [ 1 , i ] [1,i] [1,i]反转,有 a [ p ] , a [ j ] a[p],a[j] a[p],a[j]相邻,再将 [ 1 , j ] [1,j] [1,j]反转,有 a [ j ] , a [ p ] a[j],a[p] a[j],a[p]开头的序列,此时再反转 [ 1 , i ] [1,i] [1,i],就把 i i i i − 1 i-1 i1都排好了。如果对每一个奇数从大到小都来一遍是可行的,但是答案还有一个要求,就是不能超过 5 n 2 \frac{5n}{2} 25n ,此时的次数为 5 ( n + 1 ) 2 \frac{5(n+1)}{2} 25(n+1),那就不能对奇数做这样的操作,而是对偶数,操作是一致的,不过次数操作数变成了 5 ( n − 1 ) 2 \frac{5(n-1)}{2} 25(n1),就满足了。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2023;
int a[N];
vector<int> ans;
int n;
int findx(int x){
	for(int i=1;i<=n;i++){
		if(a[i]==x)return i;
	}
}
int main()
{
	int t;
	cin>>t;
	while(t--){
		cin>>n;
		for(int i=1;i<=n;i++)cin>>a[i];
		bool ok=1;
		for(int i=1;i<=n;i++){
			if(a[i]%2!=i%2){
				//cout<<a[i]<<" "<<i<<endl; 
				ok=0;
				break;
			}
		}
		if(!ok){
			cout<<-1<<endl;
			continue;
		}
		for(int i=n-1;i>=1;i-=2){
			int j=findx(i+1);
			ans.push_back(j);
			reverse(a+1,a+1+j);
			
			int p=findx(i);
			ans.push_back(p-1);
			reverse(a+1,a+p);
			ans.push_back(i+1);
			reverse(a+1,a+2+i);
			
			j=findx(i+1);
			ans.push_back(j);
			reverse(a+1,a+1+j);
			
			ans.push_back(i+1);
			reverse(a+1,a+2+i);
		}
		int len=ans.size();
		cout<<len<<endl;
		for(int i=0;i<len;i++){
			cout<<ans[i]<<" ";
		}
		if(len!=0)cout<<endl;
		ans.clear();
	}	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值