2021 Xinjiang Provincial Collegiate Programming Contest

Solved A B C D E F G H I J K
9/11 - Ø - Ø Ø Ø Ø Ø Ø Ø Ø

题目链接

https://codeforces.com/gym/103115

Problem B. cocktail with hearthstone

题意:

有 $2^{n+m}$ 个人,每个人初始分为 $(0,0)$。

当两个人当前分数都为 $(a,b)$ 时,代表 $a$ 胜 $b$ 负,那么这两人之间能够进行一场对局,最终一人为 $(a+1,b)$,一人为 $(a,b+1)$。

当某人拥有 $n$ 个胜场或者 $m$ 个负场时,其无法再进行比赛。

$q$ 此询问,求最终有多少人会是 $(a,b)$,保证 $a=n$ 或 $b=m$。

思路:

记 $f_{a,b}$ 为 $a$ 胜 $b$ 负的人数,$f_{a,b}$ 会为 $f_{a+1,b}$ 和 $f_{a,b+1}$ 带来 $\frac{f_{a,b}}{2}$ 的贡献。

可以将分子和分母分开考虑,分子为转移过来的状态的分子和,分母为 $2$ 的当前轮数幂次。

$$
\begin{aligned}
&(0,0)\
&(0,1)(1,0)\
&(0,2)(1,1)(2,0)\
&(0,3) (1,2) (2,1) (3,0)\
&\dots
\end{aligned}
$$

不难发现这是个杨辉三角,在上图中第 $i$ 行的第 $j$ 个元素代表的是 $\binom{i-1}{j-1}$,$f_{a,b}$ 的分子为 $f_{a-1,b}$ 和 $f_{a,b-1}$ 的分子和。

在最后计算答案时,因为 $\binom{0}{0}=1$,最终得到的是占总人数的比例,还需乘上总人数才为答案。

分情况讨论。

  • 当 $a=n,b=m$ 时,无解。
  • 当 $a=n,b\neq m$ 时,$(a,b)$ 只会从 $(a,b-1)$ 转移过来,$f_{a,b}=\frac{\frac{\binom{a+b-1}{a-1}}{2}}{2^{a+b-1}}*2^{n+m}=\binom{a+b-1}{a-1}2^{m-b}$。
  • 当 $a\neq n,b=m$ 时,$(a,b)$ 只会从 $(a-1,b)$ 转移过来,$f_{a,b}=\frac{\frac{\binom{a+b-1}{b-1}}{2}}{2^{a+b-1}}*2^{n+m}=\binom{a+b-1}{b-1}2^{n-a}$。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<LL> VLL;
typedef vector<PII> VPII;
// head
const LL MOD=1e9+7;
const int N=4e5+5;
LL fac[N],invfac[N],power[N];
LL qpow(LL a,LL b) {
LL ret=1;
while(b) {
if(b&1) ret=ret*a%MOD;
a=a*a%MOD;
b>>=1;
}
return ret;
}
void init(int n) {
fac[0]=power[0]=1;
for(int i=1;i<=n;i++) {
fac[i]=fac[i-1]*i%MOD;
power[i]=power[i-1]*2%MOD;
}
invfac[n]=qpow(fac[n],MOD-2);
for(int i=n-1;~i;i--) invfac[i]=invfac[i+1]*(i+1)%MOD;
}
LL binom(int n,int m) {
return fac[n]*invfac[m]%MOD*invfac[n-m]%MOD;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
init(4e5);
int n,m,q;cin>>n>>m>>q;
while(q--) {
int a,b;cin>>a>>b;
if(a==n&&b==m) cout<<0<<'\n';
else if(a==n) cout<<binom(a+b-1,a-1)*power[m-b]%MOD<<'\n';
else cout<<binom(a+b-1,b-1)*power[n-a]%MOD<<'\n';
}
return 0;
}

Problem D. cocktail with swap

题意:

长度为 $n$ 的字符串 $s$,每次操作可以选择 $i,j$ 满足 $l\le|i-j|\le r$,交换 $s_i,s_j$。

可以进行无限次操作,求能得到的字典序最小字符串。

思路:

对于 $x$,当集合中存在一个下标可以与 $x$ 交换,那么 $x$ 可以放入这个集合,这个集合一定可以通过交换从小到大排序。

  • 当 $l=r$ 时,每个集合只有两个数,$a_i$ 与 $a_{i+l}$ 可以交换。

  • 当 $l<r,n\ge 2l$ 时,$r$ 最小为 $l+1$,一开始 $1,l+1,l+2$ 属于一个集合,

    对于 $i\in[2,l]$,$i-1+l\le i+l\le i-1+r$,因此最终都属于一个集合,整个字符串从小到大排序即可。

  • 当 $l<r,n< 2l$ 时,在 $[1,l]$ 中存在无法交换的下标,对于其他位置排序即可。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<LL> VLL;
typedef vector<PII> VPII;
// head
const int N=1e5+5;
vector<char> a[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,l,r;cin>>n>>l>>r;
string s;cin>>s;
if(l==r) {
int sz=0;
for(int i=0;i<l;i++) {
for(int j=i;j<n;j+=l) {
a[i].PB(s[j]);
}
sort(ALL(a[i]));
sz=max(sz,SZ(a[i]));
}
for(int i=0;i<sz;i++) {
for(int j=0;j<l;j++) if(i<SZ(a[j])) {
cout<<a[j][i];
}
}
}
else if(n>=2*l) {
sort(ALL(s));
cout<<s<<'\n';
}
else {
a[0].PB(s[0]);
for(int i=l;i<n;i++) a[0].PB(s[i]);
for(int i=1;i<n-l;i++) a[0].PB(s[i]);
sort(ALL(a[0]));
int pos=0;
for(int i=0;i<n-l;i++) cout<<a[0][pos++];
for(int i=n-l;i<l;i++) cout<<s[i];
for(int i=l;i<n;i++) cout<<a[0][pos++];
}
return 0;
}

Problem E. is the order a rabbit ??

题意:

有 $n$ 天,每天上午和下午各有一个价格,每天最多只能买一个物品以及最多只能卖出一次物品,但数量不限。

在开始前以及结束后没有物品保留,求最大收益。

思路:

求后缀最大值,每天选择收益最大的时候买入,显然并不存在在同一天两个时间卖出。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<LL> VLL;
typedef vector<PII> VPII;
// head
const int N=1e5+5;
int a[N],b[N],mxa[N],mxb[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
LL res=0;
for(int i=n;i>=1;i--) {
mxb[i]=max(a[i+1],mxa[i+1]);
mxa[i]=max(b[i],mxb[i]);
res+=max({0,mxb[i]-b[i],mxa[i]-a[i]});
}
cout<<res<<'\n';
return 0;
}

Problem F. chino with ball

题意:

在一条水平直线上有 n 个位置不同球,每个球的速度只有可能是 $-1,0,1$,分别方向是往左,不动,往右。

两球相撞后会交换速度,求 $k$ 秒后每个球所在位置。

思路:

两球相撞后交换速度可以看成两球穿过,那么球的最终位置是确定的,考虑每个位置的球的编号。

实际上球不是真的对穿过去的,因此编号顺序并未发生改变,初始时的位置顺序就是最终的位置顺序。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<LL> VLL;
typedef vector<PII> VPII;
// head
const int N=1e5+5;
int b[N],res[N];
PII a[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,k;cin>>n>>k;
for(int i=1;i<=n;i++) {
cin>>a[i].FI;
a[i].SE=i;
int v;cin>>v;
b[i]=a[i].FI+v*k;
}
sort(a+1,a+1+n);
sort(b+1,b+1+n);
for(int i=1;i<=n;i++) res[a[i].SE]=b[i];
for(int i=1;i<=n;i++) cout<<res[i]<<" \n"[i==n];
return 0;
}

Problem G. cocktail with snake

题意:

$n*m$ 的矩阵中,从 $(1,1)$ 走到 $(n,1)$,再走到 $(n,2)$,再走到 $(1,2)$,再走到 $(1,3)$,再走到 $(n,3)$,当纵坐标超出边界时停止。

一步走一格,求 $k$ 步后与起点的曼哈顿距离。

思路:

对纵坐标奇偶讨论。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<LL> VLL;
typedef vector<PII> VPII;
// head
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;cin>>T;
while(T--) {
LL n,m,k;cin>>n>>m>>k;
LL t=k/n;
if(t&1) cout<<t+n-k%n-1<<'\n';
else cout<<t+k%n<<'\n';
}
return 0;
}

Problem H. cocktail with pony

题意:

水平数轴上有两个不重合的点 $a,b$,$a,b$ 轮流移动,$a$ 每次移动 $v_1$ 步,$b$ 每次移动 $v_2$ 步,$a$ 先手。

两点移动区间为 $[1,n]$,$b$ 要抓住 $a$(在移动过程中两点重合),$a$ 要让回合数尽量多,$b$ 要让回合数尽量少,求最终经过多少轮 $b$ 抓住 $a$。

思路:

$a$ 肯定要向 $b$ 的反方向移动,在走到边界时,只能左右移动来保持和 $b$ 的距离,模拟一下就行。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<LL> VLL;
typedef vector<PII> VPII;
// head
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;cin>>T;
while(T--) {
int n,v1,v2,x1,x2;cin>>n>>v1>>v2>>x1>>x2;
if(x1<x2) {
x1=n-x1+1;
x2=n-x2+1;
}
for(int i=1;;i++) {
if(i&1) {
if(x1==2&&x2==1) {cout<<i<<'\n';break;}
if(x2>v2) x2-=v2;
else if((v2-x2)&1) x2=1;
else x2=2;
}
else {
if(x1-x2<=v1) {cout<<i<<'\n';break;}
x1-=v1;
}
}
}
return 0;
}

Problem I. chino with mates

题意:

长度为 $n$ 的数组 $a$ 与长度为 $m$ 的数组 $b$。

给出 $l,r$ 求 $a_i*b_j\in[l,r]$ 的数对数量。

思路:

枚举 $a_i$,二分出左右边界。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<LL> VLL;
typedef vector<PII> VPII;
// head
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;cin>>n>>m;
VI a(n),b(m);
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<m;i++) cin>>b[i];
int l,r;cin>>l>>r;
sort(ALL(b));
LL res=0;
for(int i=0;i<n;i++) {
if(a[i]==0) {
if(l<=0&&r>=0) res+=m;
}
else if(a[i]>0) {
int lpos=lower_bound(ALL(b),l<0?l/a[i]:int(ceil(1.0*l/a[i])))-b.begin();
int rpos=upper_bound(ALL(b),r<0?int(floor(1.0*r/a[i])):r/a[i])-b.begin()-1;
res+=rpos-lpos+1;
}
else {
int lpos=lower_bound(ALL(b),r<0?int(ceil(1.0*r/a[i])):r/a[i])-b.begin();
int rpos=upper_bound(ALL(b),l<0?l/a[i]:int(floor(1.0*l/a[i])))-b.begin()-1;
res+=rpos-lpos+1;
}
}
cout<<res<<'\n';
return 0;
}

Problem J. do NOT a=2b

题意:
长度 为 $n$ 的数组 $p$,删去一些数,使数组中不存在 $p_i=2p_j$,求最少删去多少数。

  • $1\le n \le 1e5,1\le p_i\le 1e6$

思路:

值域可分为好多条链,对值域按照删或不删 DP。

对于 $[5e5,1e6]$ 内的值统计答案。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<LL> VLL;
typedef vector<PII> VPII;
// head
const int N=1e6+5;
int a[N],dp[N][2];
bool vis[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;cin>>n;
for(int i=1;i<=n;i++) {
int x;cin>>x;
++a[x];
}
for(int i=1;i<=1e6;i++) if(!vis[i]) {
dp[i][0]=0;
dp[i][1]=a[i];
vis[i]=true;
for(int j=i*2;j<=1e6;j*=2) {
if(a[j/2]) dp[j][0]=dp[j/2][1];
else dp[j][0]=min(dp[j/2][0],dp[j/2][1]);
dp[j][1]=min(dp[j/2][0],dp[j/2][1])+a[j];
vis[j]=true;
}
}
int sum=0;
for(int i=500001;i<=1e6;i++) sum+=min(dp[i][0],dp[i][1]);
cout<<sum<<'\n';
return 0;
}

Problem K. chino with c language

题意:

memcpy 拷贝时会覆盖原始数据,memmove 不会,模拟这两种操作。

思路:

模拟。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<LL> VLL;
typedef vector<PII> VPII;
// head
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;cin>>n;
string s;cin>>s;
string t1=s,t2=s;
int a,b,c;cin>>a>>b>>c;
--a,--b;
for(int i=0;i<c;i++) t1[b+i]=t1[a+i];
for(int i=0;i<c;i++) t2[b+i]=s[a+i];
cout<<t1<<'\n';
cout<<t2<<'\n';
return 0;
}