CF练习题17(DP)

Chocolate Bar

我们看到 \(n,m\le 30\) 想到暴搜。

考虑枚举分割线,一直到刚好满足需要或者只有一个巧克力的情况。

随手跑了个最优解。

inline int dfs(int n,int m,int k){
	if(n*m==k)return 0;
	if(k<=0)return 0;
	if(f[n][m][k]<inf)return f[n][m][k];
	int res=inf;
	up(i,1,m){
		if(m-i<=0)break;
		res=min(res,dfs(n,i,min(i*n,k))+dfs(n,m-i,max(k-i*n,0))+n*n);
	}
	up(i,1,n){
		if(n-i<=0)break;
		res=min(res,dfs(i,m,min(i*m,k))+dfs(n-i,m,max(k-i*m,0))+m*m);
	}
	return f[n][m][k]=res;
}
int n,m,k;
signed main() {
	T=read();
	memset(f,0x3f,sizeof f);
	while(T--){
		n=read();m=read();k=read();
		write(dfs(n,m,k),1);
	}
	return 0;
}

Hard Process

显然,答案具有单调性,所以可以考虑直接二分。

int n,k,sum[N],a[N];
int ans=0,p=0;
signed main(){
    n=read();k=read();
    up(i,1,n){
        a[i]=read();
        sum[i]=sum[i-1]+(a[i]==0);
    }
    up(i,1,n){
        int l=i,r=n,maxl=0;
        while(l<=r){
            int mid=(l+r)>>1;
            if(sum[mid]-sum[i-1]<=k){
                l=mid+1;
                maxl=mid-i+1;
            }
            else r=mid-1;
        }
        if(ans<maxl)ans=maxl,p=i;
    }
    write(ans,1);
    up(i,1,n){
        if(i>=p&&i<=p+ans-1)write(1,0);
        else write(a[i],0);
    }
    return 0;
}

Generate a String

有点意思的一道 DP,代码十分简短。

如果没有删去的条件,那么这个 \(dp\) 方程是显然的。

但是有删除的情况怎么处理呢?

显然,当 \(i\) 是偶数的时候,这个点只会从 \(i-1\)\(i/2\) 转移过来。
证明:如果这个点可以从 \(i+1\) 转移时,\(i+1\) 是由 \(i+2\) 转移过来的。
\(i+2\) 又是由 \(i/2+1\) 转移而来的,这个东西肯定劣于直接从 \(i/2\) 转移。

如果 \(i\) 是奇数,那么这个点会还可以从、只会从 \(i-1\)\(i+1\) 转移。

\(i+1\) 这个点又只会从 \((i+1)/2\) 的地方转移。

所以 dp 方程就很显然了。

int n,x,y;
int dp[N];
signed main(){
	n=read();x=read();y=read();
	memset(dp,0x3f,sizeof dp);
	dp[1]=x;
	up(i,1,n){
		dp[i]=min(dp[i],dp[i-1]+x);
		if(i%2==0)dp[i]=min(dp[i],dp[i>>1]+y);
		else dp[i]=min(dp[i],dp[i/2+1]+x+y);
	}
	cout<<dp[n]<<endl;
	return 0;
}

Four Segments

考虑枚举中间点 \(j\) 找前缀最大点,后缀最大点。

signed main(){
    int n = read();
    up(i,1,n)qzh[i] = read(),qzh[i] += qzh[i - 1];
    int cur1,cur2,an =0;
    int pos1,pos3,p1=0,p=0,p3=0;
    up(i,0,n){
        cur1=cur2=0;
        pos1=0,pos3=i;
        up(j,0,i)
            if (qzh[j] - (qzh[i] - qzh[j]) > cur1){
                cur1 = qzh[j] - (qzh[i] - qzh[j]);
                pos1 = j;
            }
        up(j,i,n)
            if ((qzh[j] - qzh[i]) - (qzh[n] - qzh[j]) > cur2){
                cur2 = (qzh[j] - qzh[i]) - (qzh[n] - qzh[j]);
                pos3 = j;
            }
        if (cur1 + cur2 > ans){
            ans = cur1 + cur2;
            p1 = pos1;
            p2 = i;
            p3 = pos3;
        }
    }
    cout<<p1<<" "<<p2<<" "<<p3<<endl;
    return 0;
}

Round Subset

题意可以转化为选出 \(k\) 个数,只因数分解,让 \(\min(2,5)\) 最大。

考虑转化为二维背包,把 \(2\) 个数看做价值,\(5\) 个数看做费用。

int n,m;
int a[N];
int cnt5[N],cnt2[N];
inline int get2(int x){
	int num=0;
	while(x%2==0){
		x/=2;
		num++;
	}
	return num;
}
inline int get5(int x){
	int num=0;
	while(x%5==0){
		x/=5;
		num++;
	}
	return num;
}
int dp[220][10020];
signed main(){
	n=read();m=read();
	up(i,1,n){
		a[i]=read();
		cnt2[i]=get2(a[i]);
		cnt5[i]=get5(a[i]);
	}
	memset(dp,~0x3f,sizeof(dp));
	dp[0][0]=0;
	up(i,1,n){
		dn(j,m,1){
			dn(k,10000,cnt5[i]){
				if(a[i])dp[j][k]=max(dp[j][k],dp[j-1][k-cnt5[i]]+cnt2[i]);
			}
		}
	}
	int ans=0;
	up(i,0,10000)ans=max(ans,min(i,dp[m][i]));
	cout<<ans;
	return 0;
}

Divide by Three

dp 方程很容易设计。

\(f_{i,j}\) 表示前 \(i\) 位余 \(j\) 最少需要删几个。
dp 方程很显而易见。主要是输出路径麻烦。

int f[N][3],p[N][3];
int n;
char s[N],ans[N];
int len;
signed main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    memset(f,0x3f,sizeof f);
    memset(p,-1,sizeof p);
    f[1][0]=1;
    f[1][(s[1]-'0')%3]=0;
    up(i,2,n){
        int x=(s[i]-'0')%3;
        up(j,0,2){
            f[i][j]=f[i-1][j]+1;
			p[i][j]=j;
			if(s[i]=='0'&&f[i-1][j]==i-1)continue;
			if(f[i][j]>f[i-1][(j-x+3)%3]){
				f[i][j]=f[i-1][(j-x+3)%3];
				p[i][j]=(j-x+3)%3;
			}
        }
    }
    if(f[n][0]==n){
        up(i,1,n){
            if(s[i]=='0'){
                printf("0");
                return 0;
            } 
        }
        printf("-1");
		return 0;
    }
    int pre;
	for(int i=n,j=0;i>=2;j=pre,i--){
		pre=p[i][j];
		if(f[i-1][pre]==f[i][j])ans[++len]=s[i];
	}
	if((s[1]-'0')%3==pre)ans[++len]=s[1];
    reverse(ans+1,ans+1+len);
    printf("%s",ans+1);
    return 0;
}

Mice and Holes

刚开始以为是网络流,再一看不对,\(n\le 5000\)。似乎网络流不是很能跑的样子。

考虑 \(dp\)

把洞和老鼠都按照坐标顺序排列。令 \(f_{i,j}\) 表示前 \(j\) 只老鼠进了前 \(i\) 个洞。

\[f_{i,j}=\max(f_{i-1,k}+sum_{i,j}-sum_{i,k}) \]

这个式子我们似乎可以用单调队列或者线段树来优化成 \(n^2\)


struct node{ll x,s;}b[5010];
ll n,m,l,r,a[5010],s[5010],sum[5010],dp[5010][5010],q[5010];
ll cmp(node x,node y){return x.x<y.x;}
signed main(){
    scanf("%lld%lld",&n,&m);
    memset(dp,inf,sizeof(dp)),dp[0][0]=0;
    for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(ll i=1;i<=m;i++)scanf("%lld%lld",&b[i].x,&b[i].s);
    sort(a+1,a+1+n),sort(b+1,b+1+m,cmp);
    for(ll i=1;i<=m;i++)s[i]=s[i-1]+b[i].s;
    if(s[m]<n){puts("-1");return 0;}
    for(ll i=1;i<=m;i++){
        dp[i][0]=l=r=0,q[++r]=0;
        for(ll j=1;j<=s[i]&&j<=n;j++){
            dp[i][j]=dp[i-1][j],sum[j]=sum[j-1]+abs(a[j]-b[i].x);
            while((j-q[l]>b[i].s)||l<=r&&dp[i-1][q[l]]-sum[q[l]]>dp[i-1][j]-sum[j])l++;
            q[++r]=j,dp[i][j]=min(dp[i][j],sum[j]+dp[i-1][q[l]]-sum[q[l]]);
        }
    }
    printf("%lld\n",dp[m][n]);
    return 0;
}

Roma and Poker

我只想说记搜在这种需要输出路径的题目上有着独天独厚的优势。

int n,k;
char s[N];
int f[1050][2350];
int pre[1050][2350];
int lim=1025;
inline bool dfs(int u,int cnt){
    if(u==n+1&&abs(cnt)==k)return 1;
	if(u==n+1||abs(cnt)>=k)return 0;
	if(~f[u][cnt])return f[u][cnt];
    int&res=f[u][cnt];
    if(s[u]=='W')return res=dfs(u+1,cnt+1);
	if(s[u]=='D')return res=dfs(u+1,cnt);
	if(s[u]=='L')return res=dfs(u+1,cnt-1);
    if(dfs(u+1,cnt+1))return s[u]='W',res=1;
	if(dfs(u+1,cnt))return s[u]='D',res=1;
	if(dfs(u+1,cnt-1))return s[u]='L',res=1;
    return res=0;
}
signed main(){
    n=read();k=read();
    scanf("%s",s+1);
    memset(f,-1,sizeof f);
    if(dfs(1,0))printf("%s",s+1);
    else puts("NO");
    return 0;
}

Really Big Numbers

CF 的 sb 题的代表,你以为是什么牛逼的数位 DP,其实 tm 就是一个枚举。

inline int clac(int x){
    int res=0;
    while(x){
        res+=x%10;
        x/=10;
    }
    return res;
}
signed main(){
    n=read();s=read();
    up(i,s+1,min(s+200,n)){
        if(i-clac(i)>=s){
            ans++;
        }
    }
    ans+=max(n-(s+200),0ll);
    cout<<ans;
    return 0;
}

Chemistry in Berland

首先,这是一个树形关系。

对于最底层的点来说,只有父亲是与他相连的,所以如果无法满足就从父亲处转化,如果有多的,就把多的传递给父亲,然后去掉这个点,继续进行这种操作。

告诉你个秘密,这道题卡 int128,要用 long double。

int n;
int a[N],b[N];
struct node{
    int x,k;
}c[N];
vector<pii>g[N];
f96 f[N];
inline void dfs(int u){
	f[u]=b[u]-a[u];
	for(auto i:g[u]){
        int v=i.fi,w=i.se;
        dfs(v);
        if(f[v]>=0)f[u]+=f[v];
        else f[u]+=(f96)w*f[v];
    } 
}	
signed main(){
    n=read();
    up(i,1,n)b[i]=read();
    up(i,1,n)a[i]=read();
    up(i,2,n){
        c[i].k=read();
        c[i].x=read();
        g[c[i].k].push_back({i,c[i].x});
    }
    dfs(1);
    if(f[1]>=0)puts("YES");
    else puts("NO");
    return 0;
}

Magic Numbers

数位 dp 的题目一般还是非常板的,只用把模板套上去就行了。

int m,d;
int f[2050][2050];
string l,r;
int num[2050],pos;
inline int dfs(int u,int sum,int limit,int lead){
    if(u==pos+1)return sum==0;
    if(!limit&&!lead&&~f[u][sum])return f[u][sum];
    int res=0;
    int up=limit?num[u]:9;
    if(u%2==0){
        if(d<=up){
            if(lead&&d==0)res+=dfs(u+1,(10*sum+d)%m,limit&&d==up,1);
            else res+=dfs(u+1,(sum*10+d)%m,limit&&d==up,0);
            res%=mod;
        }
    }
    else{
        up(i,0,up){
            if(i==d)continue;
            if(lead&&!i)res+=dfs(u+1,(i+sum*10)%m,limit&&i==up,1);
            else res+=dfs(u+1,(i+sum*10)%m,limit&&i==up,0);
            res%=mod;
        }
    }
    if(!limit&&!lead)f[u][sum]=res;
    return res;
    
}
inline int dp(string a){
    //reverse(a.begin(),a.end());
    pos=0;
    memset(num,0,sizeof num);
    up(i,0,a.size()-1){
        num[++pos]=a[i]-'0';
    }
    return dfs(1,0,1,1);
}
inline int check(string a){
    a=" "+a;
    int res=0;
    up(i,1,a.size()-1){
        if(i%2==1&&a[i]==d+'0')return 0;
        if(i%2==0&&a[i]!=d+'0')return 0;
        res=res*10+a[i]-'0';
        res%=m;
    }
    return res==0;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>m>>d>>l>>r;
    memset(f,-1,sizeof f);
    cout<<(dp(r)-dp(l)+check(l)+mod)%mod<<endl;
    return 0;
}

Another Sith Tournament

非常怪的一道概率 DP。

我一开始的想法是 \(f_{i,sta}\)\(i\)\(sta\) 状态的最后胜者的概率。

但是这样最后处理非常麻烦。

因为有的是求和,有的是取 \(\max\)

考虑倒序 DP。

\(f_{sta}\) 表示当集合为 \(sta\) 时,第一个人的胜率。

起始状态为 \(f_{1}=1\)

答案为 \(f_{all}\)

转移方程为 f[sta]=max(f[sta],p[j][i]*f[sta^(1<<i)]+p[i][j]*f[sta^(1<<j)])

int n;
f96 a[50][50];
f96 f[1<<19];
signed main(){
    n=read();
    up(i,0,n-1){
        up(j,0,n-1){
            scanf("%LF",&a[i][j]);
        }
    }
    int all=(1<<n)-1;
    f[1][0]=1;
    up(i,2,all){
        up(j,0,n-1){
            if(((i>>j)&1)==0)continue;
            up(k,0,n-1){
                if(((i>>k)&1)==0)continue;
                if(j==k)continue;
                f[i]=max(f[i],f[i^(1<<j)]*a[k][j]+f[i^(1<<k)]*a[j][k]);
            }
        }
    }
    f96 ans=f[all];
	printf("%.19Lf\n",ans);
    return 0;
}

Maximum path

特殊性质:只有三行,所以说如果有向左走的情况,那么一定是在中间这一行。

继续分析这个特殊性质,我们发现,如果有向左走的情况其实最多不会超过一格。

当返回格子数为偶数个时,一定可以通过上下盘曲而替代。

当返回格子数为奇数个时,一定可以通过先返回 \(1\) 格再上下盘曲而替代。

所以最多只有返回一格的必要。

那么代码就简单了:

int n;
int a[5][N];
int sum[5][N];
int f[N][5];
signed main(){
    n=read();
    up(i,1,3){
        up(j,1,n){
            a[i][j]=read();
        }
    }
    memset(f,-0x3f,sizeof f);
    f[0][1]=0;
    up(i,1,n){
        int sum=0;
        up(j,1,3)sum+=a[j][i]+a[j][i-1];
        f[i][2]=max({f[i-1][1]+a[1][i],f[i-1][2],f[i-1][3]+a[3][i]})+a[2][i];
        f[i][1]=max({f[i-1][1],f[i-1][2]+a[2][i],f[i-1][3]+a[3][i]+a[2][i]})+a[1][i];
        f[i][3]=max({f[i-1][3],f[i-1][2]+a[2][i],f[i-1][1]+a[1][i]+a[2][i]})+a[3][i];
        if(i>1){
            f[i][1]=max(f[i][1],f[i-2][3]+sum);
            f[i][3]=max(f[i][3],f[i-2][1]+sum);
        }
    }
    cout<<f[n][3];
    return 0;
}

Selling Souvenirs

这道题的做法挺多的,可以三分,亦可以针对特殊性质乱搞。

但是这里想说一种更加普适的做法。

对于 \(w_i\) 比较小的 \(01\) 背包有一种 \(O(wm\log m)\) 复杂度的做法。

首先,我们按照花费分组,可以把这道题转化成一个分组背包问题。

\(dp_{i,j}=\max(dp_{i-1,j-i\times k}+w_{i,k})\)

含义是前 \(i\) 组,重量为 \(k\) 的价值。

首先,这里有一个同余关系 \(j\)\(j-i*k\)\(\mod i\) 的情况下同余。

所以对于同一组,我们还可以对于剩余系进行分类。

然后这个 dp 存在决策单调性,因为我们已经对每个组,按照价值进行排序,所以 \(w_{i,k}\) 差分之后是递减的。

所以 \(j-k\times i\)\(j-p\times i(p>k)\) 如果 \(k\)\(j\) 更优,那么在更大的 \(j\) 上,\(k\) 依然更优。

于是可以直接分治,决策单调性。

int n,m;
vector<int>w[10];
int dp[N];
int g[N],f[N];
int p;
inline void solve(int l,int r,int x,int y){
    if(l>r)return;
    int mid=(l+r)>>1;
    f[mid]=g[mid];
    int id=mid;
    for(int i=x;i<=y&&i<mid;++i) {
		if(mid-i>w[p].size()) continue;
		int k=g[i]+w[p][mid-i-1];
		if(k>f[mid]) f[mid]=k,id=i;
	}
	if(l==r)return;
	solve(l,mid-1,x,id);
    solve(mid+1,r,id,y);
}
inline bool cmp(int x,int y){
    return x>y;
}
signed main(){
    n=read();m=read();
    int c,v;
    up(i,1,n){
        c=read();v=read();
        w[c].push_back(v);
    }
    up(i,1,3){
        //if(w[i].size()==0)continue;
        sort(w[i].begin(),w[i].end(),cmp);
        up(j,1,w[i].size()-1)w[i][j]+=w[i][j-1];
    } 
    dn(i,3,1){
        if(!w[i].size())continue;
        p=i;
        up(j,0,i-1){
            int tot=0;
            for(int k=j;k<=m;k+=i)g[++tot]=dp[k];
            solve(1,tot,1,tot);
            for(int q=1,k=j;k<=m;k+=i,q++)dp[k]=f[q];
        }
        up(j,1,m)dp[j]=max(dp[j],dp[j-1]);
    }
    cout<<dp[m];
    return 0;
}

Two Melodies

这也是网络流的一种经典模型了。

但是看到 \(n\le 5000\) 就知道不能暴力连边。

  1. 拆点,\(in_i\)\(out_i\) 连一条 \({1,1}\) 的边。
  2. 源点向 \(in_i\)\(inf,0\)\(out_i\) 向汇点连 \(inf,0\)
  3. 继续拆点,把一个点拆成 \(sub\)\(mod\),然后在相邻的同余之间建立一条 \(inf,0\) 的边
  4. 相差为 \(1\) 的最近的建立一条 \(inf,0\) 的边

求最大费用最大流。

struct Dinic{
	struct edge{
        int u,v,cap,flow,w;
    };
    vector<edge>edges;
    vector<int>g[N];
    inline void add(int u,int v,int cap,int w){
        edges.push_back({u,v,cap,0,w});
        edges.push_back({v,u,0,0,-w});
        int t=edges.size();
        g[u].push_back(t-2);
        g[v].push_back(t-1);
    }
    bool vis[N];
    int dis[N],cur[N];
    queue<int>q;
    inline bool spfa(int s,int t){
        memset(dis,0x3f,sizeof dis);
        memset(vis,0,sizeof vis);
        dis[s]=0;vis[s]=1;
        q.push(s);
        while(q.size()){
            int u=q.front();q.pop();
            vis[u]=0;
            for(auto i:g[u]){
                auto [_,v,cap,flow,w]=edges[i];
                if(dis[v]>dis[u]+w&&cap>flow){
                    dis[v]=dis[u]+w;
                    if(!vis[v]){
                        vis[v]=1;
                        q.push(v);
                        if(dis[q.front()]>dis[q.back()])swap(q.front(),q.back());
                    }
                }
            }
        }
        return dis[t]<inf;
    }
    inline int dfs(int u,int a,int t){
        if(u==t||a==0)return a;
        vis[u]=1;
        int flow=0;
        for(int &i=cur[u];i<g[u].size();i++){
            auto &e=edges[g[u][i]];
            if(!vis[e.v]&&dis[e.v]==dis[u]+e.w&&e.cap>e.flow){
                int res=dfs(e.v,min(a,e.cap-e.flow),t);
                flow+=res;
                e.flow+=res;
                edges[g[u][i]^1].flow-=res;
                a-=res;
                if(a==0)break;
            }
        }
        vis[u]=0;
        return flow;
    }
    inline pii mincostmaxflow(int s,int t){
        int res,flow=0,cost=0;
        while (spfa(s,t)) {
            memset(cur, 0, sizeof(cur));
            while ((res=dfs(s,inf,t)))flow += res,cost += res * dis[t];
        }
        return {flow,cost};
    }
}E;
int n;
int a[N];
int s,t;
signed main(){
    n=read();
    up(i,1,n)a[i]=read();
    s=4*n+1,t=4*n+2;
    E.add(0,s,2,0);
    up(i,1,n){
        E.add(s,2*n+i,inf,0);
        E.add(i,2*n+i,inf,0);
        E.add(n+i,2*n+i,inf,0);
        E.add(2*n+i,3*n+i,1,-1);
        E.add(3*n+i,t,inf,0);
        up(j,i+1,n){
            if(a[i]-a[j]==1){
                E.add(n*3+i,n+j,inf,0);
                break;
            } 
        }
        up(j,i+1,n){
            if(a[j]-a[i]==1){
                E.add(n*3+i,n+j,inf,0);
                break;
            }
        } 
		up(j,i+1,n){
            if(a[i]%7==a[j]%7){
                E.add(n*3+i,j,inf,0);
                E.add(i,j,inf,0);
                break;
            }
        } 
		up(j,i+1,n){
            if(a[i]==a[j]){
                E.add(n+i,n+j,inf,0);
                break;
            }
        } 
    }
    cout<<-E.mincostmaxflow(0,t).se;
    return 0;
}

Guards In The Storehouse

看到 \(nm\le 250\) 想到轮廓线 DP。

\(dp_{i,j,sta,b_1,b_2}\) 表示为 \(i\)\(j\) 列,这一行中,那些列被上方覆盖到 \(sta\),这一行前方是否有摄像头 \(b_1\),前面所有点是否有空格 \(b_2\)

如果 \(i,j\) 是障碍,那么 \(dp_{i,j+1,sta/{j},0,b2}+=dp_{i,j,sta,b1,b2}\)

如果不是,又分两种情况考虑,放摄像头或者不放摄像头。

如果放摄像头,那么 \(dp_{i,j+1,sta|{j},1,b_2}+=dp_{i,j,sta,b_1,b_2}\)

如果不放,又可以分情况讨论:

  1. \((i,j)\) 没有被覆盖到,\(dp_{i,j+1,sta,0,1}+=dp_{i,j,sta,0,0}\)
  2. \((i,j)\) 被覆盖到了,\(dp_{i,j+1,sta,b_1,b_2}+=dp_{i,j,sta,b_1,b_2}\)
int all=(1<<m)-1;
    dp[0][0][0][0]=1;
    up(i,0,n-1){
        up(j,0,m-1){
            up(sta,0,all){
                up(b1,0,1){
                    up(b2,0,1){
                        if(s[i][j]=='x'){
                            int p=sta&(~(1<<j)),t1=0,t2=b2;
                            dp[j+1][p][t1][t2]+=dp[j][sta][b1][b2];
                            dp[j+1][p][t1][t2]%=mod;
                        }
                        else{
                            int tt=(b1||(sta&(1<<j)));
				            int tS=sta,t1=b1,t2=b2+(1-tt);
				            if(t2<=1)dp[j+1][tS][t1][t2]+=dp[j][sta][b1][b2];
				             dp[j+1][tS][t1][t2]%=mod;
                            tS|=(1<<j);t1=1,t2=b2;
				            dp[j+1][tS][t1][t2]+=dp[j][sta][b1][b2];
                            dp[j+1][tS][t1][t2]%=mod;
                        }
                        dp[j][sta][b1][b2]=0;
                    }
                }
            }
        }
        up(sta,0,all){
            up(b1,0,1){
                up(b2,0,1){
			        dp[0][sta][0][b2]+=dp[m][sta][b1][b2];
			        dp[m][sta][b1][b2]=0;
                    dp[0][sta][0][b2]%=mod;
		        }
            } 
        } 
    }
    int ans=0;
	up(sta,0,all){
        up(b2,0,1){
            ans+=dp[0][sta][0][b2];
            ans%=mod;
        }
    } 
	cout<<ans<<endl;
    return 0;
}

Simba on the Circle

dp 方程是非常容易想到的,但是就是需要输出路径,这就很折磨了。

首先有一个结论:从 \(u\)\(v\) 一定是先到 \(v-1\)\(v+1\) 再绕一圈到 \(v\)

然后离散化一下,把每个点都存进去,暴力跑 dp。

int n,s;
int a[N];
vector<int>h;
inline int find(int x){
	return lower_bound(h.begin(),h.end(),x)-h.begin();
}
vector<int>g[N];
int f[2050],t;
int d(int a,int b){return min(abs(a-b),n-abs(a-b));}
inline pii dist(int t,int p,int x){
	int sz=g[t].size();
	if(sz==1)return {d(p,g[t][x]),0};
	pii ans={inf,0};
	if(x>0)ans=min(ans,{d(g[t][x-1],p)+n-(g[t][x]-g[t][x-1]),-1});
	else ans=min(ans,{d(g[t][sz-1],p)+(g[t][sz-1]-g[t][0]),-1});
	if(x<sz-1)ans=min(ans,{d(g[t][x+1],p)+n-(g[t][x+1]-g[t][x]),1});
	else ans=min(ans,{d(g[t][0],p)+(g[t][sz-1]-g[t][0]),1});
	return ans;
}
inline void pri(int co,int now,int x){
	int o=dist(co,now,x).se,sz=g[co].size(),tr=g[co][(x+o+sz)%sz];
	if(abs(now-tr)<n-abs(now-tr)){
		printf("%c%d\n",(now<=tr)?'+':'-',abs(now-tr));
	} 
	else {
		printf("%c%d\n",(now>=tr)?'+':'-',n-abs(now-tr));
	}
	if(!o)return;
	if(o==1){
		int p=(x+1)%sz;
		while(p!=x){
			if(p<sz-1)printf("+%d\n",g[co][p+1]-g[co][p]);
			else printf("+%d\n",n-(g[co][sz-1]-g[co][0]));
			p=(p+1)%sz;
		}
	}
	else{
		int p=(x-1+sz)%sz;
		while(p!=x){
			if(p)printf("-%d\n",g[co][p]-g[co][p-1]);
			else printf("-%d\n",n-(g[co][sz-1]-g[co][0]));
			p=(p-1+sz)%sz;
		}
	}
}
inline void print(int i,int j){
	if(!i){
		pri(i,s,j);
		return;
	}
	for(int k=0;k<g[i-1].size();k++){
		if(f[g[i-1][k]]+dist(i,g[i-1][k],j).fi==f[g[i][j]]){
			print(i-1,k);
			pri(i,g[i-1][k],j);
			break;
		}
	} 
}
signed main() {
	n=read();s=read()-1;
	up(i,0,n-1)a[i]=read(),h.push_back(a[i]);
	sort(h.begin(),h.end());
	h.erase(unique(h.begin(),h.end()),h.end());
	t=h.size();
	up(i,0,n-1){
		a[i]=find(a[i]);
		g[a[i]].push_back(i);
	}
	memset(f,0x3f,sizeof f);
	up(i,0,g[0].size()-1){
		f[g[0][i]]=min(f[g[0][i]],dist(0,s,i).fi);
	}
	up(i,1,t-1){
		for(int j=0;j<g[i-1].size();j++){
			for(int k=0;k<g[i].size();k++){
				f[g[i][k]]=min(f[g[i][k]],f[g[i-1][j]]+dist(i,g[i-1][j],k).fi);
			}
		}
	}
	int ans=inf,pos=-1;
	up(j,0,g[t-1].size()-1){
		if(ans>f[g[t-1][j]]){
			ans=f[g[t-1][j]];
			pos=j;
		}
	}
	cout<<ans<<endl;
	print(t-1,pos);
	return 0;
}

放张图:

image

posted @ 2023-11-01 11:23  LiQXing  阅读(51)  评论(0)    收藏  举报