Loading

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;
}
posted @ 2020-11-19 21:39  dyhaohaoxuexi  阅读(105)  评论(0编辑  收藏  举报