[ARC119F]AtCoder Express 3

题目

传送门 to AtCoder

题目描述
有一部 精彩绝伦的 国漫,一共 n + 1 n+1 n+1 集,编号为 0 0 0 n n n 。每一集中的主角,要么为「阿七」,要么为「阿柒」;特别的,第 0 0 0 集和第 n n n 集两个主角都有。现在电视台提供了一个 “根据你的喜好自动推荐” 换台模式。具体来说,如果当前在观看第 i i i 集,按一下换台键之后,会立刻找到接下来的第一个主角相同的剧集,然后跳转;第 0 0 0 集和第 n n n 集等效于二者兼具,而做选择不需要按任何键。当然传统的换台也被保留了下来,按一下左键,进入上一集;按一下右键,进入下一集。

现在「湘妹」在看第 0 0 0 集。她只想赶紧看最后一集,赶紧去学 “我今天就要带这个傻子走”,而她只想按最多 k k k 次按键。糟糕的是,她并不知道中间的剧集的主角是谁,只能问 当事人「小剪刀」;它的记忆又比较模糊,只能确定一部分,剩下的就无能为力了。

如果有 r r r 集的主角是不确定的,显然就有 2 r 2^r 2r 种不同的情况。在所有这些情况中,有多少种情况能够让「湘妹」完成她的梦想?请你告诉「湘妹」。

数据范围与提示
n ≤ 4000 n\le 4000 n4000 k ≤ n + 1 2 k\le\frac{n+1}{2} k2n+1 。不保证「小剪刀」到底能记住多少集的主角,因为年轻人,剪刀不是这样用的,要把气集中在尖尖上……

思路

《关于魔改题面被骂这件事》:建议大家看原本的题面。

这种计数题,肯定要 d p \tt dp dp 嘛。状态是什么呢?显然是最短路距离嘛。设置为到哪里的最短路距离呢?

先来想象一下整个旅程。当你位于一段连续的 A A A 的开头时,你只有两种选择:后退一步,使用 B B B 跳过这一段;一直往前走,老老实实地走过这一段。因为 没有一种路径可以同时跳过后一个交界处的两个点。是不是有点抽象?举个例子: A ⋯ A B ⋯ B B A A ⋯ A A\cdots AB\cdots B{\color{green}BA}A\cdots A AABBBAAA,你能构造出一条路径,那两个绿色标记的站台都不经过吗?不能。无论用 A    e x p r e s s A\;\rm express Aexpress 还是 B    e x p r e s s B\;\rm express Bexpress 都做不到。

所以,我们只需要记录这样的两个站台的最短路距离。这好像是 O ( k 2 ) \mathcal O(k^2) O(k2) 的,实则不然。根据最短路的定义,二者的差值必然不超过 1 1 1,否则就可以内部更新一次。所以这是 O ( k ) \mathcal O(k) O(k) 的。

定义 d p ( i , j , d , c ) dp(i,j,d,c) dp(i,j,d,c) 为,站台 i i i i + 1 i+1 i+1 最短路距离分别为 j j j j + d j+d j+d,并且站台 i + 1 i+1 i+1 是类型 c c c 。这是 O ( n k ) \mathcal O(nk) O(nk) 的。转移的时候,由于有 ? 的存在,需要枚举下一个转变点(非类型 c c c 的站台),是 O ( n ) \mathcal O(n) O(n) 转移。

# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
	rep(i,0,n-2) rep(j,0,k) rep(d,-1,1)
	rep(c,0,1) rep(p,i+1,n-1){
		if(str[p+1]-'A' != c){ // may be a stop
			int d1 = min(j+2,j+d+(p-i)-1);
			int d2 = min(j+1,j+d+(p-i));
			if(d1 <= k+1) // useful
				add(dp[p][d1][d2-d1+1][c^1],
					dp[i][j][d+1][c]);
		}
		if(str[p+1]-'A' == (c^1)) // must stop
			break; // cannot keep on
	}

我们细心一点就会发现,当 p − i ≥ 4 p-i\ge 4 pi4 时,哪怕 d = − 1 d=-1 d=1,也有
{ j + d + ( p − i ) − 1 ≥ j + 2 j + d + ( p − i ) ≥ j + 1 \begin{cases} j+d+(p-i)-1\ge j+2\\ j+d+(p-i)\ge j+1 \end{cases} {j+d+(pi)1j+2j+d+(pi)j+1

也就是说,后面都会转移到 f ( p , j + 2 , − 1 , 1 − c ) f(p,j+2,-1,1-c) f(p,j+2,1,1c) 的状态。那就打个懒标记呗!很容易实现。

于是转移变成了 O ( 4 ) \mathcal O(4) O(4),总复杂度 O ( n k ) \mathcal O(nk) O(nk),只是常数高达 3 × 2 × 4 = 24 3\times 2\times 4=24 3×2×4=24,已经接近 log ⁡ \log log 了……

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int_ readint(){
	int_ a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int_ x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int Mod = 1e9+7;
inline void add(int &x,const int &y){
	(x += y) >= Mod ? (x -= Mod) : 0;
}

const int MaxN = 4005;
// dis = (j, j + dif), color of i+1 is fourth
int dp[MaxN][MaxN][3][2];
int lazy[MaxN][MaxN][3][2];
char str[MaxN];

int main(){
	int n = readint(), k = readint();
	scanf("%s",str+1);
	if(str[1] == '?') // dis = (0,1)
		dp[0][0][2][0] = dp[0][0][2][1] = 1;
	else dp[0][0][2][str[1]-'A'] = 1;
	for(int i=0; i<=n-1; ++i)
	for(int j=0; j<=k+1; ++j) // k+1 is useful
	for(int d=-1; d<=1; ++d) // dif
	for(int c=0; c<2; ++c){
		if(str[i+1]-'A' != (c^1))
			add(dp[i][j][d+1][c],lazy[i][j][d+1][c]);
		if(str[i+1]-'A' != c) // can be passed on
			add(lazy[i+1][j][d+1][c],lazy[i][j][d+1][c]);
		if(dp[i][j][d+1][c]){
			bool xez_yyds = true;
			for(int p=i+1; p<=n-1&&p<=i+3; ++p){
				if(str[p+1]-'A' != c){ // may be a stop
					int d1 = min(j+2,j+d+(p-i)-1);
					int d2 = min(j+1,j+d+(p-i));
					if(d1 <= k+1) // useful
						add(dp[p][d1][d2-d1+1][c^1],
							dp[i][j][d+1][c]);
				}
				if(str[p+1]-'A' == (c^1)){ // must stop
					xez_yyds = false;
					break; // cannot keep on
				}
			}
			if(xez_yyds && i+4 <= n-1)
				add(lazy[i+4][j+2][0][c^1],
					dp[i][j][d+1][c]);
		}
	}
	int ans = 0;
	rep(j,0,k+1) rep(d,-1,1) rep(c,0,1)
		if(j+d <= k) // satisfactory
			add(ans,dp[n-1][j][d+1][c]);
	printf("%d\n",ans);
	return 0;
}

S Y    t q l \sf SY\;tql SYtql

请看 H a n d I n D e v i l \sf HandInDevil HandInDevil 的博客,反正我是不懂了……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ARC069 F 题目传送门:https://atcoder.jp/contests/arc069/tasks/arc069_d 题目描述: 给定两个长度为 $n$ 的字符串 $s$ 和 $t$,每个字符都是小写字母。你需要找到一个长度为 $n$ 的字符串 $u$,满足: - 对于所有 $i \in [1,n]$,都有 $u_i \in \{s_i,t_i\}$。 - 对于所有 $i \in [1,n-1]$,都有 $u_i \neq u_{i+1}$。 - 对于所有 $i \in [1,n-2]$,都有 $u_i \neq u_{i+2}$。 求满足条件的字符串 $u$ 的个数,对 $10^9+7$ 取模。 解题思路: 这是一道比较经典的字符串构造问题,可以用 dp 或者数学方法来解决。 方法一:dp 我们可以使用 dp 来解决这个问题。设 $f_{i,j,k}$ 表示构造了前 $i$ 个字符,第 $i$ 个字符为 $j$,且第 $i-1$ 个字符为 $k$ 的方案数。其中,$j \in \{s_i,t_i\}$,$k \in \{s_{i-1},t_{i-1}\}$。 状态转移方程如下: $$f_{i,j,k} = \sum\limits_{l \in \{s_{i-2},t_{i-2}\},l \neq j} f_{i-1,k,l}$$ 最终的答案为 $\sum\limits_{j \in \{s_n,t_n\}} \sum\limits_{k \in \{s_{n-1},t_{n-1}\}} f_{n,j,k}$。 时间复杂度为 $O(n)$。 方法二:数学 我们可以定义 $a_i$ 表示以 $s_i$ 结尾,且不存在相邻字符相等的字符串的方案数;$b_i$ 表示以 $s_i$ 结尾,且存在相邻字符相等的字符串的方案数;$c_i$ 表示以 $t_i$ 结尾,且不存在相邻字符相等的字符串的方案数;$d_i$ 表示以 $t_i$ 结尾,且存在相邻字符相等的字符串的方案数。 根据题目的限制条件,我们可以得到递推式: $$\begin{cases} a_{i+1} = 2(b_i+c_i+d_i) \\ b_{i+1} = a_i \\ c_{i+1} = 2(a_i+d_i) \\ d_{i+1} = b_i \end{cases}$$ 初始状态为 $a_1=1,b_1=0,c_1=1,d_1=1$。 最终的答案为 $a_n+c_n$。 时间复杂度为 $O(n)$。 代码实现:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值