【状态设计的力量】【JZOJ 5411】 友谊

58 篇文章 0 订阅

Description

Flowey 是一朵能够通过友谊颗粒传播LOVE 的小花.它的友谊颗粒分为两种,
圆粒的和皱粒的,它们依次排列组成了一个长度为2m 的序列.对于一个友谊颗
粒的序列,如果存在1<=i< j <=2m,满足以下条件:
1 i 为偶数,j 为奇数
2 第i 颗友谊颗粒和第j 颗友谊颗粒同为圆粒或同为皱粒
3 第i 颗友谊颗粒和第j 颗友谊颗粒都还没有被使用过
那么,就可以使用这两颗友谊颗粒,然后提升一次LV.
定义一个友谊颗粒的序列为高效的,当且仅当尽可能多的提升LV 后,序列
上剩余的友谊颗粒数量不超过2n。
现在,Flowey 想知道,长度为2m 的友谊颗粒序列,有多少个不同的序列是
高效的?
定义两个友谊颗粒序列是不同的,当且仅当存在1<=i<=2m,第i 颗友谊颗粒在
一个序列中为圆粒,而在另一个中为皱粒.
由于答案可能很大,你只需要求出答案对p 取模的结果.
对于60%的数据,满足n<=300,m<=300
对于100%的数据,满足1<=n<=3000,1<=m<=3000,p<=1e9+7

抽象题意

将奇偶分开,偶一排放上面,奇一排放下面,球有颜色,上下同色匹配(匹配形如“\”或“|”),要求匹配数>=n,求给球赋值颜色的方案数

60%

dp显然
f[i][j][k]表示下面到第i个,上面剩余失配白色数为j,失配黑色数为k
枚举当前第i列上下颜色转移
状态O(n^3)转移O(1)

100%

不dp很难做,着手于优化dp状态
可以发现,有一个约束条件j+k<=n,考虑能否把k那一维省掉
设g[i][j],i,j 含义同上,那么k<=n-j, g[i][j]=njk=0f[i][j][k]
但是这样会算重,如果对于一组序列它最后为匹配的白色数为a,黑色为b
有a<=j && b<=n-j 且 a<=j’ && b<=n-j’
那么这一组序列在g[i][j]与g[i][j’]都会被计算
我们考虑当且仅当a=j 的时候计算,而对于a< j 且也合法的状态就不算了。这样只会被算一次
什么时候a=j呢,那么就应该是中间某个时刻出现了j=0(感受一下)
反证很显然:如果a>j,那么始终有j>0
于是多一维状态表示是否曾经出现了j=0,如果是才计入答案
状态O(n^2)转移O(1)

Code

#include<ctime>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
#define efo(i,v,u) for(int i=last[v],u=to[i];i;i=next[i],u=to[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
typedef double db;
char ch;
void read(int &n)
{
    n=0;int p=1;
    for(ch=getchar();ch<'0' || ch>'9';ch=getchar())
        if(ch=='-') p=-1;
    for(;'0'<=ch && ch<='9';ch=getchar()) n=n*10+ch-'0';
    n*=p;
}
const int N=3005;
int n,m,mo,f[N][N][2];
void update(int &x,ll y){x=(ll)(x+y)%mo;}
int main()
{
    freopen("friend.in","r",stdin);
    //freopen("friend.out","w",stdout);
    read(n),read(m),read(mo);
    --n,--m;
    fo(i,0,n) f[0][i][(i==0)]=1;
    fo(i,0,m)
    {
        update(f[i+1][0][1],2*f[i][0][1]);//0 0;1 1;
        update(f[i+1][1][1],f[i][0][1]);//0 1
        fo(j,1,n)
        {
            update(f[i+1][j][0],2*f[i][j][0]);
            update(f[i+1][j][1],2*f[i][j][1]);
            //0 0;1 1;
            if(j<n)
            {
                update(f[i+1][j+1][0],f[i][j][0]);
                update(f[i+1][j+1][1],f[i][j][1]);
            }//0 1
            if(j>1)
            {
                update(f[i+1][j-1][0],f[i][j][0]);
                update(f[i+1][j-1][1],f[i][j][1]);
            }
            else update(f[i+1][j-1][1],f[i][j][0]+f[i][j][1]);
            //1 0
        }
    }
    int ans=0;
    fo(i,0,n) update(ans,f[m][i][1]);
    printf("%d",(ll)ans*4%mo);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值