A
链接:https://codeforces.com/contest/1858/problem/A
题意:三个按钮a,b,c,alice可以摁a和c,bob可以摁b和c,每个按钮只能被摁一次,给出a,b,c的数量,谁先不能摁按钮谁就输了,alice先手,问赢家是谁。
思路:优先摁c肯定是最优的,判断即可
代码:
void solve()
{
cin >> a >> b >> c;
if((a + (c+1)/2) > b + (c/2))
cout << "First" << "\n";
else
cout << "Second" << "\n";
}
B
链接:https://codeforces.com/contest/1858/problem/B
题意:n个点,m个饼干商人,现在alice要从1走到n,她只会在以下三种情况吃饼干 1:当前位置为1,2:距离上一次吃饼干已经走了d个点,3:当前位置有饼干商人。alice可以移除任意一个饼干商人,问alice最少吃几个饼干以及移除饼干商人使alice会吃到最少饼干的方案数。
思路:枚举每一个饼干商人,考虑移除他会影响哪些状态(不会影响之前的状态,不会影响遇到下一个饼干商人之后的状态)设区间左端点l为上一次吃饼干的点,右端点r为下一个饼干商人的位置,比较在这个区间内移除前和移除后的吃饼干数,如果移除后更优,则更新答案。
代码:
void solve()
{
ll n,m,d;
cin >> n >> m >> d;
for (int i = 1;i <= m;i++)
cin >> arr[i];
arr[m+1] = n+1;
ll l = 1;
ll ans = 0;
ll sum = m;
for (int i = 1;i <= m;i++)
{
ll sum1 = (arr[i] - arr[i-1])/d;
if(i == 1)
{
sum1 = (arr[1] - 1) / d;
l = 1+sum1 * d;
}
else
l = arr[i-1] + sum1 * d;
ll r = arr[i+1];
ll tmp1 = 1 + (r-arr[i]-1)/d;
sum += tmp1 - 1;
ll tmp2 = (r-l-1)/d;
if(l == arr[i])
tmp2++;
if(tmp2 < tmp1)
ans++;
}
sum++;
sum += ((arr[1]-1) / d);
if((arr[1] - 1)%d == 0)
sum--;
if(ans != 0)
sum--;
if(ans == 0)
ans = m;
cout << sum << ' ' << ans << "\n";
}
C
链接:https://codeforces.com/contest/1858/problem/C
题意:构造一个长度为n的排列,使得gcd(
a
i
a_i
ai,
a
(
i
m
o
d
n
)
+
1
a_{(i~mod~n)+1}
a(i mod n)+1)的种类最多
思路:只要按照 i ,2i,3i…这样构造即可,注意判断当前点走没走过
void solve()
{
int n;
cin >> n;
for (int i = 1;i <= n;i++)
vis[i] = 0;
for (int i = 1;i <= n;i++)
{
if(vis[i])
continue;
int j = i;
while(j <= n)
{
cout << j << ' ';
vis[j] = 1;
j <<= 1;
}
}
cout << "\n";
}
D
链接:https://codeforces.com/contest/1858/problem/D
题意:给定一个长度为n的01字符串s,可以进行k次操作,每次操作可以选择一位翻转,定义
l
0
l_0
l0为最长连续0段,
l
1
l_1
l1为最长连续1段,对于每一个i(1 <= i <= n) 输出i*
l
0
l_0
l0 +
l
1
l_1
l1的最大值。
思路:不是很懂,看了一下dalao的题解,先考虑一个暴力的做法,我们枚举每一个区间全为0/1,在剩的区间中找到最长的连续1/0段,但这些写显然是会超时的,考虑优化,首先修改过后的
l
0
l_0
l0和
l
1
l_1
l1一定是分开的,我们可以设
l
0
l_0
l0在左
l
1
l_1
l1在右,相反的情况翻转字符串重新计算一次即可。
我们用
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示从i到n进行j次操作能获得的最大
l
1
l_1
l1,对于一个i,他的值一定是大于等于后面的值的,所以我们从后往前更新,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的初始值由
d
p
[
i
+
1
]
[
j
]
dp[i+1][j]
dp[i+1][j]获得,然后定义一个cost值,每次都计算从i到n连续1段的cost值,当cost <= k 时更新
d
p
[
i
]
[
c
o
s
t
]
dp[i][cost]
dp[i][cost] cost更新完之后当前的
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] = max(
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j],
d
p
[
i
]
[
j
−
1
]
dp[i][j-1]
dp[i][j−1])因为小花费能得到的状态大花费一定能得到。
然后考虑前缀的
l
0
l_0
l0,对于每一个点,我们向前枚举区间,用
h
[
i
]
h[i]
h[i]记录当前长度为i的区间大小全为0的最小花费,计算cost并更新
h
[
i
]
h[i]
h[i],算完之后,我们就有了从该点往前长度为j(0 <= j <= i)的0段花费,从该点往后的最长1段
d
p
[
j
]
[
k
−
h
[
j
]
]
dp[j][k - h[j]]
dp[j][k−h[j]] ,用
g
[
i
]
g[i]
g[i]表示长度为i的0段能得到的最长1段,每次算完
h
[
i
]
h[i]
h[i]更新
g
[
i
]
g[i]
g[i]最大值即可
最后对于每个a,枚举可能的0段长度i,答案为
a
∗
i
+
g
[
i
]
a * i + g[i]
a∗i+g[i],更新最大值即可
代码:
void work()
{
for (int i = 0;i <= n;i++)
{
g[i] = -1;
for (int j = 0;j <= n;j++)
dp[i][j] = 0;
}
for (int i = n-1;i >= 0;i--) // 预处理dp数组
{
for (int j = 0;j <= k;j++)
dp[i][j] = dp[i+1][j]; //继承上一个状态
int cost = 0;
for (int j = i;j < n;j++)
{
cost += (s[j] == '0');
if(cost <= k)
dp[i][cost] = max(dp[i][cost],j-i+1); //更新
}
for (int j = 1;j <= k;j++)
dp[i][j] = max(dp[i][j],dp[i][j-1]); //小花费能得到,大花费一定能得到
}
for (int i = 1;i <= n;i++)
h[i] = k+1;
h[0] = 0;
for (int i = 0;i <= n;i++)
{
int cost = 0;
for (int j = i-1;j >= 0;j--) // 区间长度为i-j+1全部是0的最小次数
{
cost += (s[j] == '1');
h[i - j] = min(h[i - j],cost);
}
for(int j = 0;j <= n;j++)
{
if(h[j] <= k)
g[j] = max(g[j],dp[i][k - h[j]]);
}
}
for (int a = 1;a <= n;a++)
{
for (int i = 0;i <= n;i++)
{
if(g[i] != -1)
ans[a] = max(ans[a],i * a + g[i]);
}
}
}
void solve()
{
cin >> n >> k;
for (int i = 1;i <= n;i++)
ans[i] = 0;
cin >> s;
work();
reverse(s.begin(),s.end());
work();
for (int i = 1;i <= n;i++)
cout << ans[i] << ' ';
cout << "\n";
}