Cf #821 Div.2
文章目录
- Cf #821 Div.2
- [A. Consecutive Sum](https://codeforces.com/contest/1733/problem/A)
- [B. Rule of League](https://codeforces.com/contest/1733/problem/B)
- [C. Parity Shuffle Sorting](https://codeforces.com/contest/1733/problem/C)
- [D1. Zero-One (Easy Version)](https://codeforces.com/contest/1733/problem/D1)
- [D2. Zero-One (Hard Version)](https://codeforces.com/contest/1733/problem/D2)
- [E. Conveyor](https://codeforces.com/contest/1733/problem/E)
A. Consecutive Sum
-
题意
- 给定一个长度为n的数组以及k(1<=k<100),下标模余k相同的数可以交换
- 通过上述操作找出长度为k的连续数之和的最大值
-
题解
- 由题意得,下标mod k相同的为一个可选集合,分别找出k个集合中的最大值,相加即为答案
-
代码
改进:用二维vector来存下标余数一样的数,代码更加简洁,不需要跨k个k个的找
#include <iostream>
#include <algorithm>
using namespace std;
const int N=110;
int a[N];
void solve() {
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
long long ans=0;
for(int i=1;i<=k;i++) {
int m=0;
for(int j=i;j<=n;j+=k) m=max(m,a[j]);
ans+=m;
}
cout<<ans<<'\n';
}
int main() {
int t;
cin>>t;
while(t--) solve();
return 0;
}
B. Rule of League
-
题意
- 有n个人比赛,每一场两个人比,输了的直接输掉,赢得继续打下一个人,第一场1vs2,第二场第一场赢的人vs 3,以此类推总共打n-1场
- 现在给n,x,y,所有人赢的次数不是x就是y,输出n-1场赢的人的编号
-
题解
- 由题得,只要一开始就输的人直接输了,即没有赢的机会,所以x,y中肯定有一个为0,而另一个肯定是n-1的因数,假设x=0,因为赢过的人都要赢y次,总共有n-1场,所以y一定是n-1的因数
- 所以可以得到结论,只有一个为0另一个为n-1的因数才有解。有解的情况,有n-1/y个人赢过y次,且可以直接输出对应场的其中一个人
-
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e5+10;
void solve() {
int n,x,y;
cin>>n>>x>>y;
bool ok=false;
if(x==0&&y&&((n-1)%y==0)) ok=1;
if(y==0&&x&&((n-1)%x==0)) ok=1;
if(!ok) {cout<<-1<<'\n';return ;}
int t=(x==0 ? y:x);
for(int i=1;i<=n-1;i++) {
int j=0;int f=0;
while(j<t) { cout<<i+1<<' '; j++;f=1;}
if(f) j--;
i+=j;
}
puts("");
}
int main() {
int t;
cin>>t;
while(t--) solve();
return 0;
}
C. Parity Shuffle Sorting
-
题意
- 给定一个n的数组,通过下列操作使得整个数组变成不递减的
- 选择两个下标,al+ar为奇数则ar=al,al+ar为偶数则al=ar
-
题解
- 因为选择后,要么左端点变右端点,要么右端点变左端点,贪心一下,直接让数组开头和结尾都变成一样的,那么中间没个数的每一次变化都可以选择左端点或者右端点,让数组变成一个完全相等的数组
-
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e5+10;
int a[N];
void solve() {
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
if(n==1){cout<<0<<'\n';return;}//特判一个数的时候
cout<<n-1<<'\n';//全部都变成一样的,所以要n-1次
int s=a[0]&1,e=a[n-1]&1;
if(s!=e) {//首位奇偶不同
cout<<1<<' '<<n<<'\n';
int t=a[0];//那么数组最终变成首位数
for(int i=1;i<n-1;i++) {
if((a[i]+t)&1) cout<<1<<' '<<i+1<<'\n';
else cout<<i+1<<' '<<n<<'\n';
}
}
else {//奇偶相同
cout<<1<<' '<<n<<'\n';
int t=a[n-1];//数组变为尾数
for(int i=1;i<n-1;i++) {
if((a[i]+t)&1) cout<<1<<' '<<i+1<<'\n';
else cout<<i+1<<' '<<n<<'\n';
}
}
}
int main() {
int t;
cin>>t;
while(t--) solve();
return 0;
}
D1. Zero-One (Easy Version)
-
题意
- 给两个长度为n的字符串a,b,进行以下两个操作,问将两个字符串变成一样的最小代价
- 操作:选择两个下标l,r进行01互换,若l,r相邻那么操作代价为x,否则代价为y,且x>y
-
题解
- 易得,只有当不同的位置数量是偶数才能变成一样
- 当不同位置的数量为2个时,可以采用两种操作,一是直接用代价为x的操作,另一种是借用另一个数把相邻的变成不相邻的,使用两次代价为y的操作。取其中代价小的
- 当不同数量>2,那么都可以直接用y代价的操作改变
-
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define int long long
void solve() {
int n,x,y;
cin>>n>>x>>y;
string a,b;
cin>>a>>b;
int cnt=0,id,f=0;
for(int i=0;i<n;i++) {
if(a[i]!=b[i]) {
cnt++;
if(!f) f=1,id=i;//记录第一个不同的位置,如果不同数量为2,那么id起作用
}
}
if(cnt&1) { puts("-1"); return;}
if(cnt==2&&a[id+1]!=b[id+1]) {cout<<min(x,2*y)<<'\n'; return;}
cout<<cnt/2*y<<'\n';
}
signed main() {
int t;
cin>>t;
while(t--) solve();
return 0;
}
D2. Zero-One (Hard Version)
-
题意
- 给两个长度为n的字符串a,b,进行以下两个操作,问将两个字符串变成一样的最小代价
- 操作:选择两个下标l,r进行01互换,若l,r相邻那么操作代价为x,否则代价为y
-
题解
-
由上一题提示,可以把问题分解成x>=y以及x<y两部分,前者用贪心,后者用dp
-
x<y,贪一下,两个分隔的可以借助中间的去用操作1,也就是说都看成使用操作1,那么距离越远的两个位置代价越大,那么最远的两个大概可以使用代价y的操作2
-
dp定义:f[i,j]表示对于区间段i~j的最小代价
转移:对于某个段,可以选择头两个,尾部两个,或者一头一尾各一个
-
-
代码
#include <iostream>
using namespace std;
const int N=5005;
int n,T,tot,pos[N];//pos记录不同的位置下标
string a,b;
long long x,y,f[N][N];
long long dfs(int l,int r) {//记忆化搜索的dp
if(l>r) return 0;
if(~f[l][r]) return f[l][r];
long long ans;
ans=min(dfs(l,r-2)+min(y,x*(pos[r]-pos[r-1])) , dfs(l+2,r)+min(y,x*(pos[l+1]-pos[l])));
ans=min(ans,dfs(l+1,r-1)+y);
return f[l][r]=ans;
}
int main() {
cin>>T;
while(T--) {
cin>>n>>x>>y;
cin>>a>>b;
tot=0;
for(int i=0;i<n;i++) {
if(a[i]!=b[i])
pos[++tot]=i+1;
}
if(tot&1) { puts("-1"); continue; }
if(x>=y) {
if(tot==2&&(pos[1]+1==pos[2])) { cout<<min(x,2*y)<<'\n'; continue; }
cout<<1ll*tot/2*y<<'\n';
}
else {
for(int i=1;i<=tot;i++)
for(int j=1;j<=tot;j++)
f[i][j]=-1;
cout<<dfs(1,tot)<<'\n';
}
}
return 0;
}
E. Conveyor
-
题意
- 有120*120的矩阵,矩阵中每个格子都有一个初始为向右的传送带,每一秒都会在(0,0)上放一个箱子
- 传送带当传送了一个箱子,其方向会改变,改变规则为向右->向下,向下->向右
- 问t秒时,在(x,y)上是否有箱子
-
题解
- 如果直接模拟t秒,看(x,y)上是否有箱子,太困难了。转变思路看t秒内,有几个箱子经过了(x,y)点,那么get(t,x,y)-get(t-1,x,y)就是t秒时经过(x,y)的箱子数量
- 如何计算t秒内经过某点的箱子数量,首先t秒内可能走到(x,y)的数量是t-(x+y)+1个,(x,y)的数量会向右或者下传送,a[x+1,y]数量为a[x,y]/2,而a[x,y+1]数量为a[x,y]-a[x,y]/2(上取整)
-
代码
#include <iostream>
#include <cstring>
using namespace std;
long long a[121][121];
long long get(long long t,long long x,long long y) {//算出t秒内经过(x,y)的箱子数量
memset(a,0,sizeof a);
a[0][0]=t-x-y+1;
if(a[0][0]<0) a[0][0]=0;
for(int i=0;i<=x;i++)
for(int j=0;j<=y;j++)
a[i+1][j]+=a[i][j]/2,
a[i][j+1]+=a[i][j]-a[i][j]/2;
return a[x][y];
}
void solve() {
long long t,x,y;
cin>>t>>x>>y;
cout<<(get(t,x,y)-get(t-1,x,y)>0 ? "YES":"NO")<<'\n';//有箱子输出YES
}
int main() {
int q;
cin>>q;
while(q--) solve();
return 0;
}