Codeforces Round 876 (Div. 2)
A.The Good Array
解题思路
比赛的时候不需要考虑什么最优,看到数据范围,直接枚举 i i i,取前 i i i 个需要多少 1 1 1 和后 n − i n-i n−i 个需要多少个 1 1 1 的和的最大值,也就是 ⌈ i k ⌉ + ⌈ n − i k ⌉ \left \lceil \frac{i}{k} \right \rceil+\left \lceil \frac{n-i}{k} \right \rceil ⌈ki⌉+⌈kn−i⌉,这个式子表示。
这样枚举就包括了所有情况。
AC_Code
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
#include<vector>
#pragma warning(disable : 4996)
#define int long long
#define se second
#define fi first
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int maxn=2e5+10,inf=0x3f3f3f3f;
signed main()
{
ios;
int t;
cin >> t;
while (t--)
{
int n,k;
cin >> n>>k;
int ans = 0;
for (int i = 1; i <= (n-1)/2+1; i++)
{
ans = max((i - 1) / k + 1 + (n - i - 1) / k + 1, ans);
}
cout << ans << endl;
}
}
B.Lamps
解题思路
将所有输入的数对按照第一个数为第一关键词从小到大排序, b b b 的从大到小为第二关键词,那么可以找出规律,数 a a a 为 1 1 1 的数对可以选 1 1 1 个, a a a 为 i i i 的数对可以选 i i i 个,因为这样选当选 a = i + 1 a=i+1 a=i+1 的时候, a = i a=i a=i 的灯会关闭, 4 4 4 就又变成了 0 0 0,所以只要从从右到左选择就好了。
AC_Code
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
#include<vector>
#pragma warning(disable : 4996)
#define int long long
#define se second
#define fi first
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int maxn=2e5+10,inf=0x3f3f3f3f;
struct A
{
int a, b;
};
signed main()
{
ios;
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
vector<A>a(n+2);
for (int i = 1; i <= n; i++)
{
cin >> a[i].a >> a[i].b;
}
sort(a.begin() + 1, a.end()-1, [](A x, A y)
{
if (x.a == y.a)return x.b > y.b;
else return x.a < y.a;
});
int ans = 0;
int x = 0;
vector<int>st(n + 1,0 );
int z = 0;
for (int i = 1; i <= n; i++)
{
if (a[i].a > x && z != a[i].a)x = 0;
if (a[i].a > x)ans += a[i].b,x++,z=a[i].a;
}
cout << ans << endl;
}
}
C.Insert Zero and Invert Prefix
解题思路
合法的序列最后一个字符一定不是1,因为假如是1的话,那么是不可能有一种方案让最后一个从0变成1的,先考虑一下这样造序列有什么序列是很容易造出来的,考虑能不能用简单的序列造出复杂的序列,可以想到 00000 00000 00000 是最简单的序列,不需要一次变换,一直带 0 0 0 后面插入 0 0 0 就好了,其次就是 11000 11000 11000,就是一些 1 1 1 加上一些 0 0 0 的序列,这种序列稍微复杂一点,先在0后面插入 4 4 4 个 0 0 0 然后在 2 2 2 后面插入 1 1 1 个 0 0 0,那么这样就可以造出这个序列,这两种方式我们都是可以轻松模拟出来的,并且所有合法的序列都可以用这两种序列拼接出来,所以我们目的达到了,这个过程要反着实现,才不会影响后面的序列,所以要先插入 1 1 1 序列的长度,再插入 0 0 0。注意输出的时候要反着输出。
AC_Code
#include<iostream>
#include<algorithm>
#include<vector>
#define int long long
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
signed main()
{
ios;
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
vector<int>a(n + 1);
for (int i = 1; i <= n; i++)cin >> a[i];
if (a[n])
{
cout << "NO" << endl;
continue;
}
int tt = 0, hh = -1;
int x=0;
int flag = 0;
vector<int>ans(n + 1);
int cnt = 0;
for (int i = 1; i <= n; i++)
{
if (a[i] == 1)x++;
else
{
ans[++cnt] = x;
while (x)
{
ans[++cnt] = 0;
x--;
}
}
}
cout << "YES" << endl;
for (int i = ans.size()-1; i >>= 0; i--)cout << ans[i] << ' ';
cout << endl;
}
}
D.Ball Sorting
解题思路
题意就是有两种操作,一种是将0插入到序列中,没有代价,另一种是将一个与0相邻的元素插入到任意位置代价为1,题目问,小于等于k次操作一前提下,求最小的代价,$1\le k\le n $。
这里有一些元素是不需要动的,也就是不需要花费代价移动,并且很容易发现,这些元素是严格单调递增的。
然后这些元素会将这个序列分割成若干个区间,这些区间的元素都是需要移动的,由于不动的元素的存在,所以每一段就至少需要一个k,那么这个区间里的所有元素才可以移动。
所以这个问题就转化为了,在这个序列中,找到一个上升子序列,也就是不移动的元素组成的序列,这个子序列会将这个序列划分为不超过 k k k 段,求这个满足条件的最长的子序列的长度。
考虑使用动态规划算法,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示序列中前 i i i 个元素以 i i i 结尾并且划分为 j j j 段的最小代价,这里类似于求最长上升子序列,不过这里需要枚举一下段数,
状态状态转移方程, d p [ i ] [ j ] = m i n { a [ i − 1 ] < a [ i ] ? d p [ i − 1 ] [ j ] : ∞ m i n k = 1 n ( a [ k ] < a [ i ] ? d p [ k ] [ j − 1 ] + ( i − k − 1 ) : ∞ ) dp[i][j]=min\left\{\begin{matrix} a[i-1]<a[i]? dp[i-1][j]:\infty \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\ min_{k=1}^n(a[k]<a[i]?dp[k][j-1]+(i-k-1):\infty )\\ \end{matrix}\right. dp[i][j]=min{a[i−1]<a[i]?dp[i−1][j]:∞ mink=1n(a[k]<a[i]?dp[k][j−1]+(i−k−1):∞)
AC_Code
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
vector<int>a(n+2,0);
a[n+1]=0x3f3f3f3f;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector dp(n + 2, vector<int>(n + 1,0x3f3f3f3f));
dp[0].assign(n+2,0);
for(int i=1;i<=n+1;i++)
{
for(int j=0;j<=n;j++)
{
if(a[i]>a[i-1])dp[i][j]=dp[i-1][j];
if(j)
for(int x=0;x<i;x++)
{
if(a[x]<a[i])dp[i][j]=min(dp[i][j],dp[x][j-1]+i-x-1);
}
}
}
for(int i=1;i<=n;i++)cout<<dp[n+1][i]<<' ';
cout<<endl;
}
return 0;
}
E.Decreasing Game
解题思路
题意:给定一个正整数数组a长度至多 300 300 300,元素值至多 300 300 300。
甲乙二人玩游戏,每次从a中各选一个正数 a [ i ] , a [ j ] , i ≠ j a[i],a[j],i\ne j a[i],a[j],i=j,甲先选。设d=min(a[i],a[j]),把a[i]和a[j]都减少d。如果有人无法操作,游戏结束。无法操作的人输掉游戏,另一个人获胜。
首先确定谁必赢。然后你扮演必胜的人,和评测机玩这个游戏。
通过分析发现,假设 s s s 为 a a a 数组所有元素的和,那么如果 a a a 可以被分成和相同的两份,那么就后手必赢。现在证明一下,假如这组数被分成了两份,那么先手选择了一个数,后手就可以选择另一个数,最后就只有全部 0 0 0,然后只要这样后手就必胜。然后分成相同的两份可以使用01背包。
AC_Code
#include<bits/stdc++.h>
using namespace std;
int n,a[999],f[1<<20],s,x,y,v[999],d;
int main(){
cin>>n,f[0]=1;
for(int i=1;i<=n;i++)
cin>>a[i],s+=a[i];
for(int i=1;i<=n;i++)
for(int j=s;j>=a[i];j--)
if(!f[j]&&f[j-a[i]])
f[j]=i;
if(s&1||!f[s/2]){
cout<<"First"<<endl;
while(1){
x=1;
while(!a[x])
x++;
cout<<x<<endl,cin>>y;
if(!y)
return 0;
d=min(a[x],a[y]),a[x]-=d,a[y]-=d;
}
}
cout<<"Second"<<endl;
x=s/2;
while(x)
v[f[x]]=1,x-=a[f[x]];
while(1){
cin>>y,x=1;
if(!y)
return 0;
while(!a[x]||v[x]==v[y])
x++;
cout<<x<<endl,d=min(a[x],a[y]),a[x]-=d,a[y]-=d;
}
}