插头dp
感受:
我觉得重点是理解,算法并不是直接想出怎样由一种方案变成另一种方案。而是方案本来就在那里,我们只是枚举状态统计了答案。
看看cdq的讲义什么的,一开始可能觉得状态很多,但其实灰常简单
就像lyd说的,考插头dp的题目就是在考模板2333
(学这个之前连hash_map都没写过2333
WA:
(1) 初始化矩阵,周围格子有可能是0--->转移出错
(2)统计答案最后统计的是合法的,即st==0的。。。
题目集锦:
(1)cojs1512 经过所有可经过的点的一条回路个数
因为是一条回路,依次dp每个点的状态,所以记录endx,endy只在终点更新答案,其它点的闭合回路不计算。
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
#define mod 13131
#define N 4500
#define ll long long
struct dp_hash{
int head[mod],next[N],sz;
ll f[N],st[N];
void init(){
memset(head,0,sizeof(head));sz=0;
}
void push(ll S,ll ins){
int now=S%mod;
for(int i=head[now];i;i=next[i])
if(st[i]==S){
f[i]+=ins;return;
}
sz++;
f[sz]=ins;st[sz]=S;
next[sz]=head[now];
head[now]=sz;
}
}dp[2];
ll ans=0;
int n,m,code[16],ch[16],a[16][16],cur,Endx,Endy;
char s[15];
void Decode(ll S){
for(int i=m;i>=0;i--){
code[i]=S&7;
S>>=3;
}
}
ll Encode(){
ll S=0;
memset(ch,-1,sizeof(ch));int cnt=0;
ch[0]=0;
for(int i=0;i<=m;i++){
if(ch[code[i]]==-1)ch[code[i]]=++cnt;
code[i]=ch[code[i]];
S<<=3;S|=code[i];
}
return S;
}
void Shift(){
for(int i=m;i>0;i--)code[i]=code[i-1];
code[0]=0;
}
void DP(int x,int y,int pre,bool type){
if(!type){
for(int k=1;k<=dp[pre].sz;k++){
Decode(dp[pre].st[k]);
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[k]);
}
return;
}
for(int k=1;k<=dp[pre].sz;k++){
Decode(dp[pre].st[k]); int Left=code[y-1],Up=code[y];
if(Left&&Up){
if(Left==Up){
if(Endx==x&&Endy==y)
ans+=dp[cur].f[k];
}
else{
code[y]=code[y-1]=0;
for(int i=0;i<=m;i++)
if(code[i]==Up)code[i]=Left;
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[k]);
}
}
else if(Left==0&&Up==0){
if(a[x][y+1]&&a[x+1][y]){
code[y-1]=code[y]=15;
dp[pre^1].push(Encode(),dp[pre].f[k]);
}
}
else{
int tmp=Left==0?Up:Left;
if(a[x][y+1]){
code[y-1]=0;code[y]=tmp;
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[k]);
}
if(a[x+1][y]){
code[y-1]=tmp;code[y]=0;
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[k]);
}
}
}
}
int main(){
freopen("formula1.in","r",stdin);
freopen("formula1.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++)
if(s[j]!='*'){
a[i][j]=1;
Endx=i;Endy=j;
}
} if(Endx==0){
puts("0");return 0;
}
cur=0;
dp[cur].init();
dp[cur].push(0,1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
dp[cur^1].init();
DP(i,j,cur,a[i][j]);
cur^=1;
}
printf("%lld\n",ans);
return 0;
}
(2) hdu1693 Eat The Trees
经过所有非障碍点的回路个数(不限条数)。
和上一道题的区别就是非终点的回路也要更新其它状态。
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
#define N 1000000
#define mod 13131
#define ll long long
struct dphash{
int head[N],next[N],sz;
ll st[N],f[N];
void init(){
memset(head,0,sizeof(head));sz=0;
}
void push(ll S,ll ins){
int now=S%mod;
for(int i=head[now];i;i=next[i])
if(st[i]==S){
f[i]+=ins;return;
}
sz++;
st[sz]=S;f[sz]=ins;
next[sz]=head[now];head[now]=sz;
}
}dp[2];
int n,m,T,cur,pw[16],a[16][16],code[16];
ll ans;
ll Encode(){
ll S=0;
memset(pw,-1,sizeof(pw));
pw[0]=0;int cnt=0;
for(int i=0;i<=m;i++){
if(pw[code[i]]==-1)pw[code[i]]=++cnt;
code[i]=pw[code[i]];
S<<=3;S|=code[i];
}
return S;
}
void Decode(ll S){
for(int i=m;i>=0;i--){
code[i]=S&7;
S>>=3;
}
}
void Shift(){
for(int i=m;i;i--)code[i]=code[i-1];
code[0]=0;
}
void DP(int x,int y,int pre,bool type){
if(!type){
for(int i=1;i<=dp[pre].sz;i++){
Decode(dp[pre].st[i]);
// cout<<"code "<<code[y]<<' '<<code[y-1]<<endl;
code[y]=code[y-1]=0;
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
return;
}
for(int i=1;i<=dp[pre].sz;i++){
Decode(dp[pre].st[i]); int Left=code[y-1],Up=code[y];
if(Left&&Up){
code[y]=code[y-1]=0;
for(int j=0;j<=m;j++)if(code[j]==Up)code[j]=Left;
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
else if(Left==0&&Up==0){
if(a[x][y+1]&&a[x+1][y]){
code[y-1]=code[y]=15;
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
}
else{
int tmp=Left==0?Up:Left;
if(a[x][y+1]){
code[y]=tmp;code[y-1]=0;
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
if(a[x+1][y]){
code[y-1]=tmp;code[y]=0;
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
}
}
}
int main(){
// freopen("1693.in","r",stdin);
// freopen("1693.out","w",stdout);
T=read();
for(int k=1;k<=T;k++){
memset(a,0,sizeof(a));
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)a[i][j]=read();
cur=0;
dp[cur].init();
dp[cur].push(0,1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
dp[cur^1].init();
DP(i,j,cur,a[i][j]);
cur^=1;
}
ans=0;
for(int i=dp[cur].head[0];i;i=dp[cur].next[i])if(dp[cur].st[i]==0)ans+=dp[cur].f[i];
printf("Case %d: There are %I64d ways to eat the trees.\n",k,ans);
}
return 0;
}
(3)[国家集训队2011]画圈圈
根据射线法,判断一个点左边的下插头奇偶性判断是否在回路内。
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
#define ll long long
#define N 1000000
#define mod 13131
#define MOD 123456791
struct dp_hash{
int head[mod],next[N],sz;
ll f[N],st[N];
void init(){
memset(head,0,sizeof(head));sz=0;
}
void push(ll S,ll ins){
ins%=MOD;int now=S%mod;
for(int i=head[now];i;i=next[i])
if(st[i]==S){
(f[i]+=ins)%=MOD;return;
}
sz++;next[sz]=head[now];head[now]=sz;
st[sz]=S;f[sz]=ins;
}
}dp[2];
int n,m,cur,pw[16],a[25][16],code[16];
ll ans;
char s[16];
void Decode(ll S){
for(int i=m;i>=0;i--){
code[i]=S&7;
S>>=3;
}
}
ll Encode(){
ll S=0;
memset(pw,-1,sizeof(pw));
pw[0]=0;int cnt=0;
for(int i=0;i<=m;i++){
if(pw[code[i]]==-1)pw[code[i]]=++cnt;
code[i]=pw[code[i]];
S<<=3;S|=code[i];
}
return S;
}
void Shift(){
for(int i=m;i;i--)code[i]=code[i-1];
code[0]=0;
}
void DP(int x,int y,int pre,int type){
if(type){
for(int i=1;i<=dp[pre].sz;i++){
Decode(dp[pre].st[i]);int t=0;
for(int j=0;j<y-1;j++)if(code[j]!=0)t++;
if((t%2==1&&type==1)||(t%2==0&&type==2)){
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
}
return;
} for(int i=1;i<=dp[pre].sz;i++){
Decode(dp[pre].st[i]); int Left=code[y-1],Up=code[y];
if(Left&&Up){
code[y]=code[y-1]=0;
for(int j=0;j<=m;j++)if(code[j]==Up)code[j]=Left;
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
else if(Left==0&&Up==0){
if(a[x][y+1]==0&&a[x+1][y]==0){
code[y-1]=code[y]=15;
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
}
else{
int tmp=Left==0?Up:Left;
if(a[x][y+1]==0){
code[y]=tmp;code[y-1]=0;
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
if(a[x+1][y]==0){
code[y-1]=tmp;code[y]=0;
if(y==m)Shift();
dp[pre^1].push(Encode(),dp[pre].f[i]);
}
}
}
}
int main(){
freopen("nt2011_circle.in","r",stdin);
freopen("nt2011_circle.out","w",stdout);
n=read();m=read();
memset(a,-1,sizeof(a));
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++)
if(s[j]=='.')a[i][j]=0;
else if(s[j]=='*')a[i][j]=1;
else a[i][j]=2;
}
cur=0;
dp[cur].init();
dp[cur].push(0,1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
dp[cur^1].init();
DP(i,j,cur,a[i][j]);
cur^=1;
}
for(int i=dp[cur].head[0];i;i=dp[cur].next[i])if(dp[cur].st[i]==0){ans=dp[cur].f[i];break;}
printf("%lld\n",ans);
}
(4)BZOJ1187
都是把记录方案个数的变量改为记录当前状态得到的权值
注意的是每个状态现在要记录的是最大值,就是说一个状态可以对应多种可能,你只有一个轮廓线,但轮廓线上方是怎么样的无法确定,所以要取ma]x
还有就是更新答案的时候,要code[y]=code[y-1]=0,然后判断Encode()==0,否则的话有可能两边还有插头,这样下面还会更新一次。
边界什么的也比较麻烦,容易写漏。
(5) BZOJ3753
这道我感觉超难的qaq,注意dp的是矩阵的边,但权值还是格子。。然后就会发现不会转移,奥妙重重。。
smz原来教过我,然而我忘了QAQ
然后她现在人在衡水QAQ不上QQ QAQ
问了大爷。。还没回复QAQ
还没写QAQ
update 前几天终于写了,这个其实可以理解为是否放置了下插头
首先就是n++,m++,然后选择一条边(即新图的一个格子),如果它有下插头,才能对当前格子是否在圈内产生影响。
贴代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=10*x+ch-'0';ch=getchar();}
return x*f;
}
#define mod 13131
#define N 1000000
#define inf 1e9
int n,m,code[20],a[15][15],v[15][15],num=0,ans=-inf;
struct hash_map{
int head[mod],next[N],sz,f[N];
ll st[N];
void init(){
memset(head,0,sizeof(head));sz=0;
}
void push(ll S,int ins){
int now=S%mod;
for(int i=head[now];i;i=next[i])
if(st[i]==S){
f[i]=max(f[i],ins);return;
}
st[++sz]=S;f[sz]=ins;
next[sz]=head[now];head[now]=sz;
}
}dp[2];
int pw[20];
ll Encode(){
memset(pw,-1,sizeof(pw));
pw[0]=0;ll S=0;int cnt=0;
for(int i=0;i<=m;i++){
if(pw[code[i]]==-1)pw[code[i]]=++cnt;
code[i]=pw[code[i]];
S=S<<3|code[i];
}
return S;
}
void Decode(ll S){
for(int i=m;i>=0;i--)
code[i]=S&7,S>>=3;
}
void Shift(){
for(int i=m;i;i--)code[i]=code[i-1];
code[0]=0;
}
bool check(int x,int y,bool is){
if(a[x][y]==1&&is)return 0;
if(a[x][y]==2&&is==0)return 0;
return 1;
}
void DP(int x,int y,int pre){
for(int i=1;i<=dp[pre].sz;i++){
Decode(dp[pre].st[i]);
if(y==1){if(code[m])continue;Shift();}
int Left=code[y-1],Up=code[y];
bool is=0;
for(int j=0;j<y-1;j++)if(code[j])is^=1;
if(Left&&Up){
code[y]=code[y-1]=0;
if(Left==Up){
if(Encode()==0&&num==0)ans=max(ans,dp[pre].f[i]);
}
else{
if(check(x,y,is)){
for(int j=0;j<=m;j++)if(code[j]==Up)code[j]=Left;
dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]);
}
}
}
else if(Left||Up){
int tmp=Left?Left:Up;
if(y!=m&&check(x,y,is)){
code[y-1]=0;code[y]=tmp;
dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]);
}
is^=1;
if(x!=n&&check(x,y,is)){
code[y-1]=tmp;code[y]=0;
dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]);
}
}
else{
if(check(x,y,is))dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]);
is^=1;
if(x!=n&&y!=m&&check(x,y,is)){
code[y]=code[y-1]=15;
dp[pre^1].push(Encode(),dp[pre].f[i]+is*v[x][y]);
}
}
}
}
int main(){
// freopen("test.in","r",stdin);
n=read();m=read();n++;m++;
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)v[i][j]=read();
int cur=0;dp[cur].init();dp[cur].push(0,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
dp[cur^1].init();
DP(i,j,cur);
cur^=1;
}
printf("%d\n",ans);
for(int i=1;i<n;i++)
for(int j=1;j<m;j++){
a[i][j]=read();
if(a[i][j]==2)num++;
}
cur=0;ans=-inf;
dp[cur].init();dp[cur].push(0,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
dp[cur^1].init();
DP(i,j,cur);
cur^=1;
if(a[i][j]==2)num--;
}
if(ans==-inf)printf("Can not establish GFW.");
else printf("%d\n",ans);
return 0;
}