传送排序

设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;
}
这场打的究极不好,兄弟们放了一天假休息回回状态,加油吧)
&spm=1001.2101.3001.5002&articleId=149978456&d=1&t=3&u=f8ce2a941d8e43ef86b7cea66f132c20)
1045

被折叠的 条评论
为什么被折叠?



