codeforces Simba on the Circle (dp)

Simba on the Circle


 题意:长度为n的环,第i个数字为ai。 从起点s出发,走一个格子的花费为1,输出当前数字的花费为0,输出n个数的非递减序列的最小花费是多少,并打印方案。


若ai都不相同的话就很好做了,考虑值相同的ai作为一段,其中肯定有一个起点和终点,即这一段第一个走的i和最后一个走的i,从起点向同一个方向走到终点走完所有ai肯定是最优的。(终点就在起点旁边→_→)


dp1[i]表示以i为起点,走值相同的ai,的答案

dp2[i]表示以i为终点


转移:

dp2[i] = min(dp1[j] + dis[i][j]),  aj为比ai大的最小的一个

如果ai相同的只有一个,dp1[i] = dp2[i]

否则, dp1[i] = min(dp2[j] + cd[i][j]),  ai = aj注意方向大概要走一圈的样子


边界 dp2[i] = 0 (ai = max)


#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;

#define LL long long
#define N 2010
#define M 200020
#define inf (1100000000)
#define ls (i << 1)
#define rs (ls | 1)
#define md (ll + rr >> 1)
#define lson ll, md, ls
#define rson md + 1, rr, rs

int d[N][N], cd[N][N], dp1[N], dp2[N];
int nxt[N], a[N];
int n, s;
int gao2(int u) ;
int gao1(int u);

int gao2(int u) {
	int &res = dp2[u];
	if(res != -1) return res;
	if(nxt[u] == inf) return res = 0;
	res = inf;
	for(int i = 0; i < n; ++i) {
		if(a[i] == nxt[u]) {
			res = min(res, gao1(i) + d[u][i]);
		}
	}
	//printf("dp2[%d] %d\n", u, dp2[u]);
	return res;
}

int gao1(int u) {
	int &res = dp1[u];
	if(res != -1) return res;
	res = inf;
	for(int dir = -1; dir <= 1; dir += 2) {
		int v = -1;
		for(int i = 1; i < n; ++i) {
			int t = (u + dir * i + n ) % n;
			if(a[t] == a[u]) {
				v = t;
				break;
			}
		}
		if(v == -1)
			res = min(res, gao2(u));
		else {
			int add = dir == -1 ? cd[u][v] : cd[v][u];
			res = min(res, gao2(v) + add);
		}
	}
	//printf("dp1[%d] %d\n", u, dp1[u]);
	return res;
}
void output1(int u);
void output2(int u);

void output2(int u){
	int res = inf;
	if(nxt[u] == inf) return ;
	for(int i = 0; i < n; ++i) {
		if(a[i] == nxt[u]) {
			res = min(res, dp1[i] + d[u][i]);
		}
		if(res == dp2[u]) {
			printf("%c%d\n", (i-u+n)%n == d[u][i] ? '+' : '-', d[u][i]);
			output1(i);
			return ;
		}
	}
}
void output1(int u) {
	int res = inf;
	for(int dir = -1; dir <= 1; dir += 2) {
		int v = -1;
		for(int i = 1; i < n; ++i) {
			int t = (u + dir * i + n ) % n;
			if(a[t] == a[u]) {
				v = t;
				break;
			}
		}
		if(v == -1)
			res = min(res, dp2[u]);
		else {
			int add = dir == -1 ? cd[u][v] : cd[v][u];
			res = min(res, dp2[v] + add);
		}
		if(res == dp1[u]) {
			int last = 0;
			dir = (dir == -1 ? 1 : -1);
			for(int i = 1; i < n; ++i) {
				int t = (u + dir*i + n) % n;
				if(a[t] == a[u] || t == v) {
					printf("%c%d\n", dir == -1 ? '-' : '+', i-last);
					last = i;
				}
			}
			if(v == -1) output2(u);
			else output2(v);
			return ;
		}
	}
}
int main() { 
	//freopen("in.txt", "r", stdin);
	scanf("%d%d", &n, &s);
	--s;
	int mi = inf;
	for(int i = 0; i < n; ++i)
		scanf("%d", &a[i]), mi = min(mi, a[i]);
	for(int i = 0; i < n; ++i) {
		nxt[i] = inf;
		for(int j = 0; j < n; ++j) {
			if(a[j] > a[i])
				nxt[i] = min(nxt[i], a[j]);
		}
	}
	for(int i = 0; i < n; ++i) {
		for(int j = 0; j < n; ++j) {
			d[i][j] = min(abs(i-j), n-abs(i-j));
			cd[i][j] = j >= i ? j - i : n - (i-j);
		}
	}

	memset(dp1, -1, sizeof dp1);
	memset(dp2, -1, sizeof dp2);
	int ans = inf;
	for(int i = 0; i < n; ++i) {
		if(a[i] == mi)
		ans = min(ans, gao1(i) + d[i][s]);
	}
	printf("%d\n", ans);
	int res = inf;
	for(int i = 0; i < n; ++i) {
		if(a[i] == mi)  {
			res = min(res, dp1[i] + d[i][s]);
			if(res == ans) {
				printf("%c%d\n", (i-s+n)%n == d[s][i] ? '+' : '-', d[s][i]);
				output1(i);
				return 0;
			}
		}
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值