轮廓线dp
dp
先来看一道题:
「九省联考 2018」一双木棋
考虑将已经选的和没有选的分界线压入一个状态,我们定义 \(1\) 是往上走的,\(0\) 是往右边走的,然后转移就是 \(01 \to 10\),直接记忆化搜索就行了。
点击查看代码
//これも運命じゃないか
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define double long double
#define Air
namespace io{
inline int read(){
int f = 1, t = 0; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
while(ch >= '0' && ch <= '9'){t = t * 10 + ch - '0'; ch = getchar();}
return t * f;
}
inline void write(int x){
if(x < 0){putchar('-'); x = -x;}
if(x >= 10){write(x / 10);}
putchar(x % 10 + '0');
}
}
using namespace io;
int n, m;
const int N = 20;
int a[N][N], b[N][N];
int dp[(1ll << 20) + 10];
bool vis[(1ll << 20) + 10];
int dfs(int now, bool st){
if(vis[now] == 1) {
return dp[now];
}
vis[now] = 1;
// cerr << now << ' ' << st << '\n';
if(st){
dp[now] = -1e18;
}
else{
dp[now] = 1e18;
}
int nx = n + 1, ny = 1;
for(int i = 0; i < n + m - 1; i++){
if(now & (1ll << i)){
nx --;
}
else{
ny ++;
}
if(((now >> i) & 3) != 1) continue;
int nxt = (now ^ (3ll << i));
if(st){
dp[now] = max(dp[now], dfs(nxt, (st ^ 1)) + a[nx][ny]);
}
else{
dp[now] = min(dp[now], dfs(nxt, (st ^ 1)) - b[nx][ny]);
}
}
// cerr << now << ' ' << dp[now] << '\n';
return dp[now];
}
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
n = read();
m = read();
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
a[i][j] = read();
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
b[i][j] = read();
}
}
dp[((1ll << n) - 1) << m] = 0;
vis[((1ll << n) - 1) << m] = 1;
cout << dfs((1ll << n) - 1, 1);
return 0;
}
这个题启示了什么呢,如果复杂度可以状压已经决策过的点与还没有决策过的点的分界线就可以尝试把分界线状压进去。
那这就是所谓的 轮廓线dp。
【模板】插头 DP
现在考虑下面一个问题就是有一个网格图,可能有障碍,你现在要在网格上铺线路,求连成一个哈密顿回路的方案数,网格长宽小于等于 \(12\)。
我们注意到问题关心非障碍点的连通性,所以我们可以在轮廓线上状压连通性。
在这里我们称与轮廓线直接相接的部分称为插头。
最小表示法
直接给每个插头所属的连通块表上不同的连通块标上不同的号,然后直接状压加哈希,状态总数不多,好像就能过。
括号表示法
这种方法可能适用网格图,并且一个连通块最多只有两个插头,我们把左边那个插头记作 \((\),右边那个插头记作 \()\),然后 \(4\) 进制状压之,也是对的。
然后考虑转移,我们可以考虑逐格转移,每次转移拿出与目前格子有关的两个插头 \(p1, p2\),然后分类讨论合并之就行了,细节就是合法点数不多,直接用 vector 来存加滚动数组。
点击查看代码
//これも運命じゃないか
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define double long double
#define Air
namespace io {
class In {
public:
template<typename T>
inline In &operator>>(T &x) {
x=0; bool f=0; char c=getchar();
while(c<'0'||c>'9') f|=(c=='-'),c=getchar();
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
if(c=='.') {
c=getchar(); double dot=0.1;
while(c>='0'&&c<='9') x+=(c-'0')*dot,dot*=0.1,c=getchar();
} return (f?x=-x:x),*this;
}
inline In &operator>>(char &x) {while(isspace(x=getchar())); return *this;}
inline In &operator>>(char *x) {
char c=getchar(); while(isspace(c)) c=getchar(); while(!isspace(c)&&~c) *(x++)=c,c=getchar();
return *x=0,*this;
}
inline In &operator>>(string &x) {
char c=getchar(); x.clear();
while(isspace(c)) c=getchar(); while(!isspace(c)&&~c) x.push_back(c),c=getchar();
return *this;
}
inline In &operator>>(In &in) { return in;}
};
class Out {
private:
char buf[35]; short dot=6,top=0;
public:
template<typename T>
inline Out &operator<<(T x) {
if(x<0) putchar('-'),x=-x;
do { buf[++top]=x%10,x/=10;} while(x);
while(top) putchar(buf[top--]|'0'); return *this;
}
inline Out &operator<<(char c) {return putchar(c),*this;}
inline Out &operator<<(string x) {for(auto c:x) putchar(c); return *this;}
inline Out &operator<<(char *x) {while(*x) putchar(*(x++)); return *this;}
inline Out &operator<<(const char *x) {while(*x) putchar(*(x++)); return *this;}
inline Out &operator<<(double x) {snprintf(buf,sizeof(buf),"%.*lf",dot,x); return (*this)<<buf;}
inline Out &operator<<(Out &out) {return out;}
inline Out &setdot(const int n) {return dot=n,*this;}
};
In fin;
Out fout;
inline Out &setdot(const int n,Out& out=fout) {return fout.setdot(n),out;}
inline In &getline(char *x,In& in=fin) {
char c=getchar();
while(!(c==' '||!isspace(c))) c=getchar(); while(c==' '||!isspace(c)) (*x++)=c,c=getchar();
return *x=0,in;
}
inline In &getline(string &x,In& in=fin) {
char c=getchar(); x.clear();
while(!(c==' '||!isspace(c))) c=getchar(); while(c==' '||!isspace(c)) x.push_back(c),c=getchar();
return in;
}
}
using namespace io;
inline int read(){
int x; fin >> x; return x;
}
int n, m;
const int N = 20, M = 5e5 + 10;
char ch[N][N];
int a[N][N];
int tx, ty;
int t;
int ans;
int bin[N];
int dp[2][M];
vector<int>nst[2];
unordered_map<int, int>mp[2];
void add(int p, int x, int val){
// cerr << x << '\n';
if(!mp[p][x]){
nst[p].push_back(x);
mp[p][x] = nst[p].size();
}
dp[p][mp[p][x]] += val;
}
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
n = read();
m = read();
for(int i = 1; i <= n; i++){
fin >> (ch[i] + 1);
for(int j = 1; j <= m; j++){
if(ch[i][j] == '*'){
a[i][j] = 1;
}
else{
tx = i;
ty = j;
}
}
}
for(int i = 0; i <= n + 1; i++){
a[i][0] = a[i][m + 1] = a[0][i] = a[n + 1][i] = 1;
}
bin[0] = 1;
for(int i = 1; i <= 15; i++){
bin[i] = bin[i - 1] * 4;
}
mp[0].clear();
mp[1].clear();
dp[t][1] = 1;
mp[t][0] = 1;
nst[t].push_back(0);
for(int i = 1; i <= n; i++){
mp[t].clear();
for(int i = 0; i < nst[t].size(); i++){
nst[t][i] *= 4;
mp[t][nst[t][i]] = i + 1;
}
for(int j = 1; j <= m; j++){
t ^= 1;
for(int tp = 1; tp <= nst[t].size(); tp++){
dp[t][tp] = 0;
}
// cerr << "### " << i << ' ' << j << '\n';
mp[t].clear();
nst[t].clear();
for(auto st: nst[t ^ 1]){
int id = mp[t ^ 1][st];
int p1 = ((st >> ((j - 1) * 2)) & 3);
int p2 = ((st >> ((j) * 2)) & 3);
// cerr << st << ' ' << id << ' ' << dp[t ^ 1][id] << '\n';
// cerr << p1 << ' ' << p2 << '\n';
if(a[i][j]){
if(!p1 && !p2){
add(t, st, dp[t ^ 1][id]);
}
continue;
}
int tp1 = bin[j - 1];
int tp2 = bin[j];
int val = dp[t ^ 1][id];
if(!p1 && !p2){
if(!a[i][j + 1] && !a[i + 1][j]){
add(t, st + tp1 + 2 * tp2, val);
}
}
if(!p1 && p2){
if(!a[i][j + 1]){
add(t, st, val);
}
if(!a[i + 1][j]){
add(t, st - tp2 * p2 + tp1 * p2, val);
}
}
if(p1 && !p2){
if(!a[i][j + 1]){
add(t, st - tp1 * p1 + tp2 * p1, val);
}
if(!a[i + 1][j]){
add(t, st, val);
}
}
if(p1 == 1 && p2 == 1){
int flg = 1;
for(int k = j + 1; k <= m; k++){
if(((st >> (k * 2)) & 3) == 1) flg ++;
if(((st >> (k * 2)) & 3) == 2) flg --;
if(!flg){
add(t, st - tp1 - tp2 - bin[k], val);
break;
}
}
}
if(p1 == 2 && p2 == 2){
int flg = 1;
for(int k = j - 2; k >= 0; k--){
if(((st >> (k * 2)) & 3) == 1) flg --;
if(((st >> (k * 2)) & 3) == 2) flg ++;
if(!flg){
add(t, st - tp1 * 2 - tp2 * 2 + bin[k], val);
break;
}
}
}
if(p1 == 2 && p2 == 1){
add(t, st - tp1 * 2 - tp2, val);
}
if(p1 == 1 && p2 == 2){
if(i == tx && j == ty){
ans += val;
}
}
}
}
}
cout << ans ;
return 0;
}

浙公网安备 33010602011771号