A. Median of an Array

排序

大概思路

直接排序,然后从下标 $\frac{1+n}{2}$ 处一直往后查到第一个不等于 $a_{\frac{1+n}{2}}$ 的数即可

参考代码

void solve(){
    CIN(n);
    vector<ll>a;a.push_back(-1);
    FOR(i,1,n){
        CIN(tmp);a.push_back(tmp);
    }
    sort(a.begin(),a.end());

    ll ans=0,ck=a[(1+n)/2];
    FOR(i,(1+n)/2,n){
        if(a[i]!=ck)break;
        ans++;
    }
    cout<<ans<<ET;
}

B. Maximum Sum

区间最大子串和

大概思路

查一遍区间最大子串和,然后乘上 $2^k-1$ ,再加上总和即可,注意取模

参考代码

ll powp(ll x,ll n){
    ll ans=1;
    while(n){
        if(n&1){ans*=x;ans%=M;}
        x*=x;x%=M;n>>=1;
    }
    return ans;
}

inline ll MO(ll x){return (x%M+M)%M;}

void solve(){
    CIN(n);CIN(k);
    vector<ll> pre(n+1,0);
    FOR(i,1,n){CIN(tmp);pre[i]=pre[i-1]+tmp;}

    ll mx=0;ans=-1e18;
    FOR(i,1,n){
        mx=min(mx,pre[i-1]);
        ans=max(ans,pre[i]-mx);
    }

    if(ans<=0){
        cout<<MO(pre[n])<<ET;
    }else{
        cout<<MO(MO(pre[n]-ans)+MO(MO(ans)*MO(powp(2,k))))<<ET;
    }
}

C. Tree Cutting

DFS,二分,贪心

大概思路

最小值的最大值,一眼二分。每次二分跑一遍DFS统计最大可删边次数即可。

参考代码

const int MAXN=1e5+5;
int n,k;
vector<int> edg[MAXN];
int siz[MAXN],cnt;
//图论,树论(各种意义上的树)不熟导致的
void dfs(int u,int From,int x){
    siz[u]=1;
    for(auto v:edg[u])if(v!=From){//无向无环图(树),只需保证下一条边不往回走就可以不发生死循环
        dfs(v,u,x);//DFS收集子树大小
        siz[u]+=siz[v];
    }
    if(siz[u]>=x){//子树满足条件,siz置0(剪掉)
        cnt++;
        siz[u]=0;
    }
}
int ck(int x){
    cnt=0;
    dfs(1,0,x);//任取一节点开始DFS,统计最大删边次数(贪心)
    return cnt-1;//DFS的贪心删法可能会剩一个小于x大小的联通快,所以要撤回一次删除
}

void solve(){
    cin>>n>>k;
    FOR(i,1,n)edg[i].clear();
    FOR(i,1,n-1){
        CIN(u);CIN(v);
        edg[u].push_back(v);
        edg[v].push_back(u);
    }
    int l=1,r=n+1;
    while(l<=r){
        int mid=l+r>>1;
        if(ck(mid)>=k)l=mid+1;
        else r=mid-1;
    }
    cout<<l-1<<ET;
}

D. Birthday Gift

前缀异或和,拆位,数位DP

大概思路

题意大概是问你把一段序列拆成多段,各段的异或和,或在一起小于等于某个数,最多能拆成多少段。

首先前缀异或和一下,得到 $pre_i$,每段的异或和此时可以表示为 $pre_{r_i}\otimes pre_{{l_i}-1}$,也就是 $pre_{r_i}\otimes pre_{r_{i-1}}$,原问题可转化为,取一段序列 $0=r_0<r_1<r_2<\dots<r_k=n$ 使得任意 $i\in[1,n]$ 都有 $pre_{r_i}\otimes pre_{r_{i-1}}\le x$,最多能选多少段这样的序列。

这里我们可以按位从前往后枚举,每次取消一个x的有效位,换取后面的所有位都能随意取的收益,遍历收集最大答案即可。

参考代码

void solve(){
    CIN(n);CIN(x);x++;
    vector<ll> pre(n+1);
    FOR(i,1,n){cin>>pre[i];pre[i]^=pre[i-1];}
    ll ans=-1;
    ll HEAD=0;
    rFOR(i,30,0){
        HEAD|=(1LL<<i);//获得“取反匹配串”,当一个数与该“取反匹配串”相与不为0时,说明我们不想让这个数出现的一些数位为1,那么这个数就“不合法”
        if((x>>i)&1){
            //当然这题你直接在这里用ll HEAD=((((~x)>>i)^1)<<i)也是可行的,用HEAD不断记录前到后只是数位DP的一种思想体现
            int cnt=0;
            if((HEAD&pre[n])!=0){HEAD^=(1LL<<i);continue;}//如果全异或不匹配,说明没法完成匹配,因为这说明非法数位的数量是奇数,没法通过适当分组消掉所有的非法数位
            FOR(i,1,n)if((HEAD&pre[i])==0)cnt++;//如果可行就记录答案
            ans=max(ans,cnt);
            HEAD^=(1LL<<i);//保证到当前数位为止数位都是取反的
        }
    }
    CA;
}

这里附上kk神的思路(差不多,就是在枚举拆位时,采用的是贪心遍历统计答案,感觉这样写比前缀异或和更直观一点):

void crossparallel() {
    int n, x;
    cin >> n >> x;
    vector<int> a(n + 1);
    int ans = 0;
    int tmp = 0;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        tmp ^= a[i];
        if ((tmp | x) <= x) {
            ans++;
            tmp = 0;
        }
    }
    if (tmp) {
        ans = 0;
    }
    for (int i = 31; i >= 0; i--) {
        if ((x >> i) & 1) {
            int now = (x ^ (1 << i)) | ((1 << i) - 1);
            int cur = 0;
            int res = 0;
            for (int j = 1; j <= n; j++) {//直接上贪心,满足条件直接输出
                cur ^= a[j];
                if ((cur | now) == now) {
                    res++;
                    cur = 0;
                }
            }
            if (!cur)//可以证明,如果有剩,说明没法匹配(非法数位为奇数个,匹配不完)
                ans = max(ans, res);
        }
    }
    if (ans)
        cA;
    else
        c1;
}

E. Girl Permutation

组合数,逆元

大概思路

易知,如果premax最后一位不等于sufmax的第一位,那么肯定输出 $0$

不然就直接不考虑数组最大值所在位,先从剩下的 $n-1$ 个数中任取 $m_1-1$ 个数($\textrm{C}_{n-1}^{m-1}$),放在最大值前面,然后从前往后遍历,假象自己在一个队列中维护“数的大小关系”,如果这个数对应的下标是“premax”所在处,那么这个数只能放在队首,对总方案数贡献为 $1$;如果这个数对应的下标不是“premax”所在处,那么这个数可以插在队首以前的任何一个位置,对总方案数贡献为 $i-1$。

对sufmax那边的处理也差不多,倒序收集一遍方案数就行。

参考代码

const int MAXN=2e5+5;
ll fac[MAXN],invfac[MAXN];
ll C(ll m,ll n){return (m<0||m>n)?0:MO(MO(fac[n]*invfac[m])*invfac[n-m]);}
ll powp(ll x,ll n){
    ll ans=1;
    while(n){
        if(n&1)ans=MO(ans*x);
        x=MO(x*x);n>>=1;
    }
    return ans;
}
void pre(){//预处理组合数
    fac[0]=fac[1]=invfac[0]=invfac[1]=1;
    FOR(i,2,MAXN-1){
        fac[i]=MO(fac[i-1]*i);
        invfac[i]=powp(fac[i],M-2);
    }
}


bool ism[200005];
void solve(){
    CIN(n);CIN(m1);CIN(m2);
    memset(ism,0,sizeof ism);
    int a,b;
    FOR(i,1,m1){CIN(tmp);ism[tmp]=1;if(i==m1)a=tmp;}
    FOR(i,1,m2){CIN(tmp);ism[tmp]=1;if(i== 1)b=tmp;}
    if(a!=b){cout<<0<<ET;return;}

    ll ans=C(a-1,n-1);
    FOR (i,1,a-1)if(!ism[i])ans=MO(ans*(i-1));
    rFOR(i,n,a+1)if(!ism[i])ans=MO(ans*(n-i));
    CA;
}

F. Nobody is needed

待补题

附录

这里贴上用到的头部和main函数:

#include<iostream>
#include<vector>
#include<cmath>
#include<set>
#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
#define ET '\n'
#define FOR(i,a,b) for(ll i=(a);i<=(b);i++)
#define rFOR(i,a,b) for(ll i=(a);i>=(b);i--)
//趋向for循环,一般情况别用,规定方向时会跳出的循环在这里不会跳出
#define rep(i,a,b) for(ll i=(a);i!=(b)+2*(a<b)-1;i+=2*(a<b)-1)
#define CIN(a) ll a;cin>>a
#define DCIN(a) ld a;cin>>a
#define SCIN(a) string a;cin>>a
#define CA cout<<ans<<ET
#define CY cout<<"YES"<<ET
#define CN cout<<"NO"<<ET
#define max(a,b) ((a>b)?(a):(b))
#define min(a,b) ((a<b)?(a):(b))
std::mt19937 myrand(time(0));
ll rnd(ll l, ll r) {return std::uniform_int_distribution<ll>(l, r)(myrand);}
//没有C++20就不能用ranges了//CF又可以用C++20了,封印解除
#define PP(l,r,CK) *ranges::partition_point(ranges::iota_view((l),(r)+1),(CK))
int M=1e9+7;
inline ll MO(ll x){return (x%M+M)%M;}
string ANS[2]={"No\n","Yes\n"};

void solve(){

}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    //pre();
    //CIN(t);while(t--)
    solve();
    return 0;
}