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\) 个洞。
这个式子我们似乎可以用单调队列或者线段树来优化成 \(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\) 就知道不能暴力连边。
- 拆点,\(in_i\) 和 \(out_i\) 连一条 \({1,1}\) 的边。
- 源点向 \(in_i\) 连 \(inf,0\) ,\(out_i\) 向汇点连 \(inf,0\)
- 继续拆点,把一个点拆成 \(sub\) 和 \(mod\),然后在相邻的同余之间建立一条 \(inf,0\) 的边
- 相差为 \(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}\)
如果不放,又可以分情况讨论:
- \((i,j)\) 没有被覆盖到,\(dp_{i,j+1,sta,0,1}+=dp_{i,j,sta,0,0}\)。
- \((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;
}
放张图:


CF练习题16
浙公网安备 33010602011771号