论文部分
基于连通性的状态压缩方法
1. 最小表示法压缩联通块
可以把联通块从左到右最小标号。也可以按照同一联通块中的最小下标标号
2. 对于路径用括号序表示
因为路径不可能相交,所以这样表示可以大大减少状态
如果不是回路,而是路径,记录独立插头,但独立插头最多只有两个
3. 只对插头状压连通性
其他部分因为已经联通,不用考虑
4. 最后用hash来存状态
直接在插入状态的时候更新DP值。
用位运算简化代码和加速。
不用写繁琐的encode和extract函数
void ins(int zt,ll num){ //直接在hash的过程中更新答案
int tmp = zt % Has + 1;
for (int i = h[tmp] ; i ; i = ne[i])
if ( a[now][i] == zt ) { f[now][i] += num; return; }
ne[++tot[now]] = h[tmp] , h[tmp] = tot[now]; //在链表前面添加
a[now][tot[now]] = zt , f[now][tot[now]] = num;
}
例题
注意:
转移一定要推清楚!不能有半点马虎!
插头DP的难点在于不知道该怎么调。至少无法输出调试。只能肉眼查错
必须尽量一次AC
1. Ural 1519 Formula 1
论文题
括号序表示插头连通性
如果可以用多个回路覆盖?
那么不需要记录连通性,直接用插头转移
一个细节错误:找括号匹配的时候忽略了没有插头的情况,调了好久,1个多小时!
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
typedef long long ll;
typedef long double ld;
typedef int ull;
typedef pair<int,int> pr;
const ld inf = 2e18;
const int N = 3e6 + 10;
const int maxn = 1000020;
const ll mod = 1e9 + 7;
const int LIM=300005,Has=299989;
int h[300005],a[2][LIM],ne[LIM],now,las,bin[30],mul[30],ex,ey,tot[2];
ll f[2][LIM],ans;
char ch[20][20];
int n,m;
void init(){
bin[1] = 1;
for (int i = 2 ; i <= 13 ; i++) bin[i] = bin[i - 1] * 4;
for (int i = 1 ; i <= 13 ; i++) mul[i] = i * 2;
for (int i = 1 ; i <= n ; i++){
for (int j = 1 ; j <= m ; j++){
if ( ch[i][j] == '.' ){
ex = i , ey = j;
}
}
}
}
void ins(int zt,ll num){ //直接在hash的过程中更新答案
int tmp = zt % Has + 1;
for (int i = h[tmp] ; i ; i = ne[i])
if ( a[now][i] == zt ) { f[now][i] += num; return; }
ne[++tot[now]] = h[tmp] , h[tmp] = tot[now]; //在链表前面添加
a[now][tot[now]] = zt , f[now][tot[now]] = num;
}
inline int getS(int v,int id){
return (v >> mul[id - 1]) & 3;
}
void clear(int t){
memset(h,0,sizeof(h)) , memset(ne,0,sizeof(ne));
rep(i,0,tot[t]) a[t][i] = 0 ,f[t][i] = 0;
tot[t] = 0;
}
void solve(){
ins(0,1);
rep(i,1,n){
rep(j,1,m){
las = now , now ^= 1;
clear(now);
//cout<<"ans "<<i<<" "<<j<<" num "<<endl;
rep(k,1,tot[las]){
int v = a[las][k]; ll num = f[las][k];
if ( !num ) continue;
if ( j == 1 ){
if ( getS(v,m + 1) ) continue;
v <<= 2;
}
// cout<<"from S \n";
//rep(l,1,m + 1) cout<<getS(v,l)<<" ";
//cout<<endl;
// cout<<" "<<k<<" "<<v<<" "<<num<<endl;
// cout<<" "<<k<<" "<<v<<" "<<num<<endl;
int b1 = getS(v,j) , b2 = getS(v,j + 1) , tag = ch[i][j] == '.';//b1 : 左插头状态 , b2 : 下插头状态
int tmpv = v - b1 * bin[j] - b2 * bin[j + 1];
if ( !tag ){
if ( !b1 && !b2 ) ins(v,num);
continue;
}
if ( !b1 && !b2 ){
ins(tmpv + bin[j] + bin[j + 1] * 2,num);
}
else if ( !b1 && b2 ){
ins(v,num);
// ins(tmpv + bin[j],num);
ins(tmpv + bin[j] * b2,num);
}
else if ( b1 && !b2 ){
ins(v,num);
ins(tmpv + bin[j + 1] * b1,num);
}
else if ( b1 == b2 ){ //括号类型相同,合并当前括号,并找到匹配的括号,翻转
if ( b1 == 1 ){
int id = 0 , sum = 1;
rep(l,j + 2,m + 1){
int c = getS(v,l);
if ( c == 2 ) sum--;
else if ( c == 1 ) sum++; //注意没有插头的情况!
if ( !sum ){
id = l; break;
}
}
assert(id);
ins(tmpv - bin[id],num); //右括号变成左括号
}
else{
int id = 0 , sum = 1;
repd(l,j - 1,1){
int c = getS(v,l);
if ( c == 1 ) sum--;
else if ( c == 2 ) sum++;
if ( !sum ){
id = l; break;
}
}
assert(id);
ins(tmpv + bin[id],num);
}
}
else if ( b1 != b2 ){
if ( b1 == 2 ) ins(tmpv,num); //直接合并括号
else if ( !tmpv && b1 == 1 && i == ex && j == ey ){
// cout<<num<<endl;
ans += num; //最后一个位置更新答案,不能有其他插头
}
}
}
}
}
cout<<ans<<endl;
}
int main(){
freopen("input.txt","r",stdin);
scanf("%d %d",&n,&m);
rep(i,1,n) scanf("%s",ch[i] + 1);
init();
solve();
}
2. 七月集训 by HbFs
3. GDKOI2016 Map 地图
题目大意:有一张n×m n \times mn×m的地图,有些地方是平地’.’,有些是障碍’#’,有些是未知的’?’,还有入口’S’,出口’E’和神器’X’。
合法的地图只有一个入口一个出口一个神器,并且入口,出口和神器两两连通。
现在要确定未知格子’?'是什么,问有多少种确定未知格子的方案使得地图合法?保证至少存在一种方案。
1 ≤ n ≤ 7 , 1 ≤ m ≤ 7 1 \leq n \leq 7,1 \leq m \leq7 1≤n≤7,1≤m≤7
这道题只要求联通,就需要最小表示法记录路径。可以看前面litble的博客
4. Black & White
这题主要是优化状态
后面的题都还没有写,一定要抽时间写,才能掌握!
5. 2331: [SCOI2011]地板
题意:用L形方块铺地板
题解:
插头记录一下是否需要拐弯
注意保留插头,但是标号会改变!
还有转移的时候插头种类也要保留
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
typedef long long ll;
typedef long double ld;
typedef int ull;
typedef pair<int,int> pr;
const ld inf = 2e18;
const int N = 3e6 + 10;
const int maxn = 1000020;
const ll mod = 20110520;
const int LIM=300005,Has=299989;
int h[300005],a[2][LIM],ne[LIM],now,las,bin[30],mul[30],ex,ey,tot[2];
ll f[2][LIM],ans;
char ch[120][120],ch2[120][120];
int n,m;
void init(){
bin[1] = 1;
for (int i = 2 ; i <= 13 ; i++) bin[i] = bin[i - 1] * 4;
for (int i = 1 ; i <= 13 ; i++) mul[i] = i * 2;
if ( n < m ){
swap(n,m);
rep(i,1,n) rep(j,1,m) ch2[i][j] = ch[j][i];
memcpy(ch,ch2,sizeof(ch));
}
for (int i = 1 ; i <= n ; i++){
for (int j = 1 ; j <= m ; j++){
if ( ch[i][j] == '_' ){
ex = i , ey = j;
}
}
}
}
inline void up(ll &x,ll y){ x = (x + y) % mod; }
inline int getS(int v,int id){
return (v >> mul[id - 1]) & 3;
}
void ins(int zt,ll num){ //直接在hash的过程中更新答案
//cout<<"cur S \n";
//rep(i,1,m + 1) cout<<getS(zt,i)<<" ";
//cout<<endl;
int tmp = zt % Has + 1;
for (int i = h[tmp] ; i ; i = ne[i])
if ( a[now][i] == zt ) { up(f[now][i],num); return; }
ne[++tot[now]] = h[tmp] , h[tmp] = tot[now]; //在链表前面添加
a[now][tot[now]] = zt , f[now][tot[now]] = num;
}
void clear(int t){
memset(h,0,sizeof(h)) , memset(ne,0,sizeof(ne));
rep(i,0,tot[t]) a[t][i] = 0 ,f[t][i] = 0;
tot[t] = 0;
}
void solve(){
ins(0,1);
rep(i,1,n){
rep(j,1,m){
las = now , now ^= 1;
clear(now);
// cout<<"ans "<<i<<" "<<j<<endl;
rep(k,1,tot[las]){
int v = a[las][k]; ll num = f[las][k];
if ( !num ) continue;
if ( j == 1 ){
if ( getS(v,m + 1) ) continue;
v <<= 2;
}
// cout<<"from S \n";
//rep(l,1,m + 1) cout<<getS(v,l2 2)<<" ";
//cout<<endl;
// cout<<" "<<k<<" "<<v<<" "<<num<<endl;
int b1 = getS(v,j) , b2 = getS(v,j + 1) , tag = ch[i][j] == '_';//b1 : 左插头状态 , b2 : 下插头状态
int tmpv = v - b1 * bin[j] - b2 * bin[j + 1];
if ( !tag ){
if ( !b1 && !b2 ) ins(v,num);
continue;
}
if ( !b1 && !b2 ){
ins(tmpv + bin[j] + bin[j + 1],num);
ins(tmpv + bin[j + 1] * 2,num);
ins(tmpv + bin[j] * 2,num);
}
else if ( !b1 && b2 ){
//ins(v,num);
ins(tmpv + bin[j] * b2,num); //插头保留但是标号变化!
//关闭插头或者拐弯
if ( b2 == 1 ) ins(tmpv,num);
else ins(tmpv + bin[j + 1],num);
}
else if ( b1 && !b2 ){
//ins(v,num);
ins(tmpv + bin[j + 1] * b1,num); //插头保留但是标号变化!
if ( b1 == 1 ) ins(tmpv,num);
else ins(tmpv + bin[j],num);
}
else if ( b1 == b2 && b2 == 2 ){
ins(tmpv,num);
}
}
}
}
rep(k,1,tot[now]) if ( !a[now][k] ) up(ans,f[now][k]);
cout<<ans<<endl;
}
int main(){
//freopen("input.txt","r",stdin);
scanf("%d %d",&n,&m);
rep(i,1,n) scanf("%s",ch[i] + 1);
init();
solve();
}