P10156(dp思想)
难度2
也是比较有意思的一道题。首先发现每个小团体独立,所以对于每个小团体分开直接暴力dp,dp[i][j]表示当前小团体做到第i人,走了j人,然后O(n)转移,加上部分分喜提50pts。为什么要O(n)转移呢,因为我要枚举匹配的两个人然后算贡献。但是对于这种带绝对值的贡献,我们一般都要把绝对值拆掉。拆的方式有两种,一种是像这个和这个一样进行分类讨论,还有就是像这个可以用一些特殊的做法保证绝对值内部的正负性从而消掉绝对值。
我们发现绝对值的正负只和i,j的相对位置有关,我们只需保证i<j或i>j也就是正着做或反着做,我选择正着做,绝对值处理完了,发现还是要O(n)转移怎么办。我们把式子拆开,发现i,j没关系了,自然想到dp[i][j][0/1],表示前i个人走j个人,是否有剩的。代码写起来不难。
#include<bits/stdc++.h>
using namespace std;
long long n,m,kk,x,y,a[5005],b[5005][5005],len[5005];
long long minn[5005],dp1[5005][5005],dp[5005][5005][2];//前i个人走j个人,有剩的
int main(){
ios::sync_with_stdio(false);cin.tie(0);
memset(dp,0x3f,sizeof(dp));
memset(dp1,0x3f,sizeof(dp1));
dp1[0][0]=0;
cin>>n>>m>>kk>>x;
m=n-m;
if(m%2==1) m++;
for(long long i=1;i<=n;i++){
cin>>a[i];
}for(long long i=1;i<=n;i++){
cin>>y;
len[y]++;
b[y][len[y]]=i;
}
for(long long i=1;i<=kk;i++){
if(len[i]>=2){
for(int j=0;j<=len[i];j++){
for(int k=0;k<=len[i];k++){
dp[j][k][0]=dp[j][k][1]=1e16;
}
}
dp[1][0][1]=a[b[i][1]]-x*b[i][1];
dp[1][0][0]=0;
for(int j=2;j<=len[i];j++){
dp[j][0][0]=dp[j-1][0][0];
dp[j][0][1]=min(dp[j-1][0][1],a[b[i][j]]-x*b[i][j]);
for(int k=2;k<=len[i];k+=2){
dp[j][k][0]=min(dp[j-1][k-2][1]+a[b[i][j]]+x*b[i][j],dp[j-1][k][0]);
dp[j][k][1]=min(dp[j-1][k][1],dp[j-1][k][0]+a[b[i][j]]-x*b[i][j]);
}
}
memset(minn,0x3f,sizeof(minn));
for(long long j=2;j<=len[i];j+=2){
for(long long k=j;k<=len[i];k++){
minn[j]=min(minn[j],dp[k][j][0]);
}
}
minn[0]=0;
dp1[i][0]=0;
for(long long j=0;j<=len[i];j+=2){
for(long long k=j;k<=m;k+=2){
dp1[i][k]=min(dp1[i][k],dp1[i-1][k-j]+minn[j]);
}
}
}else{
for(long long j=0;j<=m;j+=2){
dp1[i][j]=dp1[i-1][j];
}
}
}
if(dp1[kk][m]>1e14) cout<<"Impossible"<<endl;
else cout<<dp1[kk][m];
return 0;
}/*
7
5 3 2 1 6 4 7
*/