2022SCUT Winter Training Day 10

A. Array Rearrangment

解法:送分题,不解释

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<'\n';
const int N = 55;
int t, n, k, a[N], b[N];

bool cmp(int a,int b){return a>b;}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;
    while(t--){
        cin>>n>>k;
        for(int i=1; i<=n; i++) cin>>a[i];
        for(int i=1; i<=n; i++) cin>>b[i];
        sort(a+1,a+n+1); sort(b+1,b+1+n,cmp);
        bool flag = 1;
        for(int i=1; i<=n; i++)
            if(a[i]+b[i] > k) {flag=0; break;}
        puts(flag?"Yes":"No");
    }
}

B. Elimination

题意:进行两次比赛,每次比赛排名非递增顺序。
第一场第 100 名选手得 a 分,第二场每个选手最少得 b 分
第二场第 100 名选手得 b 分,第一场每个选手至少得 d 分
给你整数 a , b , c , d 问第 100 名选手至少总分多少

解法:画个集合图,分类讨论一下即可严谨证明,答案是max(a+b, c+d)

#include <bits/stdc++.h>
using namespace std;
int t,a,b,c,d;

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin>>t;
    while(t--){
        cin>>a>>b>>c>>d;
        cout<<max(a+b,c+d)<<'\n';
    }
}

C. Division 

题意:给你两个数 p,q,求出最大的数 x,使得 x | p,但不满足 q | x。

解法:分类讨论,首先如果q不被p整除,那么答案就是p。否则,p必定包含q所有因子,并且每个次数不小于q的次数。我们只需要枚举某个因子,让x的这个因子次数比q少1,其他因子次数和p一样。然后取最大值就是答案。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<'\n';
#define int long long
typedef pair<int,int> P;
int t,p,q;
vector<P> v; //q的{因子,次数}

int Pow(int a, int b){
    int res = 1, base = a; 
    while(b>0){
        if(b&1) res = res*base;
        base = base*base;
        b >>= 1;
    }
    return res;
}
void solve(){
    if(p%q) {cout<<p<<'\n'; return;} //特判
    v.clear(); //清空vector
    int tq=q; //临时q变量
    for(int i=2; i*i<=q; i++){
        if(tq%i) continue;
        int cnt = 0; //因子i的次数
        while(tq%i==0) {tq/=i; cnt++;}
        v.push_back({i,cnt}); //存放
    }
    if(tq>1) v.push_back({tq,1}); //最后一个因子

    int maxx = 0;
    for(auto P:v){
        int a=P.first, b=P.second;
        int tp=p;
        while(tp%a==0) tp/=a;
        maxx=max(maxx,tp*Pow(a,b-1));
    }
    cout<<maxx<<'\n';
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;
    while(t--){
        cin>>p>>q;
        solve();
    }
}

D. Divide and Sum 

解法:手算找规律,具体见注释。(乘法逆元部分我还不会,是队友写的)

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<'\n';
#define int long long
const int N = 1.5e5+5, mod = 998244353;
int n, a[2*N], sum;

int fac[400000], inv[400000];
int pow_mod(int n, int p, int m) {
	if (p == 0) return 1;
	int ans = pow_mod(n, p / 2, m);
	ans = ans * ans%m;
	if (p % 2 == 1)ans = ans * n%m;
	return ans;
}
void init() {
	inv[0]=fac[0] = 1;
    for(int i=1; i<=301000; i++){
		fac[i] = fac[i - 1] * i%mod;
		inv[i] = pow_mod(fac[i], mod - 2, mod);
	}
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n; for(int i=1; i<=2*n; i++) cin>>a[i];
    sort(a+1,a+2*n+1);
    for(int i=1; i<=n; i++) sum = (sum+a[i+n]-a[i])%mod;
    //所有的sum都是一样的,而组合方式有C(n,2n)种,重点是计算C(n,2n),要用到乘法逆元
    init();
    cout << sum * fac[2 * n] % mod*inv[n] % mod*inv[n] % mod;
}

 F. Marketing Scheme

解法:贪心,找规律

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<'\n';
int t, l, r;

signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;
    while(t--){
        cin>>l>>r;
        int tmp = 2*l;
        if(tmp>r) puts("YES");
        else puts("NO");
    }
}

G. Reverse Binary Strings

解法:每一次reverse,最多让两个相邻1,和两个相邻的0分开,所以可以猜到,答案是max(sum(连续1长度-1),sum(连续0长度-1))。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<'\n';
int t, n;

signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;
    while(t--){
        cin>>n;
        int sum0=0, sum1=0;
        char pre, ch;
        cin>>pre;
        for(int i=2; i<=n; i++){
            cin>>ch;
            if(ch==pre){
                if(ch=='0') sum0++;
                else sum1++;
            }
            pre=ch;
        }
        cout<<max(sum0,sum1)<<'\n';
    }
}

H. Chef Monocarp 

解法:dp,用f[i][j]表示在 j 时刻拿了前 i 个菜的最小时间差之和。

先把a[i]排序,然后递推式为f[i][j] = min(f[i-1][k] + abs(j-a[i])),其中 k 表示任意小于 j 的时刻。

需要特别注意的是,j要枚举到2*n而不是n,因为极限情况下,所有菜出炉时间都是n,第一个菜在n时刻拿出。f数组也要记得第二维开2*n大小!

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<'\n';
const int N = 205, inf=0x3f3f3f3f;
int t, n, a[N], f[N][2*N];

void init(){
    for(int i=1; i<=n; i++)
    for(int j=0; j<=2*n; j++)
        f[i][j] = inf;
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;
    while(t--){
        cin>>n; init();
        for(int i=1; i<=n; i++) cin>>a[i];
        sort(a+1,a+n+1);
        for(int i=1; i<=n; i++)
        for(int j=1; j<=2*n; j++)
        for(int k=0; k<j; k++){
            f[i][j] = min(f[i][j], f[i-1][k]+abs(j-a[i]));
        }
        int ans = inf;
        for(int i=1; i<=2*n; i++)
            ans = min(ans, f[n][i]);
        cout<<ans<<'\n';
    }
}

可是,这样复杂度是O(N^3),能过但是不够好,可以考虑把f[i][j]改为记录原本的min ( f [ i ][1~j ]) ,即前缀最小值。这样就能降到O(N^2)。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<'\n';
const int N = 205, inf=0x3f3f3f3f;
int t, n, a[N], f[N][2*N];

void init(){
    for(int i=1; i<=n; i++)
    for(int j=0; j<=2*n; j++)
        f[i][j] = inf;
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;
    while(t--){
        cin>>n; init();
        for(int i=1; i<=n; i++) cin>>a[i];
        sort(a+1,a+n+1);
        for(int i=1; i<=n; i++)
        for(int j=1; j<=2*n; j++){
            f[i][j] = min(f[i][j], f[i-1][j-1]+abs(j-a[i])); //dp递推
            f[i][j] = min(f[i][j], f[i][j-1]); //求前缀最小值
        }
        int ans = inf;
        for(int i=1; i<=2*n; i++)
            ans = min(ans, f[n][i]);
        cout<<ans<<'\n';
    }
}

I. Minimal Height Tree

解法:考虑贪心,【接同一结点 --->  接同一层 --->  接下一层 】这三项依次考虑,O(N)遍历一遍即可。代码主要是模拟整个过程。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<'\n';
const int inf = 0x3f3f3f3f;
const int N = 2e5+5;
int t, n;

inline void solve(){
    int pre=inf, x, cnt=0, nxt=1, ans=0;
    cin>>x; //root
    for(int i=2; i<=n; i++){
        cin>>x;
        if(x<=pre){ //不符合递增顺序,不能接到同一结点下面
            if(cnt==0) {cnt=nxt-1; nxt=1; ans++;} //这一层没地方接,接到下一层
            else {cnt--; nxt++;} //接到这一层一个新结点下方
        }
        else nxt++; //接到同一结点下面
        pre = x; //记录前驱
    }
    cout<<ans<<'\n';
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;
    while(t--){
        cin>>n;
        solve();
    }
}

 J.Make It Increasing

题意:给一个数组,给定几个绑定的位置,这些数字不能修改,可修改其他数字,判断是否能把数组改成递增序列,如果不能输出-1,如果能,输出最少修改次数。

解法:举个例子,1 * * 4 可以修改成成递增,但是1 * * * * * 4不行,所以直接来看的话,能不能改还和距离有关,这样比较麻烦,所以可以预处理一下,令a[i] = a[i]-i,然后题目就变成了改成非递减序列,就不用管距离了,判断能否修改变得很简单。接下来讲如何算最少修改次数,可以分多段来算,每两个绑定位置之间算一下要修改的数量,然后加起来(可在两边界加入哨兵点方便处理)。那这个要修改的数量怎么算呢?假设相邻两个绑定的数分别是a[l]和a[r]那么不在[ a[l], a[r] ]范围内的数肯定要改,排除这些数之后,在[l+1,r-1]范围内找一个LIS,这些不用改,其他要改。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<'\n';
const int N = 5e5+5, inf=0x3f3f3f3f;
int n, k, a[N], b[N];

int cal(int l, int r){
    int st[N], t=0;
    for(int i=l+1; i<=r-1; i++){ //寻找[l+1,r-1]范围内最长的不下降子序列
        if(a[i]<a[l] || a[i]>a[r]) continue; //超出范围,不予考虑
        if(t==0 || a[i]>=st[t]) st[++t] = a[i]; //满足,接到后面
        else{
            int p = upper_bound(st+1,st+t+1,a[i])-st; //寻找插入位置
            st[p] = a[i];
        }
    }
    return (r-l-1)-t; //返回不在LIS中的长度,即需要修改的长度
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>k; for(int i=1; i<=n; i++) cin>>a[i], a[i]-=i;
    for(int i=1; i<=k; i++) cin>>b[i];
    a[0]=-inf;a[n+1]=inf; b[0]=0; b[k+1]=n+1; //哨兵标记
    for(int i=1; i<=k+1; i++) //检查是否可行
        if(a[b[i-1]] > a[b[i]]) {cout<<-1; return 0;}
    int ans = 0; //可行,进入计算,答案是ans
    for(int i=1; i<=k+1; i++){
        ans += cal(b[i-1],b[i]); //计算两段之间非递增的长度
    }
    cout<<ans;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值