2025 “钉耙编程”中国大学生算法设计暑期联赛(6)

传送排序

设dp[i]表示数字i不动,形成1,2,3.......i的最小步数

不难发现如果pos[i]>pos[i-1]时,dp[i]=dp[i-1]

至于从其他地方转移,dp[i]=min(dp[i],dp[j]+i-j)

我们只需要将pos[i]之前位置切比他小的dp[j]-j找到最小值即可

而却我们发现对于原序列中的每个递增序列dp[j]-j是不升的,所以我们转移i的时候,相当于会从他之前每个递增序列的尾部转移,从而保证了正确性

Code:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define inl inline
#define pii pair<int,int>
using namespace std;
inl int read() {
    int sum=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
    return sum*f;
}
inl int ksm(int a,int b,int p) {
    int res=1;
    while(b) {
        if(b&1) res=res*a%p;
        b>>=1;
        a=a*a%p;
    }
    return res;
}
const int N=2e5+10;
int pos[N];
struct node{
    struct Node{
        int minn;
    }tree[800010];
    inl void build(int k,int l,int r) {
        if(l==r) {
            tree[k].minn=1e9;
            return;
        }
        int mid=(l+r)>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
        tree[k].minn=min(tree[k<<1|1].minn,tree[k<<1].minn);
    }
    inl void update(int k,int l,int r,int pos,int num) {
        if(l==r) {
            tree[k].minn=num;
            return;
        }
        int mid=(l+r)>>1;
        if(pos<=mid) update(k<<1,l,mid,pos,num);
        if(pos>mid) update(k<<1|1,mid+1,r,pos,num);
        tree[k].minn=min(tree[k<<1|1].minn,tree[k<<1].minn);
    }
    inl int query(int k,int l,int r,int l1,int r1) {
        //cout<<l<<" "<<r<<endl;
        //exit(0);
        if(l1<=l&&r<=r1) {
            //cout<<l<<" "<<r<<" "<<tree[k].minn<<endl;
            return tree[k].minn;
        }
        int mid=(l+r)>>1;
        int ans=1e9;
        if(l1<=mid) ans=min(ans,query(k<<1,l,mid,l1,r1));
        if(r1>mid) ans=min(ans,query(k<<1|1,mid+1,r,l1,r1));
        return ans;
    }
}T;
inl void solve() {
    int n=read();
    for(re int i=1;i<=n;i++) {
        int x=read();
        pos[x]=i;
    }
    vector<int> dp(n+10,0x3f3f3f3f);
    dp[0]=0;
    pos[0]=0;
    pos[n+1]=n+1;
    T.build(1,0,n+1);
    for(re int i=0;i<=n+1;i++) {
        if(i>0&&pos[i]>pos[i-1]) dp[i]=min(dp[i],dp[i-1]);
        //cout<<pos[i]<<" "<<T.query(1,0,n+1,0,pos[i])<<endl;
        dp[i]=min(dp[i],T.query(1,0,n+1,0,pos[i])+i);
        //cout<<i<<endl;
        //cout<<pos[i]<<" "<<dp[i]<<endl;
        T.update(1,0,n+1,pos[i],dp[i]-i);
    }
    cout<<dp[n+1]<<endl;
}
int main() {
    int T=read();
    while(T--) {
        solve();
    }
    return 0;
}

cats 的 max

其实不难知道要对m进行状态压缩,但是还是有些要好好想的地方

首先,k>=m时,只要选出每列最大元素即可

剩下我们对于k<m时,我们可以预处理出每行取的状态的最大值,最后答案其实就是不超过k个区间合并的区间的最大值

看代码,其实对区间的处理技巧很高:

#include<bits/stdc++.h>
#define int long long
#define re register
#define inl inline
#define pii pair<int,int>
using namespace std;
inl int read() {
    int sum=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
    return sum*f;
}
inl int ksm(int a,int b,int p) {
    int res=1;
    while(b) {
        if(b&1) res=res*a%p;
        b>>=1;
        a=a*a%p;
    }
    return res;
}
int n,m,k;
int dp[15][1<<14],a[15];
int tmp[1<<14];
int val[1<<14],tmp2[1<<14];
inl void solve() {
    n=read(),m=read(),k=read();
    k=min(k,m);
    memset(dp,0,sizeof(dp));
    memset(val,0,sizeof(val));
    for(re int i=0;i<n;i++) {
        for(re int j=0;j<m;j++) a[j]=read();
        for(re int j=1;j<(1<<m);j++) {
            tmp2[j]=tmp2[j&(j-1)]+a[tmp[j-(j&(j-1))]];//快速计算出状态值
            val[j]=max(val[j],tmp2[j]);//预处理出每个状态在n行中的最大值
        }
    }
    //cout<<111<<endl;
    for(re int i=0;i<k;i++) {
        for(re int j=0;j<(1<<m);j++) {
            for(re int t=(1<<m)-1-j(子集的补集);t;t=((t-1)&((1<<m)-1-j))(枚举补集子集)) {
                dp[i+1][j+t]=max(dp[i+1][j+t],dp[i][j]+val[t]);//状态转移
            }
        }
    }
    cout<<dp[k][(1<<m)-1]<<endl;//答案
}
signed main() {
    int T=read();
    for(re int i=0;i<13;i++) tmp[1<<i]=i;
    while(T--) {
        solve();
    }
    return 0;
}

这场打的究极不好,兄弟们放了一天假休息回回状态,加油吧)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值