AtCoder Beginner Contest 369 A~F

A.369(思维)

题意:

给你两个整数 A A A B B B

有多少个整数 x x x满足以下条件?

  • 条件:可以将三个整数 A A A B B B x x x按一定顺序排列,组成一个等差序列。

当且仅当 q − p q-p qp等于 r − q r-q rq时,按此顺序排列的三个整数 p p p q q q r r r的序列是等差序列。

分析:

题意为给你两个数 A A A B B B,再加一个整数,使得它与 A , B A,B A,B构成等差数列,问有几个数满足条件。

分情况讨论:

  • A = B A=B A=B,答案为 1 1 1

  • ( A + B ) / 2 (A+B)/2 (A+B)/2为整数,答案为 3 3 3

  • ( A + B ) / 2 (A+B)/2 (A+B)/2不为整数,答案为 2 2 2

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;

void solve(){
	int a,b;
    cin>>a>>b;
    if(a==b){
        cout<<1<<endl;
    }
	else{
        if((a+b)%2==0){
            cout<<3<<endl;
        }
		else{
            cout<<2<<endl;
        }
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

B.Piano 3(模拟)

题意:

高桥有一架钢琴,上面有 100 100 100个琴键排成一行。从左边开始的 i i i个键叫做第 i i i个键。

他将依次按下 N N N个琴键来演奏音乐。对于第 i i i个按键,如果 S _ i = L S\_i=L S_i=L,他会用左手按 A _ i A\_i A_i键,如果 S _ i = R S\_i=R S_i=R,他会用右手按 A _ i A\_i A_i

开始演奏前,他可以将双手放在任意键上,此时疲劳度 0 0 0。演奏过程中,如果他将一只手从 x x x移到 y y y键上,疲劳度会增加 ∣ y − x ∣ |y-x| yx(反之,除移动双手外,疲劳度不会增加)。用手按下某个键时,该手必须放在该键上。

找出表演结束时可能的最低疲劳度。

分析:

按题意模拟,用一个map记录左右手当前位置,然后移动到下一个位置时计算距离(差值的绝对值),累计求和。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;

void solve(){
    int n;
    cin>>n;
    int ans=0;
    map<char,int>pos;
    while(n--){
        int p;
        string s;
        cin>>p>>s;
        if(pos.find(s[0])!=pos.end()) {
            ans+=abs(pos[s[0]]-p);
        }
        pos[s[0]]=p;
    }
    cout<<ans<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

C.Count Arithmetic Subarrays(差分)

题意:

给你一个由 N N N个正整数 A = ( A _ 1 , A _ 2 , … , A _ N ) A=(A\_1,A\_2,\dots,A\_N) A=(A_1,A_2,,A_N)组成的序列。

求满足 1 ≤ l ≤ r ≤ N 1\leq l\leq r\leq N 1lrN的一对整数 ( l , r ) (l,r) (l,r)中,子序列 ( A _ l , A _ l + 1 , … , A _ r ) (A\_l,A\_{l+1},\dots,A\_r) (A_l,A_l+1,,A_r)构成等差数列的个数。

当且仅当存在一个 d d d使得 x _ i + 1 − x _ i = d   ( 1 ≤ i < ∣ x ∣ ) x\_{i+1}-x\_i=d\ (1\leq i\lt |x|) x_i+1x_i=d (1i<x)是等差数列时,序列 ( x _ 1 , x _ 2 , … , x _ ∣ x ∣ ) (x\_1,x\_2,\dots,x\_{|x|}) (x_1,x_2,,x_x)才是等差数列。长度为 1 1 1的序列总是等差数列。

分析:

求一个a的差分数组 b b b,看差分数组中相同数字连续有几个(设为 c c c个),则这一段的答案为 C _ c 2 \displaystyle C\_{c}^2 C_c2,即 c ( c − 1 ) 2 \displaystyle\frac{c(c-1)}{2} 2c(c1),将答案累加即可。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N=200005;
const LL MOD=1000000007;
LL n;
LL v[N],b[N];
LL ans;
void solve(){
	cin>>n;
	if(n==1){
		cout<<1<<endl;
		return;
	}
	for(LL i=0;i<n;i++){
		cin>>v[i];
	}
	for(LL i=1;i<n;i++){
		b[i]=v[i]-v[i-1];
	}
	LL c=2;
	vector<LL> p;
	for(LL i=2;i<n;i++){
		if(b[i]!=b[i-1]){
			p.push_back(c);
			c=2;
		}else{
			c++;
		}
	}
	p.push_back(c);
	for(LL x:p){
		ans+=x*(x-1)/2;
	}
	cout<<ans+n<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

D.Bonus EXP(动态规划)

题意:

高桥将依次遇到 N N N只怪物。第 i i i个怪物 ( 1 ≤ i ≤ N ) (1\leq i\leq N) (1iN)的力量为 A _ i A\_i A_i

对于每只怪物,他都可以选择放走或打败它。 每次行动都会获得以下经验值:

  • 如果放走怪物,他将获得 0 0 0点经验值。

  • 如果他以 X X X的力量击败了怪物,则会获得 X X X点经验值。 如果击败的是偶数个怪物(第2个、第4个…),他将额外获得 X X X点经验值。

求他能从 N N N只怪物身上获得的经验值上限。

分析:

题意为较朴素的 d p dp dp,显然对于每只怪兽考虑打或不打,如果选择打,其结果会受是否是偶数这一状态的影响,因此 d p dp dp状态,除了包含基本状态 考虑前 i i i只怪兽外,还要加上状态打败了奇数/偶数只怪兽这一 0 / 1 0/1 0/1状态。

由此可得递推方程:定义状态 f _ i , 0 f\_{i,0} f_i,0表示到第 i i i个为止,已经选了奇数个数的最大价值, f _ i , 1 f\_{i,1} f_i,1表示到第 i i i个为止,已经选了偶数个数的最大价值。

对于每个位置可以考虑选或不选,所以有以下转移:

  • f _ i , 0 = m a x ( f _ i − 1 , 0 , f _ i − 1 , 1 + A _ i ) f\_{i,0}=max(f\_{i−1,0},f\_{i−1,1}+A\_i) f_i,0=max(f_i1,0,f_i1,1+A_i)

  • f _ i , 1 = m a x ( f _ i − 1 , 1 , f _ i − 1 , 0 + 2 × A _ i f\_{i,1}=max(f\_{i−1},1,f\_{i−1,0}+2×A\_i f_i,1=max(f_i1,1,f_i1,0+2×A_i)

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=200005;
const int MOD=1000000007;
LL f[N][2],a[N],n;
void solve(){
	cin>>n;
	f[0][0]=-1e15;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		f[i][0]=max(f[i-1][0],f[i-1][1]+a[i]);
		f[i][1]=max(f[i-1][1],f[i-1][0]+2*a[i]);
	}
	cout<<max(f[n][0],f[n][1])<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

E.Sightseeing Tour(最短路、全排列)

题意:

问题陈述

N N N个岛屿和 M M M座双向桥梁连接两个岛屿。这些岛屿和桥梁的编号分别为 1 1 1 2 2 2 … \ldots N N N 1 1 1 2 2 2 … \ldots M M M。 大桥 i i i连接着岛屿 U _ i U\_i U_i V _ i V\_i V_i,从任一方向穿过大桥所需的时间为 T _ i T\_i T_i

没有一座桥将一个岛屿与自己连接起来,但是两个岛屿有可能被不止一座桥直接连接起来。 人们可以通过一些桥梁来往于任意两个岛屿之间。

给你 Q Q Q个问题,请逐一回答。 第 i i i个查询如下:

给你 K _ i K\_i K_i个不同的桥:桥 B _ i , 1 , B _ i , 2 , … , B _ i , K _ i B\_{i,1},B\_{i,2},\ldots,B\_{i,K\_i} B_i,1,B_i,2,,B_i,K_i,求从岛屿 1 1 1到岛屿 N N N所需的最少时间,每座桥梁至少使用一次。

只考虑过桥的时间。

你可以以任何顺序、朝任何方向通过给定的桥梁。

分析:

考虑先求出任意两点的最短路径,由 n ≤ 500 n\le500 n500,选择使用 F l o y d Floyd Floyd算法求一下多源最短路。

然后对于每组询问,必须要经过编号为 B _ i , 1 , B _ i , 2 , … , B _ i , K _ i B\_{i,1},B\_{i,2},\ldots,B\_{i,K\_i} B_i,1,B_i,2,,B_i,K_i的桥,定义编号为 i i i的桥为 ( u _ i , v _ i , w _ i ) (u\_i,v\_i,w\_i) (u_i,v_i,w_i)

同时注意到 k ≤ 5 k\le 5 k5非常小,考虑全排列出依次经过哪些桥,同时暴力搜索是先到达这个桥的左端还是右端即可。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=505;
const int MOD=1000000007;
const LL M=2e5+10;
LL n,m,q,k,x,y,w,ans;
LL a[N],p[N],A[M],B[M],W[M];
LL dis[N][N];
void dfs(LL pos,LL pre,LL sum){
	if(sum>ans)
	  return ;
	if(pos==k+1){
		ans=min(ans,sum+dis[pre][n]);
		return ;
	}
	dfs(pos+1,A[a[p[pos]]],sum+dis[pre][B[a[p[pos]]]]+W[a[p[pos]]]);
	dfs(pos+1,B[a[p[pos]]],sum+dis[pre][A[a[p[pos]]]]+W[a[p[pos]]]);
}

void Floyd(){
	for(int k=1;k<=n;k++)
	  for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		  dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i==j)
			  continue;
			dis[i][j]=1e18;
		}
	}
	for(int i=1;i<=m;i++){
		cin>>x>>y>>w;
		dis[x][y]=min(dis[x][y],w);
		dis[y][x]=min(dis[y][x],w);
		A[i]=x,B[i]=y,W[i]=w;
	}
    Floyd();
	cin>>q;
	while(q--){
		ans=1e18;
		cin>>k;
		for(int i=1;i<=k;i++){
			cin>>a[i];
			p[i]=i;
		}
		while(1){
			dfs(1,1,0);
			if(!next_permutation(p+1,p+k+1))
			  break;	
		}
		cout<<ans<<endl;
	}
	return 0;
}

F.Gather Coins(思维)

题意:

有一个网格,网格中有 H H H行和 W W W列。 ( i , j ) (i,j) (i,j)表示从上往下数第 i i i行,从左往上数第 j j j列的单元格。

在这个网格中有 N N N枚硬币,通过 ( R _ i , C _ i ) (R\_i,C\_i) (R_i,C_i)单元可以拾取 i i i枚硬币。

你的目标是从 ( 1 , 1 ) (1,1) (1,1)单元格开始,反复向下或向右移动一个单元格,到达 ( H , W ) (H,W) (H,W)单元格,同时尽可能多地拾取硬币。

请找出能拾取的最大硬币数以及能达到最大值的路径之一。

分析:

由于题目限制只能向右或向下移动,所以一个格子只能从它左边或上面的格子转移而来。总格子数很多,但有硬币的格子不多,可以只考虑有硬币之间的格子的转移,最后对右下角特殊考虑即可。把硬币的坐标以行数为第一关键字,列数为第二关键字进行排序,那么一个格子就可以从之前已经扫描过的,列数小于等于它的格子转移而来,可以使用树状数组维护这一点。为了输出路径,树状数组要存储的是格子编号而非具体数量。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=2e5 + 5;
const int MOD=1000000007;

int n,m,k,t[N], pre[N], num[N];
pair<int, int> a[N];

void out(int x) {
    if (!x) return;
    out(pre[x]);
    for(int i=a[pre[x]].first;i<a[x].first;++i) cout<<"D";
    for(int i=a[pre[x]].second;i<a[x].second;++i) cout<<"R";
    return ;
}

void update(int p,int id) {
    for(;p<=m;p+=p & -p)
        if(num[id]>num[t[p]]) 
            t[p]=id;
    return;
}

int query(int p) {
    int res=0,id=0;
    for(; p; p -= p & -p)
        if(num[t[p]]>res) {
            res=num[t[p]];
            id=t[p];
        }
    return id;
}

void solve(){
    cin>>n>>m>>k;
    for(int i=1;i<=k;++i) 
        cin>>a[i].first>>a[i].second;
    sort(a+1,a+1+k);
    a[0]=make_pair(1,1);
    a[k+1]=make_pair(n,m);
    for (int i=1;i<=k;++i) {
        pre[i]=query(a[i].second);
        num[i]=num[pre[i]]+1;
        update(a[i].second, i);
    }
    pre[k+1]=query(m);
    num[k+1]=num[pre[k+1]];
    cout<<num[k+1]<<endl;
    out(k+1);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值