Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020 - Final)
A. Kids Seating
大意:
输入n,需要输出n个\(1-4*n\)之间的数,满足任意两个数不互素且不互相整除
思路:
直接输出\(2*n+2,2*n+4.....4*n\)即可
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int main(){
int i, n, t;
cin >> t;
while(t--){
cin >> n;
for (i = 0; i < n;i++){
cout << 4*n - i * 2 << ' ';
}
cout << endl;
}
return 0;
}
B. Saving the City
大意:
输入一个01串,需要将所有的1转化为0,每次都可以将一整个1的联通块转化为0,每一次转化消耗a,可以将0转化为1,消耗b,问将所有1都转化为0的最小花费
思路:
贪心,依次判断是将两个联通块之间的0转化为1再一次性消掉花费少还是分别消掉花费少
#include<bits/stdc++.h>
using namespace std;
long long t,a,b,sum,cnt,l,i,r,flag1;
int main(){
cin>>t;
while(t--){
cin>>a>>b;
string s;
cin>>s;
flag1=0;
cnt=0;
sum=0;
for(i=0;i<s.size();i++){
if(s[i]=='1'&&flag1==0){
flag1=1;
}
else if(s[i]=='0'&&flag1==1){
if(s[i-1]=='1'){
cnt=0;
}
cnt++;
}
else if(s[i]=='1'&&flag1==1&&s[i-1]=='0'){
if(cnt*b<=a){
sum+=cnt*b;
}
else{
sum+=a;
}
}
}
if(flag1){
sum+=a;
}
cout<<sum<<endl;
}
}
C. The Delivery Dilemma
大意:
一共n个不同的菜,点外卖,花费 \(a_i\)时间,自己拿,花费\(b_i\)时间。可以同时点很多外卖,但是自己拿一次只能拿一个,问最少时间是多少
思路:
按照外卖的时间排序,对于\(i\)个菜,所有外卖时间比他小的都可以采用点外卖的方式,所以按照外卖时间从大到小遍历,尽量使得取外卖的时间和自己拿的时间接近,最后判断一下即可,具体看代码:
#include<bits/stdc++.h>
using namespace std;
int t, n;
const int N = 2e5 + 5;
long long a[N], b[N];
vector<pair<long long, long long> >v;
int main(){
cin >> t;
while(t--){
v.clear();
cin >> n;
for(int i = 0; i < n; i++){
cin >> a[i];
}
for(int i = 0; i < n; i++){
cin >> b[i];
v.push_back({a[i], b[i]});
}
sort(v.begin(), v.end());
long long spd = 0;
for(int i = v.size() - 1; i >= 0; i--){
pair<long long, long long> x = v[i];
if(spd + x.second < x.first){
spd += x.second;
}
else{
spd = min(max(spd, x.first), max(spd + x.second, v[i - 1].first));
break;
}
}
cout << spd << endl;
}
}
D. Extreme Subtraction
大意:
给定一个数组,每次可以将前任意个数减一或者后任意个数减一,问经过任意次操作,能否将数组修改为0
思路
将前任意个数减一的操作,可以将整个数组由不上升序列变为0,所以我们可以先尝试利用将后任意个数减一的操作,将整个数组化为上升的序列,如果可以转化为不上升的序列,那么就可以将整个数组修改为0
那么怎么才能转化为不上升的序列呢?只需要从后往前扫,遇到\(a[i-1]<a[i]\)的情况,就将后面所有的数都减去\((a[i]-a[i-1])\),如果扫一遍之后,最后一个数仍然大于0,那么肯定可以转化为不上升序列(因为最后一个数是最小的)
#include<bits/stdc++.h>
using namespace std;
int i,t,n,a[30005],maxn,flag;
int main(){
cin>>t;
while(t--){
cin>>n;
for(i=0;i<n;i++){
cin>>a[i];
}
maxn=a[n-1];
flag=1;
for(i=n-1;i>=1;i--){
if(a[i]-a[i-1]>0){
if(maxn-(a[i]-a[i-1])>=0){
maxn-=(a[i]-a[i-1]);
}
else{
cout<<"NO"<<endl;
flag=0;
break;
}
}
}
if(flag){
cout<<"YES"<<endl;
}
}
}
E. Long Permutation
大意:
给出一个数组,有两个操作,分别是求这个序列以后的第k个全排列,以及求区间x到y的和
思路:
仔细算一下可以发现,因为k个数的全排列有\(k!\)个,所以数据范围导致只会在后14个数进行全排列,所以利用逆康托展开,求后面14个数的全排列即可,求区间和可以用前缀和维护,而每次求全排列的时候暴力更新后14个前缀和即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, q,a[N],c[100],m,offset;
typedef long long LL;
LL sum[N], fact[100];
int lowbit(int x) {
return x & (-x);
}
// 单点修改
void add(int x, int y) {
for (int i = x; i <= m; i += lowbit(i)) c[i] += y; // 不断往父节点跳
}
int kth(int k) {
int res = 0; // res记录小于k的最后一个位置
for(int i = m >> 1; i; i >>= 1) // 以2进制逼近
if(c[res + i] < k) {
res += i;
k -= c[res];
}
return res + 1; // 因为返回的是前一个位置
}
// 逆康托展开: 2 -> [1, 4, 2, 3]
vector<int> decode(LL x) {
m = 1;
while(m <= min(14, n)) m <<= 1; // m为大于n的第一个2的幂次方,这是利用了树状数组c数组处于2的幂次方时,c[i]相当于query(i)的性质,以此来优化二分
memset(c, 0, sizeof c);
vector<int> res; // res存储答案序列
for(int i = 1; i <= min(14, n); ++i) add(i, 1); // 1表示没有使用,0表示使用过
for(int i = min(14, n) - 1; i >= 0; --i) { // 按照逆康托展开的公式处理
int t = kth(x / fact[i] + 1); // 类似二分的思想
res.push_back(t);
add(t, -1);
x %= fact[i];
}
return res;
}
int main(){
cin >> n >> q;
fact[0] = 1;
for (int i = 1; i <= n;i++){
a[i] = i;
sum[i] = sum[i - 1] + a[i];
if(i<=14)
fact[i] = fact[i - 1] * i;
}
offset = 0;
if(n>14){
offset = n - 14;
}
long long k=0;
while(q--){
int op;
cin >> op;
if(op==1){
int l, r;
cin >> l >> r;
cout << sum[r] - sum[l - 1] << endl;
}
else{
long long x;
cin>>x;
k += x;
vector<int> res = decode(k);
for (int i = max(1, n - 13); i <= n;i++){
sum[i] = sum[i - 1] + res[i - 1 - offset] + offset;
}
}
}
return 0;
}
F. Identify the Operations
大意:
给你两个数组a和b,两个数组中每个数组数字都不相同,现需要将a数组每次删除一个字符,放入到一个新数组中,最后使得b数组==c数组,问你有多少种删法。删除操作是你选定要删除的数的旁边的一个数,然后就可以删除你想要的删除的数。
思路:
直接扫一遍b数组,如果b数组中的数在a数组中都可以被删,则\(sum*2\),如果只有一个则\(sum*1\),否则sum=0,然后这个在b数组中的数就可以标记为可以被删了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int t,sum,cnt,i,vis[N],pos[N],n,m,a[N],b[N],mod=998244353;
int main(){
cin >> t;
while(t--){
cin >> n >> m;
for (i = 1; i <= n;i++){
vis[i] = 0;
cin >> a[i];
pos[a[i]] = i; // pos记录数字在a中的位置
}
for (i = 1; i <= m;i++){
cin >> b[i];
vis[b[i]] = 1;
}
sum = 1;
for (i = 1; i <= m;i++){
cnt = 0;
if (pos[b[i]] -1 >= 1&&vis[a[pos[b[i]] -1]]==0){
cnt++;
}
if (pos[b[i]] +1 <= n&&vis[a[pos[b[i]] +1]]==0){
cnt++;
}
sum = (sum * cnt) % mod;
vis[b[i]] = 0;
}
cout << sum << endl;
}
return 0;
}