Codeforces Round #783 (Div. 2)

A. Direction Change

在这里插入图片描述

Sample input

6
1 1
2 1
1 3
4 2
4 6
10 5

Sample output

0
1
-1
6
10
17

题意:

现在有个n*m的迷宫,你可以向四个方向走,但是不能两次走同一个方向,问你最少走多少步从左上角走到右下角,若无法走到则输出-1

思路:

首先考虑方阵从左上角走到右下角需要的步数就是2*n-2,那么就想到了朝着比较长的那个边界走,然后构造出方阵情况就行了,下面看代码

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

void solve(){
	int n,m;
	cin>>n>>m;
	if(n > m) swap(n,m);
	if(n == 1){
		if(m <= 2) cout<<m-1<<endl;
		else cout<<-1<<endl;
	}
	else{
		int cha = m-n,ans = 2*n - 2 + cha*2 ;
		if(cha&1) ans --; 
		cout<<ans<<endl;
	}
}
int main(){
	int _;
	for(cin>>_;_;_--) solve();
	return 0;
} 

B. Social Distance

在这里插入图片描述

Sample input

6
3 2
1 1 1
2 4
1 1
2 5
2 1
3 8
1 2 1
4 12
1 2 1 3
4 19
1 2 1 3

Sample output

NO
YES
NO
YES
NO
YES

题意:

有n个人和m把椅子,这m把椅子是环形的,每个人都有一个value值ai,表示第i个人坐在的位置左边和右边都至少空着ai个椅子,问能不能使得所有人都坐下

思路:

先按照value值从小到大排序,然后按顺序安排就行,不排序的话会WA,因为一开始的那一段空白会浪费,所以说浪费的最小一定是最优的,下面看代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200010;
int a[N];
void solve(){
	int n,m;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	sort(a+1,a+1+n);
	m -= 2*a[1]+1;
	for(int i=2;i<=n;i++){
		if(a[i] > a[i-1]) m -= a[i]-a[i-1];
		m --;
		if(i != n)
			m -= a[i];
		else 
			m -= a[1]<a[i]?a[i]-a[1]:0;
	}
	puts(m>=0?"YES":"NO");
}
signed main(){
	int _;
	for(cin>>_;_;_--) solve();
	return 0;
} 

C. Make it Increasing

在这里插入图片描述
在这里插入图片描述

题意:

有一个长度为n的数组a和长度为n的数组b,数组b的所有元素都为0,你可以进行一种操作让bi = bi-ai 或 bi = bi + ai,问你最少需要多少次能使得b是一个单调递增的序列

思路:

通过分析样例发现最优解中b数组的元素一定有一个是0,那么就可以枚举0的位置,然后这个位置以前的最小操作次数确定了,以后的最小操作次数也确定了,总时间复杂度为O(n^2),下面看代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
long long a[N];
void solve(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	long long ans = LLONG_MAX;
	for(int i=1;i<=n;i++){
		long long re = 0,cnt = 0;
		for(int j=i+1;j<=n;j++){
			long long t = re/a[j]+1;
			cnt += t;
			re = 1ll*a[j]*t;
		}
		re = 0;
		for(int j=i-1;j;j--){
			long long t = re/a[j]+1;
			cnt += t;
			re = 1ll*a[j]*t;
		}
		ans = min(ans,cnt);
	}
	printf("%lld\n",ans);
}
signed main(){
	int _;
	for(_=1;_;_--) solve();
	return 0;
} 

D. Optimal Partition

在这里插入图片描述

Sample input

5
3
1 2 -3
4
0 -2 3 -4
5
-1 -2 3 -1 -1
6
-1 2 -3 4 -5 6
7
1 -1 -1 1 -1 -1 1

Sample output

1
2
1
6
-1

题意:

有一个长度为n的数组,这个数组的子段l,r,若sum(a[l],…,a[r]) > 0,那么这段的贡献就是这段的长度,若sum < 0 那么这段的贡献就是这段的长度取负,若sum = 0,那么这段的贡献就是0,现在问你所有的划分方式中的贡献值之和的最大值

思路:

首先可以写一个n*2动态规划,代码如下


for(int i=1;i<=n;i++){
	f[i] = f[i-1];
	if(a[i] > 0) f[i]++;
	if(a[i] < 0) f[i]--; 
	for(int j=1;j<i;j++)
		if(sum[i] > sum[j]) f[i] = max(f[i],f[j]+i-j);
}

状态转移分为单独把这个数给拿出来和跟前面的数合并,单拿出来的话就是判断正负,合并的话就是跟前面的每一个判一下,如果说子段和为负数就不用了,因为合不合的都一样,如果说子段和为0的话也是不能转移,这样会发现是在每一层循环中找到了f[j]-j的最大值跟i相加,所以说只需要维护一下比sun[i[小的f[j]-j的最大值就行就用树状数组维护一下就行,下面请看代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 500010;
long long a[N],tr[N],s[N];
vector<long long> alls;
inline int lowbit(int x){
	return x & -x;
}
void add(long long x,long long y){
	for(int i=x;i<N;i+=lowbit(i)) tr[i] = max(tr[i],y);
}
long long query(long long pos){
	long long ans = -0x3f3f3f3f;
	for(int i=pos;i;i-=lowbit(i)) ans = max(ans,tr[i]);
	return ans;
}
long long f[N];
int find(long long x){
	return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
void solve(){
	alls.clear();
	int n;
	scanf("%d",&n);
	for(int i=0;i<=n;i++) s[i] = 0;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),s[i] = s[i-1] + a[i],alls.push_back(s[i]);
	alls.push_back(0);
	sort(alls.begin(),alls.end());
	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	for(int i=0;i<=n;i++) s[i] = find(s[i]); 
	int m = alls.size();
	for(int i=1;i<=m;i++) tr[i] = -0x3f3f3f3f;
	for(int i=1;i<=n;i++){
		add(s[i-1],f[i-1]-(i-1));
		f[i] = f[i-1];
		if(a[i] > 0) f[i]++;
		if(a[i] < 0) f[i]--;
		f[i] = max(f[i],i+query(s[i]-1));
	}
	printf("%lld\n",f[n]);
}
signed main(){
	int _;
	for(cin>>_;_;_--) solve();
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇智波一打七~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值