P14256 平局
P14256 平局
题意:
定义 最大平局问题 为以下问题:
有 \(n\) 个人站成一排玩石头剪刀布,每个人只会出一种固定的手势。
定义一次操作为:
- 选择任意的相邻两人,让他们玩石头剪刀布,将败者移除。若平局,则任意移除一人。然后,其余的人会补上空位。
已知当双方手势不同时,石头可以战胜剪刀,剪刀可以战胜布,布可以战胜石头;若一局中双方手势相同,则该局游戏为平局。
你需要不断执行操作直到只剩下一个人。最大化操作造成的平局次数。
现在这 \(n\) 个人的手势还未确定。你需要求出所有满足限制的确定手势的方案的 最大平局问题 的答案之和,对 \(10^9+7\) 取模。
正题:
求一个类似计数的问题,但最大平局问题本身也不好统计,所以看着像一个 \(DP\) 套 \(DP\) 的题 。
那么我们首先就可以去思考最大平局问题,然后再考虑外层的计数。
最大平局问题:
假设每个人的手势固定,我们如何求出最大的平局次数呢?
性质:
令:
-
剪刀 \(→ 0\)
-
石头 \(→1\)
-
布 \(→2\)
我们去思考那些情况能够产生平局:
-
如果两个相邻人的手势相同,那么必定移除这两个人之一(具体哪个无所谓),然后产生一个平局。
-
如果出现类似 \(1,0,1\) 的情况,那么可以先把 \(0\) 干掉,然后变成 \(1,1\) ,产生一个平局。
为了简化描述,我们思考一下手势的差分数组:
令其取值范围是 \([-1,0,1](\mod 3)\) 。
则产生平局的情况为:
-
出现一个 \(0\) 。
-
出现 \(-1,1\) 。
然后我们再思考石头剪刀布游戏的过程,发现可行的操作有:
-
删去一个 \(0\) ,平局数量 \(+1\) 。
-
删去连续的 \(-1, 1\) ,平局数量 \(+1\) 。
-
删去连续的 \(1,1\) ,变成一个 \(-1\) 。
-
删去连续的 \(-1,-1\) ,变成一个 \(1\) 。
这个过程可以看成一个栈操作且不失最优性:
-
当前有一个 \(1\) :
-
栈顶为空:直接放入。
-
栈顶为 \(1\) :将两个 \(1\) 变成 \(-1\) 。
-
栈顶为 \(-1\) : 删去栈顶,平局数量 \(+1\) 。
-
-
当前有一个 \(-1\) :
- 直接放入 。
最后把栈中多出的 \(-1\) 三个一组,先把后两个合成 \(1\) ,再将 \(1\) 和 \(-1\) 合成,平局数 \(+1\) 。
外层DP:
平局问题解决后,外层计数就很好做了。
完全类似 \(DP\) 套 \(DP\) 的思想去维护就好了。
但值得一提的是,这里需要同时计数满足条件的方案数和该方案数目前的最大平局问题答案和。
由于是计数问题,所以两个都可以直接存储。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5010;
const int mod=1e9+7;
int n;
string s;
struct STATE{
int cnt;//-1的个数
bool h;//对首是否有1
int last_c;//最后一位的数字
};
struct EDGE{
int to;
int v;
};
int get_id(STATE now){
return now.cnt*6+now.h*3+now.last_c;
}
EDGE get_trans(STATE now,int c){
STATE res=now;
int cha=(c-now.last_c+3)%3;
if (cha==2) cha=-1;
if (cha==1){
res.last_c=c;
if (now.cnt){
res.cnt-=1;
return {get_id(res),1};
}
else{
if (res.h){
res.h=0;
res.cnt+=1;
return {get_id(res),0};
}
else{
res.h=1;
return {get_id(res),0};
}
}
}
else if(cha==0){
res.last_c=c;
return {get_id(res),1};
}
else{
res.last_c=c;
res.cnt+=1;
return {get_id(res),0};
}
}//最主要的地方-维护栈
EDGE trans[N*6][3];
ll f[2][N*6];//方案数
ll g[2][N*6];//和
int maxid=0;
int solve(int id) {
int cnt=id/6;
return cnt/3;
}
int main(){
cin >> n;
cin >> s;s=' '+s;
maxid=n*6+10;
for (int cnt=0;cnt<=n;cnt++){
for (int h=0;h<=1;h++){
for (int last_c=0;last_c<=2;last_c++){
int id=get_id({cnt,h,last_c});
for (int now_c=0;now_c<=2;now_c++){
trans[id][now_c]=get_trans({cnt,h,last_c},now_c);
}
}
}
}//预处理,模仿DP套DP。
//此题由于get_trans是 O(1)的所以不予处理也没问题。
memset(f,0,sizeof f);
memset(g,0,sizeof g);
int cur=1;
if (s[1]=='1'||s[1]=='3'||s[1]=='5'||s[1]=='7') f[1][get_id({0,0,0})]=1;
if (s[1]=='2'||s[1]=='3'||s[1]=='6'||s[1]=='7') f[1][get_id({0,0,1})]=1;
if (s[1]=='4'||s[1]=='5'||s[1]=='6'||s[1]=='7') f[1][get_id({0,0,2})]=1;
for (int i=1;i<n;i++){
memset(f[cur^1],0,sizeof f[cur^1]);
memset(g[cur^1],0,sizeof g[cur^1]);
for (int id=0;id<=maxid;id++){
//if (f[cur][id]==0) continue;这一句加了就错飞了,因为取模了
if (s[i+1]=='1'||s[i+1]=='3'||s[i+1]=='5'||s[i+1]=='7'){
(f[cur^1][trans[id][0].to]+=f[cur][id])%=mod;
(g[cur^1][trans[id][0].to]+=g[cur][id]+f[cur][id]*trans[id][0].v)%=mod;
}
if (s[i+1]=='2'||s[i+1]=='3'||s[i+1]=='6'||s[i+1]=='7'){
(f[cur^1][trans[id][1].to]+=f[cur][id])%=mod;
(g[cur^1][trans[id][1].to]+=g[cur][id]+f[cur][id]*trans[id][1].v)%=mod;
}
if (s[i+1]=='4'||s[i+1]=='5'||s[i+1]=='6'||s[i+1]=='7'){
(f[cur^1][trans[id][2].to]+=f[cur][id])%=mod;
(g[cur^1][trans[id][2].to]+=g[cur][id]+f[cur][id]*trans[id][2].v)%=mod;
}
}
cur^=1;
}
ll ans=0;
for (int id=0;id<=maxid;id++){
(ans+=g[cur][id]+1ll*f[cur][id]*solve(id))%=mod;
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号