jzoj5990. 【北大2019冬令营模拟2019.1.6】Bear (状压dp)

题面


题解

我永远讨厌dp.jpg

搞了一个下午优化复杂度最后发现只要有一个小trick就可以A了→_→。全场都插头dp就我一个状压跑得贼慢……

不难发现我们可以状压,对于每一行,用状态\(S\)表示有哪些格子是已经被上一行推倒了的,那么我们可以枚举本行所有格子的字母情况,然后计算一下这个时候下一行格子被推倒的情况,把这一行的贡献加到下一行就行了。

简单来说就是记一个\(f[pos][S]\)表示第\(pos\)行,格子被推倒的情况为\(S\)时的方案数,\(dp[pos][S]\)为所有方案中推倒树的总数,那么假设一个选字母的方案会使下一行的推倒情况为\(S'\),会使这一行可以推倒\(k\)棵树,则有转移\[f[pos+1][S']+=f[pos][S]\]
\[dp[pos+1][S']+=f[pos][S]+k\times f[pos][S]\]
最后\(f[n+1][0]\)就是答案。这样的话能有\(40\)分(建议先看一下40分代码不然看不太懂AC代码的……)

//minamoto
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
using namespace std;
const int N=13,M=35,L=(1<<21)+5;
int a[N][M],f[N][L],dp[N][L],g[N][L],n,m,P,lim,ans,vis[N];
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
void solve(int pos){
    fp(i,0,lim-1)if(f[pos][i]){
        fp(j,0,lim-1){
            int S=0,res=0;
            fp(k,0,m-1)vis[k]=i&(1<<k);
            fp(k,0,m-1)if(!vis[k]){
                if(j&(1<<k)){
                    if(k!=m-1&&!vis[k+1])vis[k]=vis[k+1]=1,++res;
                    else{
                        if(pos!=n)S|=(1<<k),++res;
                    }
                }else{
                    if(pos!=n)S|=(1<<k),++res;
                    else if(k!=m-1&&!vis[k+1])vis[k]=vis[k+1]=1,++res;
                }
            }
            f[pos+1][S]=add(f[pos+1][S],f[pos][i]);
            dp[pos+1][S]=add(dp[pos+1][S],mul(res,f[pos][i]));
            dp[pos+1][S]=add(dp[pos+1][S],dp[pos][i]);
        }
    }
}
int main(){
//  freopen("testdata.in","r",stdin);
    freopen("bear.in","r",stdin);
    freopen("bear.out","w",stdout);
    scanf("%d%d%d",&n,&m,&P),lim=(1<<m);
    f[1][0]=1,dp[1][0]=0;
    fp(i,1,n)solve(i);
    printf("%d\n",dp[n+1][0]);
}

然后我们发现复杂度高的主要原因是因为行数太多,不过列数很少,那么我们可以对列进行状压。然而这样的话会不符合推倒的顺序。

我们考虑每一条副对角线,这条副对角线上肯定是从右上到左下的推倒顺序,于是我们可以对每一条副对角线进行状压,因为副对角线上元素个数为\(min(n,m)\),所以时间复杂度没问题

信心满满的交上去结果只有\(70\)分,因为按上面那种方式枚举行的推倒情况和行的字母不太好,对于那些已经被推倒的格子,它们不管怎么选都没有影响,所以我们可以只枚举那些没有被推倒的格子,那些已经被推倒的格子直接把贡献加上去就可以了,这样的话复杂度就是\(O(3^n\times\)乱七八糟的常数\()\)

还是一句话,注意细节

//minamoto
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
using namespace std;
const int N=55,M=35,L=(1<<12)+5;
int a[N][M],f[N][L],dp[N][L],n,m,P,ans,vis[N];
int id[N][M],sz[L],bin[N];
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
void solve(int pos){
    int cnt=pos-max(0,pos-n)-max(0,pos-m);
    int stx,sty,edx,edy,dx,dy;
    if(pos<=m)stx=pos,sty=1;
        else stx=m,sty=pos-m+1;
    if(pos<=n)edx=1,edy=pos;
        else edx=pos-n+1,edy=n;
    int qaq=pos+1>m,c=pos+1-max(0,pos+1-n)-max(0,pos+1-m);
    int lim=(1<<cnt)-1;
    fp(i,0,(1<<cnt)-1)if(f[pos][i]){
        int T=lim^i,p=bin[sz[i]],flag=-2;
        for(R int j=T;flag+=(j==T);j=(j-1)&T){
            int res=0,S=0;
            fp(k,0,c-1)vis[k]=0;
            dx=stx,dy=sty;
            fp(k,0,cnt-1){
                if(!(i&(1<<k))){
                    if(j&(1<<k)){
                        if(dx!=m&&!vis[k-qaq])vis[k-qaq]=1,++res,S|=(1<<(k-qaq));
                        else if(dy!=n)vis[k+1-qaq]=1,++res,S|=(1<<(k-qaq+1));
                    }else{
                        if(dy!=n)vis[k+1-qaq]=1,++res,S|=(1<<(k-qaq+1));
                        else if(dx!=m&&!vis[k-qaq])vis[k-qaq]=1,++res,S|=(1<<(k-qaq));
                    }
                }--dx,++dy;
            }
            f[pos+1][S]=add(f[pos+1][S],mul(f[pos][i],p));
            dp[pos+1][S]=add(dp[pos+1][S],mul(mul(f[pos][i],res),p));
            dp[pos+1][S]=add(dp[pos+1][S],mul(dp[pos][i],p));
        }
    }
}
int main(){
//  freopen("testdata.in","r",stdin);
    freopen("bear.in","r",stdin);
    freopen("bear.out","w",stdout);
    scanf("%d%d%d",&n,&m,&P);
    fp(i,1,(1<<(min(n,m)))-1)sz[i]=sz[i>>1]+(i&1);
    bin[0]=1;fp(i,1,30)bin[i]=mul(bin[i-1],2);
    f[1][0]=1,dp[1][0]=0;
    fp(i,1,n+m-2)solve(i);
    printf("%d\n",mul(add(dp[n+m-1][0],dp[n+m-1][1]),2));
}

原文地址:https://www.cnblogs.com/bztMinamoto/p/10233864.html

时间: 2024-10-04 16:49:31

jzoj5990. 【北大2019冬令营模拟2019.1.6】Bear (状压dp)的相关文章

jzoj5991. 【北大2019冬令营模拟2019.1.6】Juice

题面 题解 好迷-- //minamoto #include<bits/stdc++.h> #define R register #define ll long long #define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i) #define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i) #define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)

【11.8校内测试】【倒计时2天】【状压DP】【随机化?/暴力小模拟】

Solution 数据范围疯狂暗示状压,可是一开始发现状态特别难受. 将每一层的奇偶性状压,预处理所有状态的奇偶性.每一层的输入代表的其实可以是下一层某个点可以被从这一层哪些点转移到. 所以枚举每个状态,再枚举下一层转移到哪个点,统计这个点被这个状态更新的话正边和反边分别的奇偶性,转移即可. 第二层和最后一层单独处理即可. Code #include<bits/stdc++.h> #define mod 998244353 using namespace std; int x, dp[2][(

UVA 1412 - Fund Management(用vector容器模拟状态的状压dp)

Frank is a portfolio manager of a closed-end fund for Advanced Commercial Markets (ACM ). Fund collects money (cash) from individual investors for a certain period of time and invests cash into various securities in accordance with fund's investment

POJ 4979 海贼王之伟大航路 【状压dp】【北大ACM/ICPC竞赛训练】

该死的题让我想起来艾斯之死... 首先想到dp(i)代表从1到[i表示的这些岛屿]所花的最小时间,然后每次枚举最后一个岛屿以此缩小范围,但发现枚举了最后一个岛屿后没有办法转移,因为不知道倒数第二个岛屿是什么,随着倒数第二个岛屿的不同,时间的增加也会不同,也就是不具备[无后效性]. 因此想到再加一个参数去约束当前问题的状态,dp(i,j)代表1到[i代表的这些点]所需的最少时间,且这趟旅程的最后一个岛屿是j,这样就可转移了,每次枚举倒数第二岛屿:答案就是dp( (1<<n) -1,n ) 具体的

[CSP-S模拟测试]:巨神兵(状压DP)

题目描述 欧贝利斯克的巨神兵很喜欢有向图,有一天他找到了一张$n$个点$m$条边的有向图.欧贝利斯克认为一个没有环的有向图是优美的,请问这张图有多少个子图(即选定一个边集)是优美的?答案对$1,000,000,007$取模. 输入格式 第一行两个整数$n$和$m$.接下来$m$行每行两个整数表示一条有向边.保证无重边无自环. 输出格式 一行一个整数表示答案,对$1,000,000,007$取模. 样例 样例输入: 3 61 22 11 33 12 33 2 样例输出: 25 数据范围与提示 对于

2018冬令营模拟测试赛(三)

2018冬令营模拟测试赛(三) [Problem A]摧毁图状树 试题描述 输入 见"试题描述" 输出 见"试题描述" 输入示例 见"试题描述" 输出示例 见"试题描述" 数据规模及约定 见"试题描述" 题解 这题没想到贪心 QwQ,那就没戏了-- 贪心就是每次选择一个最深的且没有被覆盖的点向上覆盖 \(k\) 层,因为这个"最深的没有被覆盖的点"不可能再有其它点引出的链覆盖它了,而它又

2018冬令营模拟测试赛(五)

2018冬令营模拟测试赛(五) [Problem A][UOJ#154]列队 试题描述 picks 博士通过实验成功地得到了排列 \(A\),并根据这个回到了正确的过去.他在金星凌日之前顺利地与丘比签订了契约,成为了一名马猴烧酒. picks 博士可以使用魔法召唤很多很多的猴子与他一起战斗,但是当猴子的数目 \(n\) 太大的时候,训练猴子就变成了一个繁重的任务. 历经千辛万苦,猴子们终于学会了按照顺序排成一排.为了进一步训练,picks 博士打算设定一系列的指令,每一条指令 \(i\) 的效果

2018冬令营模拟测试赛(十七)

2018冬令营模拟测试赛(十七) [Problem A]Tree 试题描述 输入 见"试题描述" 输出 见"试题描述" 输入示例 见"试题描述" 输出示例 见"试题描述" 数据规模及约定 见"试题描述" 题解 这个数据范围肯定是树上背包了. 令 \(f(i, j, k)\) 表示子树 \(i\) 中选择了 \(j\) 个节点,路径与根的连接情况为 \(k\),具体地: \(k = 0\) 时,路径的两个端点

2018冬令营模拟测试赛(十九)

2018冬令营模拟测试赛(十九) [Problem A]小Y 试题描述 输入 见"试题描述" 输出 见"试题描述" 输入示例 见"试题描述" 输出示例 见"试题描述" 数据规模及约定 见"试题描述" 题解 目前未知. 这题目前就能做到 \(O(n \sqrt{M} \log n)\),其中 \(M\) 是逆序对数,然而会被卡 \(T\):当然这题暴力可以拿到和左边那个算法一样的分数,只要暴力加一个剪枝:当左