2024牛客寒假算法基础集训营2【难题详解】

本文讨论了多重背包问题的三种解法:暴力TLE版本、二进制优化的O(nmlogn)方法和基于图论的O(nm+n^2)解法。同时涉及C.Tokitsukaze的问题,如Min-MaxXOR中的关键点和线段树在PowerBattle问题中的应用。
摘要由CSDN通过智能技术生成

D. Tokitsukaze and Slash Draw

多重背包解法

题意:将物品移动n-k步的最小价值,移动ai步需要花费bi (若>=n则%n)

暴力TLE O(n^2*m)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
void solve(){
    int m,n,k;cin>>n>>m>>k;
    int v[m]={};int w[m]={};
    for(int i=0;i<m;i++){
        cin>>v[i]>>w[i];
    }
    vector<int>dp(n+1,1e18);
    dp[k]=0;
    for(int i=0;i<m;i++){
        for(int p=0;p<n;p++){
            for(int j=0;j<=n;j++){
                dp[j]=min(dp[j],dp[(j-v[i]+n)%n]+w[i]);
            }
        }
    }
    if(dp[n]==1e18)cout<<-1<<endl;
    else cout<<dp[n]<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    cin>>_;
    while(_--)solve();
    return 0;
}

二进制优化 O(nmlogn)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
void solve(){
    int m,n,k;cin>>n>>m>>k;
    vector<int>v;
    vector<int>w;
    for(int i=0;i<m;i++){
        int a,b;cin>>a>>b;
        int t=n-1;
        if(n%a==0){
            t=n/a-1;
        }
        int cnt=1;
        while(t>cnt){
            v.push_back(cnt*a);
            w.push_back(cnt*b);
            t-=cnt;
            cnt*=2;
        }
        if(t>0){
            v.push_back(t*a);
            w.push_back(t*b);
        }
    }
    vector<int>dp(n+1,1e18);
    dp[0]=0;
    for(int i=0;i<v.size();i++){
        for(int j=0;j<=n;j++){
            dp[j]=min(dp[j],dp[((j-v[i])%n+n)%n]+w[i]);
        }
    }
    if(dp[n-k]==1e18)cout<<-1<<endl;
    else cout<<dp[n-k]<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    cin>>_;
    while(_--)solve();
    return 0;
}

此题不能用单调队列优化,因为取模的关系,导致状态转移没有单调性

图论解法 O(nm+n^2)

可以发现,此题为起点为 k 终点为 n 的最短路

不要使用堆优化,因为是稠密图

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
#define x first
#define y second
typedef pair<int,int> PII;
const int N=5050;
const int mod=998244353;
const int INF=1e18;
int n,m,k;
void solve(){
    cin>>n>>m>>k;
    PII edge[m];
    for(int i=0;i<m;i++){
        int a,b;cin>>a>>b;
        edge[i]={a,b};
    }
    int d[n+1]={};
    int book[n+1]={};
    for(int i=1;i<=n;i++)d[i]=1e18;
    d[k]=0;
    for(int p=0;p<n;p++){
        int mn=INF;int t=k;
        for(int i=1;i<=n;i++){//找最近的点
            if(book[i]==0&&d[i]<=mn){
                mn=d[i];
                t=i;
            }
        }
        if(t==n)break;
        book[t]=1;
        for(int j=0;j<m;j++){//松弛出边
            if(book[(t+edge[j].x+n-1)%n+1])continue;
            d[(t+edge[j].x+n-1)%n+1]=min(d[(t+edge[j].x-1+n)%n+1],d[t]+edge[j].y);
        }
    }
    if(d[n]==1e18)cout<<-1<<endl;
    else cout<<d[n]<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    cin>>_;
    while(_--)solve();
    return 0;
}

C. Tokitsukaze and Min-Max XOR

关键点:\min(a_{b1},a_{b2},\ldots,a_{bm})\oplus\max(a_{b1},a_{b2},\ldots,a_{bm})\leq k  与a的相对位置没有关系,所以当max=ai,min=aj,且ai⊕aj<=k时,中间数可以随便选,那么贡献就为 2^i-j-1

暴力枚举最大最小值为O(n^2),trie树优化为O(nlogn):

对于一个ai,其贡献可能为 2^{i-j_1-1}+2^{i-j_2-1}+\cdots,所以trie中维护累加  \sum2^{-j}

故对于一个ai其贡献为:  ans+=ksm(2,i-1)*query(a[i]);
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin()+1,x.end()
#define endl '\n'
const int N=200010;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
unordered_map<int,int>mp;
unordered_map<int,int>mp2;
int ksm(int a,int n){
    int r=1ll;
    while(n){
        if(n&1)r=r*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return r;
}
int inv(int a){
    return ksm(a,mod-2);
}
void solve(){
    int n,k;cin>>n>>k;
    int nx[n*30][2]={},val[n*30]={},idx=1;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++)cin>>a[i];
    sort(all(a));

    auto insert=[&](int x,int pos){
        int t=0;//初始为根节点0
        for(int i=30;i>=0;i--){
            int j=x>>i&1;
            if(!nx[t][j])nx[t][j]=idx++;
            t=nx[t][j];
            val[t]=(val[t]+mp2[pos])%mod;
        }
    };

    auto query=[&](int x){
        int t=0,res=0;
        for(int i=30;i>=0;i--){
            int j=x>>i&1;
            if(k>>i&1){
                if(nx[t][j])res=(res+val[nx[t][j]])%mod;
                if(nx[t][!j]){
                    t=nx[t][!j];
                }else return res;
            }else{
                if(nx[t][j]){
                    t=nx[t][j];
                }else return res;
            }
        }
        res=(res+val[t])%mod;
        return res;
    };

    int ans=0;
    for(int i=2;i<=n;i++){
        insert(a[i-1],i-1);
        ans=(ans+mp[i-1]*query(a[i])%mod)%mod;
    }
    cout<<(ans+n)%mod<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    cin>>_;
    int t=1;
    for(int i=0;i<=N;i++){
        mp[i]=t;
        mp2[i]=inv(t);
        t=t*2%mod;
    }
    while(_--)solve();
    return 0;
}

G. Tokitsukaze and Power Battle (easy)

不难发现此题本质是:

线段树中存每个点的pre[i]-a[i+1] 

区间修改:维护 [l,r] 区间的 pre[i]-a[i+1] 

区间查询:查询区间的最大值

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f3f3f3f3f;
struct SegmentTree
{
    struct Node
    {
        int l, r;
        int val, lz;
    };
    vector<Node>tr;
    SegmentTree(int n) {
        tr.resize(4 * n + 5);
    }
    void pushup(int t)
    {
        tr[t].val = max(tr[t * 2].val, tr[t * 2 + 1].val);
    }
    void pushdown(int t)
    {
        if (tr[t].lz) {
            tr[2 * t].val += tr[t].lz;
            tr[2 * t + 1].val += tr[t].lz;
            tr[2 * t].lz += tr[t].lz;
            tr[2 * t + 1].lz += tr[t].lz;
            tr[t].lz = 0;
        }
    }
    template<typename M>
    void build(int t, int l, int r, vector<M>& a)
    {
        tr[t].l = l, tr[t].r = r;
        if (l == r) {
            tr[t].val = a[l];
            return;
        }
        int mid = tr[t].l + tr[t].r >> 1;
        build(t * 2, l, mid, a); build(t * 2 + 1, mid + 1, r, a);
        pushup(t);
    }
    template<typename M>
    void modify(int t, int x, M val) {
        if (tr[t].l == tr[t].r) {
            tr[t].val += val ;
            tr[t].lz += val;
            return;
        }
        pushdown(t);
        int mid = tr[t].l + tr[t].r >> 1;
        if (x <= mid) modify(t * 2, x, val);
        else modify(t * 2 + 1, x, val);
        pushup(t);
    }
    template<typename M>
    void modify(int t, int l, int r, M val) {
        if (l <= tr[t].l && tr[t].r <= r) {
            tr[t].val += val ;
            tr[t].lz += val;
            return;
        }
        pushdown(t);
        int mid = tr[t].l + tr[t].r >> 1;
        if (l <= mid) modify(t * 2, l, r, val);
        if (r > mid) modify(t * 2 + 1, l, r, val);
        pushup(t);
    }
    int query(int t, int l, int r) {
        if (l <= tr[t].l && tr[t].r <= r) {
            return tr[t].val;
        }
        pushdown(t);
        int mid = tr[t].l + tr[t].r >> 1;
        int mx = -INF;
        if (l <= mid) mx = max(mx, query(t * 2, l, r));
        if (r > mid) mx = max(mx, query(t * 2 + 1, l, r));
        return mx;
    }
};
void solve(){
    int n,q;cin>>n>>q;
    vector<int>a(n+1);
    vector<int>pre(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
        pre[i]=pre[i-1]+a[i];
    }
    for(int i=1;i<=n-1;i++){
        pre[i]-=a[i+1];
    }
    SegmentTree tr(n);
    tr.build(1,1,n-1,pre);
    while(q--){
        int op;cin>>op;
        if(op==1){
            int x,y;cin>>x>>y;
            if(x-1>=1)tr.modify(1,x-1,a[x]-y);
            if(x<=n-1)tr.modify(1,x,n-1,y-a[x]);
            a[x]=y;
        }else{
            int l,r;cin>>l>>r;
            if(l==1)cout<<tr.query(1,l,r-1)<<endl;
            else cout<<tr.query(1,l,r-1)-tr.query(1,l-1,l-1)-a[l]<<endl;
        }
    }

}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    cin>>_;
    while(_--)solve();
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值