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;
}