A:Fox and Card Game | CF388C
题意
- Fox and Card Game | CF388C
有
n
n
n 堆牌,第
i
i
i 堆有
a
i
a_i
ai? 张牌,每堆牌从上往下,给出每张牌的分数 两个人轮流拿牌,
A
l
i
c
e
Alice
Alice 选一堆牌,拿走这堆牌的最上面那张牌;
B
o
b
Bob
Bob 则是选一堆,拿走这堆牌的最下面那张牌。 - 选完后,计算每个人的分数和,大的获胜。求每个人的最终分数。
∑
a
i
≤
1
0
4
\sum a_i\le 10^4
∑ai?≤104
思路
- 一开始感觉是无从下手的,选择决策有很多。
如果靠近牌顶有收益很大的,肯定是
A
l
i
c
e
Alice
Alice 能拿到的。但是她可以不一开始就去拿。当
B
o
b
Bob
Bob 拿了这堆的底的时候,她再来拿这堆的顶即可,有点像围棋的下法和思路。 - 这么一看,每堆牌就是一个独立的游戏。那么我们只考虑一堆牌。如果是偶数牌,
A
l
i
c
e
Alice
Alice 拿走前一半,
B
o
b
Bob
Bob 拿走后一半。如果是奇数,那么中间那张牌会被先手拿走。
- 如果考虑多堆奇数牌,先手拿走中间的某张牌后,先手就会变成后手,后手会拿走另一堆中间的某张牌。所以我们把奇数牌堆的中间那张牌的收益降序,然后双方轮流拿即可。
代码
- 时间复杂度:
O
(
n
log
?
n
)
O(n\log n)
O(nlogn)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
typedef long long ll;
const int MAX = 5e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
vector<int>V;
int main()
{
int n;cin >> n;
ll sumA = 0,sum = 0;
ll mx = 0;
for(int i = 1;i <= n;++i){
int s;cin >> s;
for(int j = 1;j <= s;++j){
ll t;cin >> t;
if(s & 1 && j == (s + 1) / 2)V.push_back(t);
else if(j <= s / 2)sumA += t;
sum += t;
}
}
sort(V.begin(),V.end(),greater<ll>());
int sz = V.size();
for(int i = 0;i < sz;++i){
if(i%2==0)sumA += V[i];
}
cout << sumA + mx << " " << sum - (sumA + mx);
return 0;
}
B:Berzerk | CF786A
题意
- Berzerk
有
n
n
n 个球,编号
1
~
n
1\sim n
1~n,环形排成一圈,第
n
n
n 个球是黑洞。
A
l
i
c
e
Alice
Alice 有一个前进集合
A
A
A,
B
o
b
Bob
Bob 有一个前进集合
B
B
B 对于每一个开始位置
p
p
p,双方轮流选择他集合中的一个数,相当于他顺时针走几步。如果走到黑洞,他就赢了。 - 求对于每一个
p
p
p,对于每个人先手的情况,是必胜还是必输,或者是平局。
2
≤
n
≤
7000
2\le n\le 7000
2≤n≤7000
思路
- 会跑循环,貌似挺麻烦的。考虑到多起点单终点,我们倒着跑是一个不错的选择。
我们可以定义
d
p
[
i
]
[
0
/
1
]
dp[i][0/1]
dp[i][0/1] 表示在这个位置,谁先手,的必胜态怎么样。 如果下一步,都走到对方的必胜态,自己就是必败态。如果下一步,有一步走到对方的必败态,自己就是必胜态。 否则,就是平局。 - 感觉写起来挺麻烦的。倒着走,都走到必胜态,我们可以用一个
c
n
t
cnt
cnt 数组去做。
平局,我们可以使用一个
v
i
s
vis
vis 数组去做。
代码
- 时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
typedef long long ll;
const int MAX = 7e3+50;
const int MOD = 1e9+7;
int n;
vector<int>V[2];
bool vis[MAX][2];
int mex[MAX][2];
int cnt[MAX][2];
void dfs(int x,int who){
if(vis[x][who])return;
vis[x][who] = 1;
int opp = who ^ 1;
for(auto it : V[opp]){
int xp = (x - it + n - 1) % n + 1;
if(xp == 1)continue;
if(!mex[x][who]){
mex[xp][opp] = 1;
dfs(xp,opp);
}else if(++cnt[xp][opp] == V[opp].size()){
mex[xp][opp] = 0;
dfs(xp,opp);
}
}
}
int main()
{
cin >> n;
int s;
cin >> s;
while(s--){
int t;cin >> t;
V[0].push_back(t);
}
cin >> s;
while(s--){
int t;cin >> t;
V[1].push_back(t);
}
dfs(1,0);dfs(1,1);
for(int i = 2;i <= n;++i)
cout << (vis[i][0] ? mex[i][0] ? "Win" : "Lose" : "Loop") << " ";
puts("");
for(int i = 2;i <= n;++i)
cout << (vis[i][1] ? mex[i][1] ? "Win" : "Lose" : "Loop") << " ";
return 0;
}
C:Ithea Plays With Chtholly | CF896B
题意
- Ithea Plays With Chtholly | CF896B
有
n
n
n 张纸,一开始没有都是空。 有
m
m
m 轮,每一次给定一个
[
1
,
c
]
[1,c]
[1,c] 的数字。 给你数字之后,你需要选择把它写在那一张纸上,若纸上之前写过数字,则覆盖。 你需要摆完一个数字,他才给你下一个数字。 问你怎么摆,让最后
n
n
n 张纸上的数字非单调递减。 -
n
,
m
≥
2
n,m\ge 2
n,m≥2
1
≤
c
≤
1000
1\le c\le 1000
1≤c≤1000
1
≤
n
?
?
c
2
?
≤
m
≤
1000
1\le n \cdot \lceil \frac{c}{2}\rceil \le m\le 1000
1≤n??2c??≤m≤1000
思路
- 非常神奇的交互题。也是一开始感觉摆数字让最后非单调递减,策略比较复杂的感觉。
但是仔细想想,假设它最开始给了你个
1
1
1,你摆在最左边是毫无疑问的。 假设最开始给了你个
2
2
2,你摆在最左边好像也可以,但如果再给了你个
1
1
1,你摆成
[
2
,
1
]
[2,1]
[2,1] 就很劣,不如把
2
2
2 替换成
1
1
1 - 经过上述非常离散的思考,我们感觉一直维护一个非单调递减的序列就可以了,做法有点类似求最长上升子序列的过程。
- 但是这样
W
A
WA
WA了。再次仔细考虑,我们的次数超过了
m
m
m 轮了。假设它给你
[
1
,
c
]
[1,c]
[1,c] 范围内的数字,给你
c
c
c 的话,你放在最右位置是毫无疑问的。
于是,凭感觉,我们把值分成小值和大值,序列从左往右维护一个小值的非递减序列,从右往左维护一个大值的非递增序列。这样,次数就够了。 - 严格分析轮数的话,考虑最劣情况的维护非递增序列的次数,即给定序列为
[
x
,
x
?
1
,
x
?
2
,
?
?
,
1
,
x
,
x
?
1
,
?
?
]
[x,x-1,x-2,\cdots,1,x,x-1,\cdots]
[x,x?1,x?2,?,1,x,x?1,?],即可以分析得到轮数的最劣情况就是
n
?
?
c
2
?
n \cdot \lceil \frac{c}{2}\rceil
n??2c??
代码
- 时间复杂度:
O
(
n
log
?
n
)
O(n\log n)
O(nlogn)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
typedef long long ll;
const int MAX = 1e3+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
int arr[MAX];
int arr_iv[MAX];
int main()
{
int n,m,c;cin >> n >> m >> c;
fill(arr+1,arr+n+1,INF);
fill(arr_iv+1,arr_iv+n+1,INF);
while(1){
int t;cin >> t;
if(t <= c / 2){
int p = upper_bound(arr+1,arr+1+n,t) - arr;
cout << p << endl;
arr[p] = t;
arr_iv[n - p + 1] = -t;
fflush(stdout);
}else{
int p = upper_bound(arr_iv+1,arr_iv+1+n,-t) - arr_iv;
cout << n - p + 1 << endl;
arr[n - p + 1] = t;
arr_iv[p] = -t;
fflush(stdout);
}
bool can = true;
for(int i = 1;i <= n;++i){
if(arr[i] == INF){
can = false;
break;
}
if(arr[i] < arr[i-1]){
can = false;
break;
}
}
if(can)break;
}
return 0;
}
D:Lieges of Legendre | CF603C
题意
- Lieges of Legendre | CF603C
n
n
n 堆石头,每堆有
a
i
a_i
ai? 个石头。 两个人轮流拿石头。每次可以: (1) 选择一堆,拿走其中一个石头 (2)选择一堆,这堆的石头个数为
2
x
2x
2x,把它用
k
k
k 堆石头,每堆
x
x
x 个石头替换 谁不能拿就输了。求先手是否必胜。 -
1
≤
n
≤
1
0
5
1\le n\le 10^5
1≤n≤105
1
≤
a
i
,
k
≤
1
0
9
1\le a_i,k\le 10^9
1≤ai?,k≤109
思路
- 石头,考虑
S
G
SG
SG 值。
设
f
(
x
)
f(x)
f(x) 表示
x
x
x 个石头的
S
G
SG
SG 值。 一个可以转移到
f
(
x
?
1
)
f(x-1)
f(x?1) 若
x
x
x 是偶数,可以变成
k
k
k 个
f
(
x
/
2
)
f(x/2)
f(x/2) 的异或值。 - 容易想到按照
k
k
k 的奇偶性分类讨论。
- 如果
k
k
k 是偶数,我们能算出来
f
[
0
,
1
,
2
]
=
{
0
,
1
,
2
}
f[0,1,2]=\{0,1,2\}
f[0,1,2]={0,1,2}
如果
x
x
x 是奇数,那么可以得到
f
(
x
)
=
m
e
x
{
f
(
x
?
1
)
}
f(x)=mex\{f(x-1)\}
f(x)=mex{f(x?1)} 如果
x
x
x 是偶数,那么可以得到
f
(
x
)
=
m
e
x
{
f
(
x
?
1
)
,
0
}
f(x)=mex\{f(x-1),0\}
f(x)=mex{f(x?1),0},因为
k
k
k 个
f
(
x
/
2
)
f(x/2)
f(x/2) 的异或值为
0
0
0 我们可以直接得到
f
(
x
)
=
0
f(x)=0
f(x)=0,如果
x
x
x 是大于
2
2
2 的奇数;
f
(
x
)
=
1
f(x)=1
f(x)=1,如果
x
x
x 是大于
2
2
2 的偶数。 - 如果
k
k
k 是奇数,我们能算出来
f
[
0
,
1
,
2
,
3
,
4
]
=
{
0
,
1
,
0
,
1
,
2
}
f[0,1,2,3,4]=\{0,1,0,1,2\}
f[0,1,2,3,4]={0,1,0,1,2}
如果
x
x
x 是奇数,仍然
f
(
x
)
=
m
e
x
{
f
(
x
?
1
)
}
f(x)=mex\{f(x-1)\}
f(x)=mex{f(x?1)} 如果
x
x
x 是偶数,那就
f
(
x
)
=
m
e
x
{
f
(
x
?
1
)
,
f
(
x
/
2
)
}
f(x)=mex\{f(x-1),f(x/2)\}
f(x)=mex{f(x?1),f(x/2)} 因为
k
k
k 个
f
(
x
/
2
)
f(x/2)
f(x/2) 的异或值为
f
(
x
/
2
)
f(x/2)
f(x/2) 我们能得到一个结论,如果
x
x
x 是大于
4
4
4 的奇数,那么
f
(
x
)
=
0
f(x)=0
f(x)=0。否则,
f
(
x
)
>
0
f(x)>0
f(x)>0,我们可以归纳法去做,假设成立,然后根据
m
e
x
{
f
(
x
?
1
)
}
mex\{f(x-1)\}
mex{f(x?1)} 可以证明。 我们接下来只要递归计算
f
(
x
)
f(x)
f(x) ,
x
x
x 是偶数的情况即可。每次都除以
2
2
2,是在
log
?
\log
log 的数量级可以求出来的。 - 当然有人说:结论怎么想得出来的?你可以打表找规律,但是记得按
k
k
k 的奇偶性去看。
代码
- 时间复杂度:
O
(
n
log
?
n
)
O(n\log n)
O(nlogn)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
typedef long long ll;
const int MAX = 1e3+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const int pre[] = {0, 1, 0, 1, 2};
int sg(int x,int k){
if(k & 1){
if(x < 5)return pre[x];
if(x & 1)return 0;
return sg(x / 2,k) == 1 ? 2 : 1;
}else{
if(x <= 2) return x == 1 ? 1 : 2;
return (x % 2) ^ 1;
}
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
int mex = 0;
for(int i = 1;i <= n;++i){
int t;scanf("%d",&t);
mex ^= sg(t,k);
}
puts(mex?"Kevin":"Nicky");
return 0;
}
E:Funny Game | CF731E
题意
- Funny Game | CF731E
有
n
n
n 个数,两个人轮流操作。 每一次,选择至少两个,把最左边的
x
x
x 个数字合并成一个新的数字,值为这
x
x
x 个数字的和
y
y
y,操作的人获得
y
y
y 分。 选完后,第一个人的分数减去第二个人的分数的差值为
C
C
C,第一个人希望让
C
C
C 最大,第二个人希望让
C
C
C 最小,你需要求出这个
C
C
C。 -
2
≤
n
≤
2
×
1
0
5
2\le n\le 2\times 10^5
2≤n≤2×105
思路
- 每次就是选择一个前缀和,设
d
p
[
i
]
[
0
/
1
]
dp[i][0/1]
dp[i][0/1] 表示已经选择好前
i
i
i 个数了,接下来由哪个人开始选。
写下状态转移方程:
d
p
[
i
]
[
0
]
=
max
?
{
d
p
[
j
]
[
1
]
+
p
r
e
[
j
]
}
dp[i][0]=\max\{dp[j][1] + pre[j]\}
dp[i][0]=max{dp[j][1]+pre[j]}
d
p
[
i
]
[
1
]
=
min
?
{
d
p
[
j
]
[
0
]
+
p
r
e
[
j
]
}
dp[i][1]=\min\{dp[j][0] + pre[j]\}
dp[i][1]=min{dp[j][0]+pre[j]} 然后考虑暴力做时间不够的,只要记录一个最大值和最小值,直接转移即可。 - 注意至少选择两个,也就是说我们的答案是
d
p
[
1
]
[
0
]
dp[1][0]
dp[1][0] 而不是
d
p
[
0
]
[
0
]
dp[0][0]
dp[0][0]
- 还有别的更简单的做法。考虑每个操作的人都是希望最大化自己的分数的,所以可以写成:
ll ans = pre[n];
for(int i = n-1;i >= 2;--i)
ans = max(ans,pre[i] - ans);
cout << ans;
也是非常的妙。
代码
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
typedef long long ll;
const int MAX = 2e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
ll pre[MAX];
ll dp[MAX][2];
int main()
{
int n;
scanf("%d",&n);
for(int i = 1;i <= n;++i){
ll t;scanf("%lld",&t);
pre[i] = pre[i-1] + t;
}
ll mx = pre[n];
ll mn = -pre[n];
for(int i = n - 1;i >= 0;--i){
dp[i][0] = mx;
dp[i][1] = mn;
if(dp[i][0] - pre[i] < mn){
mn = dp[i][0] - pre[i];
}
if(dp[i][1] + pre[i] > mx){
mx = dp[i][1] + pre[i];
}
}
printf("%lld",dp[1][0]);
return 0;
}
|