专栏文章

2025 年 10 月 21 日 J 组(CJ 集训)题解

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@minjlmms
此快照首次捕获于
2025/12/02 03:28
3 个月前
此快照最后确认于
2025/12/02 03:28
3 个月前
查看原文
原:24 年集训第十二场。
这里仅是题解。

A:区间求和

预估难度 红到橙。

题意

原题意:
11:A 题原题面。
简化题意:
有一个长度为 nn 的数组 aa,求满足如下条件的 l,rl,r 个数:
i=lrai=m\sum_{i=l}^r a_i=m
mm 为给定的数。数据范围:1n105,1ai109,1m10141\le n \le 10^5,1\le a_i \le 10^9,1\le m \le 10^{14}

解法

可以用双指针跑一遍区间。边跑边用一个变量 sumsum 记录当前区间的区间和。如果 sum>msum>m 那么 l+1l+1,如果 sum=msum=m,答案加一,如果 sum<msum<m,那么什么都不干,因为后面会帮你补上来的。时间复杂度 O(n)O(n)
如果存在 ai<0a_i<0 的情况,那么就只能 O(n2)O(n^2) 暴力了,因为前缀和不存在单调性,而且上面的方法也不成立,因为如果大了的话后面来个负数这个算法就炸了。

代码

CPP
#include <iostream>
#include <cstdio>
#define int long long
#define endl '\n'
using namespace std;

int n,m;
int a[100005];
signed main()
{
    freopen("sum.in","r",stdin);
    freopen("sum.out","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i = 1;i<=n;i++){
        cin>>a[i];
    }
    int l = 1,r = 1;
    int sum = a[1];
    int ans = 0;
    if(sum==m) ans++;
    for(int i = 2;i<=n;i++){
        r++;
        sum+=a[r];
        while(sum>m&&l<=r){
            sum-=a[l];
            l++;
        }
        if(sum==m){
            ans++;
        }
    }
    cout<<ans;
    return 0;
}

B:比赛

题意

这个题意简化不了,直接看吧。
22:B 题原题面。

解法

我们考虑把整个方法倒过来。考虑归并排序,因为每次都是折半取,所以符合题目要求,而且区间内还是排序好的,于是直接修改答案即可。时间复杂度 O(nlogn)O(n\log n)

代码

CPP
#include <iostream>
#include <utility>
#define int long long
using namespace std;

int n,k;
pair<int,int> a[1500005];
int ans[1500005];
pair<int,int> c[1500005];
void merge_sort(int l,int r){
    if(l==r) return ;
    int mid = (l+r)>>1;
    merge_sort(l,mid);
    merge_sort(mid+1,r);
    int p1 = l,p2 = mid+1,p3 = l;
    while(p1<=mid&&p2<=r){
        if(a[p1].first>=a[p2].first){
            c[p3++] = a[p1++];
        }else{
            c[p3++] = a[p2++];
        }
    }
    while(p1<=mid){
        c[p3++] = a[p1++];
    }
    while(p2<=r){
        c[p3++] = a[p2++];
    }
    for(int i = l;i<=r;i++){
        a[i] = c[i];
    }//归并排序板子
    for(int i = l+k;i<=r;i++){
        if(ans[a[i].second]==0) ans[a[i].second] = n/(r-l+1)*k+1;//这一段的答案,前面是能够晋级的人数
    }
    return ;
}
signed main()
{
    freopen("gaming.in","r",stdin);
    freopen("gaming.out","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>k;
    n = (1<<n);
    for(int i = 1;i<=n;i++){
        cin>>a[i].first;
        a[i].second = i;
    }
    merge_sort(1,n);
    for(int i = 1;i<=k;i++) ans[a[i].second] = i;//前 k 个特殊处理
    for(int i = 1;i<=n;i++) cout<<ans[i]<<' ';
    return 0;
}

C:加乘运算

题意

33:C 题原题面。

解法

假设我们现在有一个式子
[(abc)e(dfg)]h[(a*^b c)*^e (d*^fg)]*^h
显然我们如果修改 aa,答案只会影响所有与 aa 有关的系数,就是 b,e,hb,e,h 三个系数。
于是,我们考虑像求后缀表达式的值一样把每个数的系数求出来,这里用一个栈就行。然后每次查询直接把差值与系数相乘并计算答案即可。时间复杂度 O(nlogn)O(n\log n)。但我并不确定,因为是 gdz 告诉我的。

代码

CPP
#include <iostream>
#include <cstdio>
// #include <stack>
#include <utility>
#define int long long
#define endl '\n'
using namespace std;

template <typename T> class stack{
    private:
        T sta[1000005];
        int tt = 0;
    public:
        void push(T x){
            sta[++tt] = x;
        }
        void pop(){
            tt--;
        }
        T top(){
            return sta[tt];
        }
        void clear(){
            tt = 0;
        }
        int size(){
            return tt;
        }
        bool empty(){
            return tt==0;
        }
};
int n,m;
stack<pair<int,int>  > stk;//pair 就是存区间的
int s[1000005];
int ac[1000005];
string a;
int b[1000005];
signed main()
{
    freopen("addmul.in","r",stdin);
    freopen("addmul.out","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i = 1;i<=n;i++) s[i] = 1;
    getline(cin,a);//防止读入 \n
    getline(cin,a);
    // cout<<a<<endl;
    // bool fflag = true;
    for(int i = 1;i<n;i++){
        cin>>b[i];
        // if(b[i]!=1) fflag = false;
    }
    int now = 1;
    int len = 1;
    int nownum = 0;
    bool flag = false;
    for(int i = 0;i<a.length();i++){
        if(a[i]>='0'&&a[i]<='9'){
            flag = true;
            nownum = nownum*10+(a[i]-'0');
        }else{
            if(a[i]=='*'){
                pair<int,int> fir,sec;
                fir = stk.top();
                stk.pop();
                sec = stk.top();
                stk.pop();
                swap(fir,sec);
                // cout<<fir.first<<' '<<fir.second<<' '<<sec.first<<' '<<sec.second<<' '<<b[now]<<endl;
                // s[fir.first]*=b[now];
                // s[sec.second+1]/=b[now];
                for(int j = fir.first;j<=sec.second;j++){//暴力计算就行
                    s[j]*=b[now];
                }
                now++;
                stk.push({fir.first,sec.second});
            }else if(flag){
                flag = false;
                stk.push({len,len});
                ac[len++] = nownum;
                nownum = 0;
            }
        }
    }
    // if(fflag){
    //     int sum = 0;
    //     for(int i = 1;i<=n;i++){
    //         sum+=ac[i];
    //     }
    //     while(m--){
    //         int l,r;
    //         cin>>l>>r;
    //         sum+=r-ac[l];
    //         cout<<sum<<endl;
    //     }
    //     return 0;
    // }
    // cout<<endl;
    int nowans = 0;
    // for(int i = 1;i<=n;i++){
    //     s[i]*=s[i-1];
    // }
    for(int i = 1;i<=n;i++){
        nowans+=ac[i]*s[i];
        // cout<<ac[i]<<' '<<s[i]<<endl;
    }
    // cout<<nowans<<endl;
    while(m--){
        int a,b;
        cin>>a>>b;
        if(b>ac[a]){
            nowans+=(b-ac[a])*s[a];
            ac[a] = b;
        }else{
            nowans-=(ac[a]-b)*s[a];
            ac[a] = b;
        }
        cout<<nowans<<endl;
    }
    return 0;
}

D:课程安排

题意

44:D 题原题面

解法

显然,我们可以用 DP 求解。
设第 ii 天有课的时间为 cnticnt_i,最左边的是 llill_i,最右边的是 rrirr_i。我们枚举当前是第 ii 天,要剩余 jj 个课时,我们能够很快得到满足条件的最短区间。它的代价是 cntijcnt_i-j,价值是区间长度。
然后跑 01 背包板子就行了,但是需要注意的是:你不能一天搞两遍,所以这里再加一次循环跑枚举这一天砍哪个。

代码

CPP
#include <iostream>
#include <cstdio>
#include <vector>
#define int long long
#define endl '\n'
using namespace std;

const int N = 505;
int n,m,kkk;
int a[N][N];
int cnt[N],ll[N],rr[N],s[N][N];
int w[N][N],c[N][N];
int dp[N][N];
signed main()
{
    freopen("skip.in","r",stdin);
    freopen("skip.out","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>kkk;
    int nowans = 0;
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=m;j++){
            cin>>a[i][j];
            if(a[i][j]==1){
                cnt[i]++;
                s[i][cnt[i]] = j;
                if(ll[i]==0) ll[i] = j;
                rr[i] = j;
            }
        }
        nowans+=rr[i]-ll[i]+1;
    }
    for(int i = 1;i<=n;i++){
        for(int j = 0;j<cnt[i];j++){
            if(j==0){
                w[i][j] = cnt[i];
                c[i][j] = rr[i]-ll[i]+1;
                continue;
            }
            int maxn = -1;
            for(int k = 1;k+j-1<=cnt[i];k++){
                maxn = max(maxn,rr[i]-ll[i]+1-s[i][k+j-1]+s[i][k]-1);
            }
            w[i][j] = cnt[i]-j;
            c[i][j] = maxn;
        }
    }
    for(int i = 1;i<=n;i++){
        for(int j = 0;j<=kkk;j++){
            for(int k = 0;k<cnt[i];k++){
                dp[i][j] = max(dp[i][j],dp[i-1][j]);
                if(j>=w[i][k]){
                    dp[i][j] = max(dp[i][j],dp[i-1][j-w[i][k]]+c[i][k]);
                }
            }
        }
    }
    cout<<nowans-dp[n][kkk];
    return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...