y2823774827y
2018-12-17 19:24:09
当前点能走(非障碍必须走)
然后由
当然,可以严格证明出所有状态都能有上面的八种情况得到,因为只需要所有状态都在情况内
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const LL maxn=13;
const LL hs=299987;
LL n,m,ex,ey,now,last,ans;
LL a[maxn][maxn],head[300000],next[2<<24],que[2][2<<24],val[2][2<<24],cnt[2],inc[13];
inline void init(){
scanf("%lld%lld",&n,&m);
for(LL i=1;i<=n;++i){
char s[100];
scanf(" %s",s+1);
for(LL j=1;j<=m;++j){
if(s[j]=='.'){
a[i][j]=1;
ex=i; ey=j;
}
}
}
inc[0]=1;
for(LL i=1;i<=13;++i)
inc[i]=inc[i-1]<<2;
}
inline void insert(LL bit,LL num){
LL u=bit%hs+1;
for(LL i=head[u];i;i=next[i]){
if(que[now][i]==bit){
val[now][i]+=num;
return;
}
}
next[++cnt[now]]=head[u];
head[u]=cnt[now];
que[now][cnt[now]]=bit;
val[now][cnt[now]]=num;
}
inline void work(){
cnt[now]=1; val[now][1]=1; que[now][1]=0;
for(LL i=1;i<=n;++i){
for(LL j=1;j<=cnt[now];++j)
que[now][j]<<=2;
for(LL j=1;j<=m;++j){
memset(head,0,sizeof(head));
last=now; now^=1;
cnt[now]=0;
for(LL k=1;k<=cnt[last];++k){
LL bit=que[last][k],num=val[last][k];
LL b1=(bit>>((j-1)*2))%4,b2=(bit>>(j*2))%4;
if(!a[i][j]){
if(!b1&&!b2)
insert(bit,num);
}else if(!b1&&!b2){
if(a[i+1][j]&&a[i][j+1])
insert(bit+inc[j-1]+inc[j]*2,num);
}else if(!b1&&b2){
if(a[i][j+1])
insert(bit,num);
if(a[i+1][j])
insert(bit-inc[j]*b2+inc[j-1]*b2,num);
}else if(b1&&!b2){
if(a[i+1][j])
insert(bit,num);
if(a[i][j+1])
insert(bit-inc[j-1]*b1+inc[j]*b1,num);
}else if(b1==1&&b2==1){
LL k1=1;
for(LL l=j+1;l<=m;++l){
if((bit>>(l*2))%4==1)
++k1;
if((bit>>(l*2))%4==2)
--k1;
if(!k1){
insert(bit-inc[j]-inc[j-1]-inc[l],num);
break;
}
}
}else if(b1==2&&b2==2){
LL k1=1;
for(LL l=j-2;l>=0;--l){
if((bit>>(l*2))%4==1)
--k1;
if((bit>>(l*2))%4==2)
++k1;
if(!k1){
insert(bit-inc[j]*2-inc[j-1]*2+inc[l],num);
break;
}
}
}else if(b1==2&&b2==1){
insert(bit-inc[j-1]*2-inc[j],num);
}else if(i==ex&&j==ey){
ans+=num;
}
}
}
}
}
int main(){
init();
work();
printf("%lld",ans);
return 0;
}
来放松一下吧
其实对于刚学插头dp也较难完成,但建议读者先独立思考
题目大意
给出n*m的方格,有些格子不能铺线,其它格子必须铺,可以形成多个闭合回路。问有多少种铺法?n,m(2<=n,m<=12)
上一题的转化题,可以形成多个回路了,
怎么理解呢?上一题的括号匹配提过,再加上此题可以形成多条回路,所以实际情况肯定会有影响;至于状态转移,反正我们只需要把加方案数,自然无影响
此题不需要括号匹配,多条回路
ps:如果整个图都没有1,只有0,也算一种合法方案!
上一题放的是hash代码,为了让大家更熟悉,这里放个费空间的
#include<cstring>
#include<string>
#include<cstdio>
using namespace std;
typedef long long LL;
const LL maxn=13;
const LL hs=299987;
LL n,m,now,last,ans,kase,T,M,up;
LL a[maxn][maxn],inc[maxn],dp[maxn][maxn][1<<maxn];
inline LL read(){
LL x=0,f=1; char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=-1; c=getchar();
}
while(c>='0'&&c<='9'){
x=x*10+c-'0'; c=getchar();
}return x*f;
}
inline void solve(){
memset(dp,0,sizeof(dp));
dp[0][m][0]=1;
for(LL i=1;i<=n;++i){
for(LL j=0;j<=up;++j)
dp[i][0][j<<1]=dp[i-1][m][j];
for(LL j=1;j<=m;++j){
for(LL k=0;k<=up;++k){
LL bit=k,num=dp[i][j-1][k];
LL b1=(bit>>(j-1))&1,b2=(bit>>(j))&1;
if(!a[i][j]){
if(!b1&&!b2)
dp[i][j][bit]+=num;
}else if(!b1&&!b2)
dp[i][j][bit+inc[j-1]+inc[j]]+=num;
else if(!b1&&b2){
if(a[i][j+1])
dp[i][j][bit]+=num;
if(a[i+1][j])
dp[i][j][bit+inc[j-1]-inc[j]]+=num;
}else if(b1&&!b2){
if(a[i][j+1])
dp[i][j][bit-inc[j-1]+inc[j]]+=num;
if(a[i+1][j])
dp[i][j][bit]+=num;
}else
dp[i][j][bit-inc[j-1]-inc[j]]+=num;
}
}
}
//printf("Case %lld: There are %lld ways to eat the trees.\n",++kase,dp[n][m][0]);
printf("%lld\n",dp[n][m][0]);
}
int main(){
T=read();
inc[0]=1;
for(LL i=1;i<=13;++i)
inc[i]=inc[i-1]<<1;
while(T--){
n=read(); m=read();
up=(1<<(m+1))-1;
for(LL i=1;i<=n;++i)
for(LL j=1;j<=m;++j)
a[i][j]=read();
solve();
}
return 0;
}/*
*/
题目大意
n*n的带权网格,求某个联块的最大和 n<=9
这里不需要形成回路,而是经典的联通块题,上面的方法无用武之地了(判断联通),通常我们采取最小表示法
那为什么不考虑右插头呢?而要判断下插头呢?
因为下插头以后再也不会判断了,所以要考虑状态合理,右插头到下一行自然会考虑状态
右插头来自上一个转移状态,很可能形成新块
选
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const LL maxn=24;
const LL hs=299987;
LL n,ans=-2*1e9,now=0,last=1;
LL val[2][1<<maxn],que[2][1<<maxn],next[1<<maxn],head[300000],a[maxn][maxn],cnt[2],inc[maxn],s[maxn];
inline void insert(LL bit,LL num){
LL u=bit%hs+1;
for(LL i=head[u];i;i=next[i]){
if(que[now][i]==bit){
val[now][i]=max(val[now][i],num);
return;
}
}
next[++cnt[now]]=head[u];
head[u]=cnt[now];
que[now][cnt[now]]=bit;
val[now][cnt[now]]=num;
}
inline LL update(LL num){
LL belong[maxn];
memset(belong,0,sizeof(belong));
LL sum=0;
for(LL i=1;i<=n;++i)
if(s[i]){
if(belong[s[i]])
s[i]=belong[s[i]];
else
s[i]=belong[s[i]]=++sum;
}
LL bit=0;
for(LL i=1;i<=n;++i)
bit+=(s[i]<<inc[i-1]);
if(sum==1)
ans=max(ans,num);
return bit;
}
inline void get_bitset(LL bit){
for(LL i=1;i<=n;++i)
s[i]=(bit>>inc[i-1])%8;
}
inline void solve(){
insert(0,0);
for(LL i=1;i<=n;++i){
for(LL j=1;j<=n;++j){
swap(now,last);
cnt[now]=0;
memset(head,0,sizeof(head));
for(LL k=1;k<=cnt[last];++k){
get_bitset(que[last][k]);
LL b1=s[j-1],b2=s[j];
LL num=val[last][k];
LL sum=0;
s[j]=0;
for(LL l=1;l<=n;++l)
if(s[l]==b2)
++sum;
if(!b2 || sum)
insert(update(num),num);
num+=a[i][j];
get_bitset(que[last][k]);
if(!b1&&!b2)
s[j]=7;
else{
s[j]=max(b1,b2);
for(LL l=1;l<=n;++l)
if(s[l]&&s[l]==min(b1,b2))
s[l]=max(b1,b2);
}
insert(update(num),num);
}
}
}
}
int main(){
for(LL i=1;i<=10;++i)
inc[i]=i*3;
scanf("%lld",&n);
for(LL i=1;i<=n;++i)
for(LL j=1;j<=n;++j)
scanf("%lld",&a[i][j]);
solve();
printf("%lld",ans);
return 0;
}
题目大意
n*m的带权方格,求最大权回路 2<=n<=100,2<=m<=6
和模板题差不多,在
inline void insert(LL bit,LL num){
LL u=bit%hs+1;
for(LL i=head[u];i;i=next[i])
if(que[now][i]==bit){
val[now][i]=max(val[now][i],num);
return;
}
next[++cnt[now]]=head[u];
head[u]=cnt[now];
que[now][cnt[now]]=bit;
val[now][cnt[now]]=num;
}
以下这题由于斯坦纳树做法更简单,不作例题讲重点讲,稍微提怎么求方案
题目大意
n*m的带权网格,求联通k个方格的最小权联通块方案(求某个方案而非方案数) n<=10 m<=10 k<=10
跟
结束,将插头去掉,如果为终点更新答案,否则去掉插头则可
跟
做完一个题当然得总结一下:这题虽然与前面回路的题不同,看似无从下手,但从
这题深刻得体会到了插头dp的毒瘤
这里在联通路径情况下,增加了特殊限制,相当于每个点的权值由周围八连通块炮台数量决定
要求最后方案最小路径最大,而可以放无限个障碍,显然,为了简化状态,我们最后留一条路径就好了
这里一种状态由什么来表示?插头,路径状态,已放炮台数量
和原来好像完全不一样了,别担心,多开几个数组存状态,把所有的状态全部考虑一遍就好了嘛
以下借鉴了dalao@ComeIntoPower的思想与代码
重点:
这里的插头状态:
独立插头不知道意思?其实之前算是已经接触过了,记不记得,在求最大联通块权,有一个新块
由于起点终点只延伸一个方向,不参与括号匹配,所以单独标记为3
插头轮廓状态表示如下
棋盘轮廓状态:
LL Conn(LL st,LL op,LL u,LL v)
nxt(a) (stb-(yb<<bit(j+1))+((a)<<bit(j+1)))
滚动数组存三种小状态
本题状态较多,详情请看完整代码(内有注释),相信你能看懂
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define bit(x) ((x)<<1)
#define nxt(a) (stb-(yb<<bit(j+1))+((a)<<bit(j+1)))
using namespace std;
typedef long long LL;
const LL maxn=10000000;
const LL hs=299987;
LL n,m,now,last,K,ans;
LL cnt[2],val[2][maxn],que[2][maxn],nxt[maxn],stA[2][maxn],stB[2][maxn],stC[2][maxn],head[300000];
char a[30][30],tmp[30][30];
LL Hash(LL x,LL y,LL z){
return ((((x)<<16|y)<<4|z));
}
void Add(LL x,LL y,LL z,LL w){
LL u=Hash(x,y,z)%hs+1,h=Hash(x,y,z);
for(LL i=head[u];i;i=nxt[i])
if(que[now][i]==h){
val[now][i]=max(val[now][i],w);
return;
}
nxt[++cnt[now]]=head[u],
head[u]=cnt[now],
val[now][cnt[now]]=w,
que[now][cnt[now]]=h,
stA[now][cnt[now]]=x,
stB[now][cnt[now]]=y,
stC[now][cnt[now]]=z;
}
LL Conn(LL st,LL op,LL u,LL v){
if(op==1){
LL b=1;
for(;;++u){
LL p=(st>>bit(u))&3;
if(p==1)
b++;
if(p==2)
b--;
if(!b)
return st-(p<<bit(u))+(v<<bit(u));
}
} else {
LL b=1;
for(;;--u){
LL p=(st>>bit(u))&3;
if(p==2)
b++;
if(p==1)
b--;
if(!b)
return st-(p<<bit(u))+(v<<bit(u));
}
}
}
inline void Solve(){
now=0,last=1;
val[now][++cnt[now]]=0;
for(LL i=0;i<n;++i){
for(LL j=1;j<=cnt[now];++j)
stA[now][j]<<=2,
stB[now][j]=(stB[now][j]-(((stB[now][j]>>bit(m+1))&3)<<bit(m+1)))<<2;
for(LL j=0;j<m;++j){
swap(now,last);
memset(head,0,sizeof(head));
cnt[now]=0;
for(LL s=1;s<=cnt[last];++s){
LL sta=stA[last][s],
stb=stB[last][s],
k=stC[last][s],
w=val[last][s];
if(sta>=(1<<bit(m+1)))//上一行是否最后添加插头,添加则超出格限制,跳过
continue;
LL b1,b2,xb,yb,zb,pb;
b1=sta>>bit(j)&3,
b2=sta>>bit(j+1)&3;
xb=stb>>bit(j)&3,
yb=stb>>bit(j+1)&3;
zb=stb>>bit(j+2)&3,
pb=stb>>bit(j+3)&3;
LL nsta=sta-(b1<<bit(j))-(b2<<bit(j+1));
if((xb==1&&!b1)||(zb==1&&!b2)){//不走就拦
if(!b1&&!b2){
Add(sta,nxt(0),k,w);//障碍
w+=(xb==1)+(yb==1)+(zb==1)+(pb==1);
if(k<K&&a[i][j]=='.')
Add(sta,nxt(3),k+1,w);//炮台
}
}else if(a[i][j]=='#'){
if(!b1&&!b2)
Add(nsta,nxt(0),k,w);
}else if(a[i][j]=='.'){
LL w1=w+(xb==1)+(yb==1)+(zb==1)+(pb==1),
w2=w;
w+=(xb==3)+(yb==3)+(zb==3)+(pb==3);
if(!b1&&!b2){
if(k<K)
Add(nsta,nxt(3),k+1,w1);//记录对走过的路径造成的影响
Add(nsta+(1<<bit(j))+(2<<bit(j+1)),nxt(1),k,w);//加两个插头
Add(nsta,nxt(0),k,w2);//不走
}else if(!b1){// 延伸
Add(nsta+(b2<<bit(j)),nxt(1),k,w);
Add(nsta+(b2<<bit(j+1)),nxt(1),k,w);
}else if(!b2){//延伸
Add(nsta+(b1<<bit(j)),nxt(1),k,w);
Add(nsta+(b1<<bit(j+1)),nxt(1),k,w);
}else if(b1==2&&b2==1){//合并
Add(nsta,nxt(1),k,w);
}else if(b1==1&&b2==1){//找到右替换成左
Add(Conn(nsta,1,j,1),nxt(1),k,w);
}else if(b1==2&&b2==2){//找到左替换成右
Add(Conn(nsta,2,j,2),nxt(1),k,w);
}else if(b1==3&&b2==1){//找到右替换成独立
Add(Conn(nsta,1,j,3),nxt(1),k,w);
}else if(b1==3&&b2==2){//找到左替换成独立
Add(Conn(nsta,2,j,3),nxt(1),k,w);
}else if(b1==1&&b2==3){//找到右替换成独立
Add(Conn(nsta,1,j,3),nxt(1),k,w);
}else if(b1==2&&b2==3){//找到左替换成独立
Add(Conn(nsta,2,j,3),nxt(1),k,w);
}else if(b1==3&&b2==3){//合并
Add(nsta,nxt(1),k,w);
}
}else if(a[i][j]=='S'||a[i][j]=='T'){
w+=(xb==3)+(yb==3)+(zb==3)+(pb==3);
if(a[i][j]=='S'&&!b1&&!b2){//起点且无插头
Add(sta+(3<<bit(j)),nxt(1),k,w);
Add(sta+(3<<bit(j+1)),nxt(1),k,w);//延伸
}else if(a[i][j]=='T'&&!b1&&!b2){//终点且无插头
Add(sta+(3<<bit(j)),nxt(1),k,w);
Add(sta+(3<<bit(j+1)),nxt(1),k,w);//延伸
}else if(!b1&&b2!=3&&b2!=0){//有非独立插头改成独立插头
Add(Conn(nsta,b2,j,3),nxt(1),k,w);
}else if(!b2&&b1!=3&&b1!=0){//有非独立插头改成独立插头
Add(Conn(nsta,b1,j,3),nxt(1),k,w);
}else if(!b1&&b2==3){//两端已连接,不需要再添加
Add(nsta,nxt(1),k,w);
}else if(b1==3&&!b2){////两端已连接,不需要再添加
Add(nsta,nxt(1),k,w);
}
}
}
}
}
}
int main(){
scanf("%lld%lld%lld",&n,&m,&K);
for(LL i=0;i<n;++i)
scanf(" %s",tmp[i]);
if(n<m){
for(LL i=0;i<n;++i)
for(LL j=0;j<m;++j)
a[j][i]=tmp[i][j];
swap(n,m);
}else{
for(LL i=0;i<n;++i)
for(LL j=0;j<m;++j)
a[i][j]=tmp[i][j];
}
Solve();
for(LL i=1;i<=cnt[now];++i)
if(!stA[now][i])
ans=max(ans,val[now][i]);
printf("%lld\n",ans);
return 0;
}