树上最大独立集问题
Ural 大学有 N N N 名职员,编号为 1 ∼ N 1 \sim N 1∼N。他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。每个职员有一个快乐指数,用整数 H i H_i Hi 给出,其中 1 ≤ i ≤ N 1 \le i \le N 1≤i≤N。现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。
#include
#include
#include
using namespace std;
const int maxn = 6010;
int h[maxn], e[maxn], ne[maxn], idx;
bool has_father[maxn];
int f[maxn][2], N, happy[maxn];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u) {
//f[u][0]是不选u, f[u][1]是选u。
f[u][1] = happy[u];
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
dfs(v);
f[u][0] += max(f[v][0], f[v][1]);
f[u][1] += f[v][0];
}
}
int main() {
memset(h, -1, sizeof h);
scanf("%d", &N);
for (int i = 1; i <= N; i++) scanf("%d", &happy[i]);
for (int i = 1; i < N; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(b, a);
has_father[a] = true;
}
int root;
for (root = 1; root <= N && has_father[root]; root++);
dfs(root);
printf("%d\n", max(f[root][0], f[root][1]));
return 0;
}
法一:
(1)任取一点 a a a 作为起点,找到距离该点最远的点 u u u. (2)再找到距离 u u u 最远的一点 v v v。
则 u u u 到 v v v 之间的路径是树的直径. 证明的话,只需证 u u u 的是直径的一个端点;用反证法,假设 c d cd cd 是真的直径,那么可以分为 c d cd cd 和 u a ua ua 的直径是否有公共点. 具体可以看视频.
#include
using namespace std;
const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;
int h[N], e[M], ne[M], w[M], idx;
int n, d[N];
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void dfs(int u, int fa)
{
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
d[v] = d[u] + w[i];
dfs(v, u);
}
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof h);
for(int i = 1; i < n; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs(1, -1);
int u = 0, res = -INF;
for(int i = 1; i <= n; i++)
{
if(d[i] > res) res = d[i], u = i;
}
d[u] = 0;
dfs(u, -1);
res = -INF;
for(int i = 1; i <= n; i++)
{
res = max(res, d[i]);
}
printf("%d\n", res);
return 0;
}
法二:
记录一个子树高度的最大值和次大值即可
int ans;
int dfs(int u, int fa)
{
int height = 0, max1 = 0, max2 = 0;
for(int i = h[u]; i != -1; i = ne[i]){
int v = e[i];
if(v == fa) continue;
int nh = dfs(v, u);
height = max(height, nh + w[i]);
if(nh + w[i] > max1){
max2 = max1;
max1 = nh + w[i];
}
else if(nh + w[i] > max2){
max2 = nh + w[i];
}
}
int res = 0;
if(max1) res += max1;
if(max2) res += max2;
ans = max(ans, res);
return height;
}
#include
using namespace std;
const int N = 10010, M = N * 2;
int n;
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int d1[N], d2[N], son1[N], son2[N];
int up[N];
int dfs1(int u, int fa)
{
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
int d = dfs1(v, u) + w[i];
if(d >= d1[u])
{
d2[u] = d1[u], d1[u] = d;
son2[u] = son1[u], son1[u] = v;
}
else if(d > d2[u])
{
d2[u] = d, son2[u] = v;
}
}
return d1[u];
}
void dfs2(int u, int fa)
{
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
//直接从当前层推到下一层.
int dist = w[i] + (son1[u] == v ? d2[u] : d1[u]);
up[v] = max(dist, w[i] + up[u]);
dfs2(v, u);
}
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof h);
for(int i = 1; i < n; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs1(1, -1);
dfs2(1, -1);
int res = 0x3f3f3f3f;
for(int i = 1; i <= n; i++)
{
res = min(res, max(up[i], d1[i]));
}
printf("%d\n", res);
return 0;
}
杨雅儒告诉我们, f [ i , j , 0 / 1 ] f[i,j,0/1] f[i,j,0/1] 表示状态,最后一个 0 / 1 0/1 0/1 表示是否顶到上界.
题意:求给定区间 [ X , Y ] [X,Y] [X,Y] 中满足下列条件的整数个数:这个数恰好等于 K K K 个互不相等的 B B B 的整数次幂之和。
计算 f ( N ) f(N) f(N)。把 N N N 写成 B B B 进制 a n − 1 a n − 2 . . . a 0 ‾ \overline{a_{n-1}a_{n-2}...a_0} an−1an−2...a0。那么,第一位要么填 a n − 1 a_{n-1} an−1,要么填 0 0 0 ~ a n − 1 − 1 a_{n-1}-1 an−1−1.
转化为这道题。因为每一位只能填 1 1 1 或 0 0 0.
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 35;
ll C[maxn][maxn]; //组合数
int K, B;
ll dp(int n) {
if (n == 0) return 0;
vector<int> nums;
while (n) nums.push_back(n % B), n /= B;
ll res = 0; //保存答案。
int last = 0; //保存前缀信息。这里是保存用掉了多少个1
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
if (x) {
res += C[i][K - last]; //C(n-1, K)
if (x > 1) {
if (K - last - 1 >= 0) res += C[i][K - last - 1];
break;
}
else {
last++;
if (last > K) break;
}
}
if (i == 0 && last == K) res++;
//这个求的是方案数。跑到最右边一位时,如果此时一个1也不允许填了,那么此处填0就是这1种方案。
}
return res;
}
int main() {
for (int i = 0; i < maxn; i++) {
for (int j = 0; j <= i; j++) {
if (j == 0) C[i][j] = 1;
else C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
}
}
int L, R;
cin >> L >> R >> K >> B;
cout << dp(R) - dp(L - 1) << endl;
}
深搜代码
#include
using namespace std;
const int N = 35;
int f[N][N][2], a[N];
int k, b, X, Y;
int dp(int pos, int k, int lim)
{
if(pos == -1)
{
return k == 0;
}
int& v= f[pos][k][lim];
if(v != -1) return v;
int up = lim ? min(1, a[pos]) : 1;
int ans = 0;
for(int i = 0; i <= up; i++)
{
if(k > 0) ans += dp(pos - 1, i == 0 ? k : k - 1, i == a[pos] && lim);
if(!k && !i) ans += dp(pos - 1, 0, i == up && lim);
}
return f[pos][k][lim] = ans;
}
int solve(int x)
{
int sz = 0;
while(x)
{
a[sz++] = x % b;
x /= b;
}
memset(f, -1, sizeof f);
return dp(sz - 1, k, 1);
}
int main()
{
scanf("%d%d%d%d", &X, &Y, &k, &b);
printf("%d\n", solve(Y) - solve(X - 1));
}
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 15;
int f[maxn][maxn]; //f[i][j] 表示最高位填 j, 且总共有 i 位的数的个数
int dp(int n) {
if (n == 0) return 1;
int res = 0, last = 0;
vector<int> nums;
while (n) nums.push_back(n % 10), n /= 10;
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
for (int j = last; j < x; j++) {
res += f[i + 1][j];
}
if (last > x) break;
last = x;
//程序能走到这里,说明a0也是合法情况,即N这个数字就是不降数
if (i == 0) res++;
}
return res;
}
int main() {
for (int j = 0; j <= 9; j++) f[1][j] = 1;
for (int i = 2; i < 15; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = j; k <= 9; k++) {
f[i][j] += f[i - 1][k];
}
}
}
int a, b;
while (cin >> a >> b) {
cout << dp(b) - dp(a - 1) << endl;
}
return 0;
}
深搜方法(注意,这道题前导零是合法的。举例来说,表示 [ 1 , R ] , R > 10 [1,R], R > 10 [1,R],R>10 中的数字,如果想表示出1,前面就得有前导零)
#include
using namespace std;
const int N = 35;
int f[N][N], A, B;
int a[N];
//搜索到 pos 个位置,上一位是 k,的方案数.
int dp(int pos, int k, int lim)
{
if(pos == -1) return 1;
if(!lim && f[pos][k] != -1)
{
return f[pos][k];
}
int up = lim ? a[pos] : 9;
int ans = 0;
for(int i = k; i <= up; i++)
{
ans += dp(pos - 1, i, i == up && lim);
}
if(!lim) f[pos][k] = ans;
return ans;
}
int solve(int x)
{
//if(!x) return 1;
int sz = 0;
while(x)
{
a[sz++] = x % 10;
x /= 10;
}
memset(f, -1, sizeof f);
return dp(sz - 1, 0, 1);
}
int main()
{
while(cin >> A >> B)
{
//printf("%d\n%d\n", solve(B), solve(A - 1));
printf("%d\n", solve(B) - solve(A - 1));
}
return 0;
}
#include
using namespace std;
const int N = 300010;
int q[N], s[N];
int n, m;
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &s[i]);
s[i] += s[i - 1];
}
int hh = 0, tt = -1;
int res = -0x3f3f3f3f;
for(int i = 1; i <= n; i++)
{
if(q[hh] < i - m) hh++;
while(hh <= tt && s[q[tt]] >= s[i - 1]) tt--;
q[++tt] = i - 1;
res = max(res, s[i] - s[q[hh]]);
}
printf("%d\n", res);
return 0;
}
#include
using namespace std;
const int N = 200010;
int q[N], f[N], w[N];
int n, m;
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &w[i]);
}
//这里,0应该是单调队列第一个元素,要作为状态转移的起点
int hh = 0, tt = -1;
q[++tt] = 0;
for(int i = 1; i <= n; i++)
{
if(q[hh] < i - m) hh++;
f[i] = f[q[hh]] + w[i];
while(hh <= tt && f[q[tt]] >= f[i]) tt--;
q[++tt] = i;
}
int ans = 0x3f3f3f3f;
for(int i = max(1, n - m + 1); i <= n; i++) ans = min(ans, f[i]);
printf("%d\n", ans);
return 0;
}
如果一张无向连通图包含恰好一个环,则称它是一棵 基环树 (Pseudotree)。
如果一张有向弱连通图每个点的入度都为 1 1 1,则称它是一棵 基环外向树。
如果一张有向弱连通图每个点的出度都为 1 1 1,则称它是一棵 基环内向树。
多棵树可以组成一个 森林 (Forest),多棵基环树可以组成 基环森林 (Pseudoforest),多棵基环外向树可以组成 基环外向树森林,多棵基环内向树可以组成 基环内向森林 (Functional graph)。
如果一张无向连通图的每条边最多在一个环内,则称它是一棵 仙人掌 (Cactus)。多棵仙人掌可以组成 沙漠。
#include
using namespace std;
const int N = 1000010, M = N * 2;
typedef long long ll;
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int n;
bool st[N];
int q[2 * N], ins[N]; //ins 表示在栈中
int fu[N], fw[N]; //u的父结点,以及与父结点之间的边权.
int cnt, cir[N], ed[N];
ll s[N], d[N * 2], sum[N * 2], ans;
void dfs_c(int u, int from)
{
st[u] = ins[u] = true;
for(int i = h[u]; i != -1; i = ne[i])
{
if(i == (from ^ 1)) continue;
int v = e[i];
fu[v] = u, fw[v] = w[i];
if(!st[v]) dfs_c(v, i);
else if(ins[v])
{
cnt++;
ed[cnt] = ed[cnt - 1];
ll sum = w[i];
for(int k = u; k != v; k = fu[k])
{
s[k] = sum;
cir[++ed[cnt]] = k;
sum += fw[k];
}
s[v] = sum, cir[++ed[cnt]] = v;
}
}
ins[u] = false;
}
ll dfs_d(int u)
{
ll d1 = 0, d2 = 0;
st[u] = true;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(st[v]) continue;
ll dist = dfs_d(v) + w[i];
if(dist >= d1) d2 = d1, d1 = dist;
else if(dist > d2) d2 = dist;
}
ans = max(ans, d1 + d2);
return d1;
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof h);
for(int i = 1; i <= n; i++)
{
int a, b;
scanf("%d%d", &a, &b);
add(i, a, b), add(a, i, b);
}
for(int i = 1; i <= n; i++)
{
if(!st[i]) dfs_c(i, -1);
}
memset(st, 0, sizeof st);
for(int i = 1; i <= ed[cnt]; i++) st[cir[i]] = true;
ll res = 0;
for(int i = 1; i <= cnt; i++)
{
ans = 0;
int sz = 0;
for(int j = ed[i - 1] + 1; j <= ed[i]; j++)
{
int k = cir[j];
d[sz] = dfs_d(k);
sum[sz] = s[k];
sz++;
}
for(int j = 0; j < sz; j++)
{
d[j + sz] = d[j], sum[j + sz] = sum[j] + sum[sz - 1];
}
int hh = 0, tt = -1;
for(int j = 0; j < 2 * sz; j++)
{
if(hh <= tt && q[hh] < j - sz + 1) hh++;
if(hh <= tt) ans = max(ans, d[j] + sum[j] + d[q[hh]] - sum[q[hh]]);
while(hh <= tt && d[q[tt]] - sum[q[tt]] <= d[j] - sum[j]) tt--;
q[++tt] = j;
}
res += ans;
}
printf("%lld\n", res);
return 0;
}
破环:基环树上的最大独立集问题
从所有骑士 n ≤ 1 0 6 n \le 10^6 n≤106 中选出一个骑士军团,使得军团内没有矛盾的两人,即不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况,并且使这支骑士军团最富有战斗力。为描述战斗力,我们将骑士按照 1 1 1 至 N N N 编号,给每位骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力之和。每个骑士均有一个最痛恨的人。
可以类比 没有上司的舞会,对于每一个基环树,我们找到环上的一个点 u u u,假设环上另一个点是 v v v,那么我们讨论一下 u u u 和 v v v 之间是否要断开。断开意味着之前 u u u 和 v v v 不能同时选但是现在可能会同时选了,只要避免掉这个情况就可以了. 如果不选 u u u 的话,那么这条边是否删掉后也不改变答案. 如果选 u u u 的话,由于 ( u , v ) (u,v) (u,v) 被删掉了,那么我们特判不选择 v v v 即可. 综上,我们删掉这条边之后,只需要不同时选 u u u 和 v v v 即可.
最后答案就是 max { f ( u , 0 ) , f ( u , 1 ) } \max\{f(u, 0), f(u,1)\} max{f(u,0),f(u,1)}.
所有出度都为1,是内向基环树,建反向边.
#include
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int N = 1000010;
const ll INF = 0x3f3f3f3f3f3f3f3fll;
int h[N], e[N], ne[N], w[N], idx, rm[N];
int n;
ll f1[N][2], f2[N][2], ans;
bool st[N], ins[N];
inline void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs_f(int u, int pa, ll f[][2])
{
for(int i = h[u]; i != -1; i = ne[i])
{
if(rm[i]) continue;
int v = e[i];
dfs_f(v, pa, f);
f[u][0] += max(f[v][0], f[v][1]);
}
f[u][1] = -INF;
if(u != pa)
{
f[u][1] = w[u];
for(int i = h[u];i != -1; i = ne[i])
{
if(rm[i]) continue;
int v = e[i];
f[u][1] += f[v][0];
}
}
}
void dfs_c(int u)
{
st[u] = ins[u] = true;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(!st[v]) dfs_c(v);
else if(ins[v])
{
rm[i] = 1;
dfs_f(v, -1, f1);
dfs_f(v, u, f2);
ans += max(f1[v][0], f2[v][1]);
}
}
ins[u] = false;
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
int v;
scanf("%d%d", &w[i], &v);
add(v, i);
}
for(int i = 1; i <= n; i++)
{
if(!st[i]) dfs_c(i);
}
printf("%lld\n", ans);
return 0;
}
定义一:设 w ( x , y ) w(x,y) w(x,y) 是定义在整数集合上的二元函数。 ∀ a , b , c , d , a ≤ b ≤ c ≤ d , w ( a , d ) + w ( b , c ) ≥ w ( a , c ) + w ( b , d ) \forall a,b,c,d,a \le b \le c \le d,w(a,d) + w(b,c) \ge w(a,c) + w(b,d) ∀a,b,c,d,a≤b≤c≤d,w(a,d)+w(b,c)≥w(a,c)+w(b,d).
定义二:设 w ( x , y ) w(x,y) w(x,y) 是定义在整数集合上的二元函数。若对于定义域上的任意整数 a , b a,b a,b,其中 a < b a < b a<b,都有 w ( a , b + 1 ) + w ( a + 1 , b ) ≥ w ( a , b ) + w ( a + 1 , b + 1 ) w(a, b + 1) + w(a + 1,b)\ge w(a, b) + w(a + 1, b + 1) w(a,b+1)+w(a+1,b)≥w(a,b)+w(a+1,b+1).
f l , r = min k = l r − 1 { f l , k + f k + 1 , r } + w ( l , r ) ( 1 ≤ l < r ≤ n ) f_{l,r} = \min_{k=l}^{r-1}\{f_{l,k}+f_{k+1,r}\} + w(l,r)\qquad\left(1 \leq l < r \leq n\right) fl,r=k=lminr−1{fl,k+fk+1,r}+w(l,r)(1≤l<r≤n)
直接简单实现状态转移,总时间复杂度将会达到 O ( n 3 ) O(n^3) O(n3),但当函数 w ( l , r ) w(l,r) w(l,r) 满足一些特殊的性质时,我们可以利用决策的单调性进行优化。
引理 1:若 w ( l , r ) w(l, r) w(l,r) 满足区间包含单调性和四边形不等式,则状态 f l , r f_{l,r} fl,r 满足四边形不等式。
又叫轮廓线dp,是状态压缩dp的一种实现方式。一般都是基于一个网格,有连通性的限制。
给你一个 n × m n×m n×m 的棋盘,有的格子是障碍,问共有多少条回路满足经过每个非障碍格子恰好一次。
每一个格子至多和上下左右四个格子相连,一条入边,一条出边,因此最多是6种经过这个格子的可能性。因此按照格子枚举复杂度比较低。我们从左到右,从上到下依次枚举格子,如图所示,红色线上方表示已经枚举过的格子,下方表示尚未枚举到的格子,红线拐弯的那个地方表示正在枚举的格子。我们看那个红线,表示边的信息。可以记录是否有边出来。然后记录连通块儿的信息,绿色的线连接的表示连通块儿。注意红线上每条小边也是有编号的,从 0 到 N N N 共 N + 1 N + 1 N+1 条边。
f ( i , j , s ) f(i,j,s) f(i,j,s) 表示当前枚举到第 ( i , j ) (i,j) (i,j) 这个格子,轮廓线的状态是 s s s 的方案数。用括号表示法的话(左括号表示连通块儿的左半边,右括号表示连通块儿的右半边),没有括号是0,左括号是1,右括号是2,红线上的每一条小边有3个状态,因此可以采用 4 进制表示这条红线的状态 s。而且我们也发现,枚举的 ( i , j ) (i,j) (i,j) 的格子确定的话,方案数就确定了。
插头dp的状态表示都是这个样子,但是状态转移很复杂。插头dp最难的地方就是状态转移的讨论。
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 50010, maxm = maxn * 2 + 7;
//合法状态大致不到五万个
int N, M, end_x, end_y; //end_x, end_y 表示最后一个没有障碍的格子
int g[20][20], q[2][maxn], cnt[maxn]; //滚动状态数组,q 里面存的是有效状态在哈希表内的下标。
int h[2][maxm]; //滚动哈希表
ll v[2][maxm];
int find(int cur, int x) //哈希表
{
int t = x % maxm;
while(h[cur][t] != -1 && h[cur][t] != x){
if(++t == maxm) t = 0;
}
return t;
}
void insert(int cur, int state, ll w)
{
int t = find(cur, state);
if (h[cur][t] == -1)
{
h[cur][t] = state, v[cur][t] = w;
q[cur][++cnt[cur]] = t;
}
else v[cur][t] += w;
}
int get(int state, int k) //求第 k 个格子的状态,其实就是求state的4进制的意义下第 k 位数字。
{
return (state >> (k * 2)) & 3;
}
int set(int k, int v) //构造四进制下第k位数字为 v 的数
{
return v * (1 << (k * 2));
}
int main()
{
scanf("%d%d", &N, &M);
for(int i = 1; i <= N; i++){
char str[20];
scanf("%s", str + 1);
for(int j = 1; j <= M; j++){
if(str[j] == '.'){
g[i][j] = 1;
end_x = i, end_y = j;
}
}
}
ll res = 0;
memset(h, -1, sizeof h);
//将最开始的状态置为1
int cur = 0;
insert(cur, 0, 1);
//cur表示滚动数组当前是哪一层。
for (int i = 1; i <= N; i ++ )
{
for (int j = 1; j <= cnt[cur]; j ++ )
h[cur][q[cur][j]] <<= 2;
for (int j = 1; j <= M; j ++ )
{
int last = cur;
cur ^= 1, cnt[cur] = 0;
memset(h[cur], -1, sizeof h[cur]);
for (int k = 1; k <= cnt[last]; k ++ )
{
int state = h[last][q[last][k]];
ll w = v[last][q[last][k]];
int x = get(state, j - 1), y = get(state, j);
if (!g[i][j])
{
//当前是障碍物
if (!x && !y) insert(cur, state, w);
}
else if (!x && !y)
{
if (g[i + 1][j] && g[i][j + 1])
insert(cur, state + set(j - 1, 1) + set(j, 2), w);
}
else if (!x && y)
{
if (g[i][j + 1]) insert(cur, state, w); //状态不变
if (g[i + 1][j]) insert(cur, state + set(j - 1, y) - set(j, y), w); //在j-1的位置减去插头y,在j的位置加上插头y
}
else if (x && !y)
{
if (g[i][j + 1]) insert(cur, state - set(j - 1, x) + set(j, x), w);
if (g[i + 1][j]) insert(cur, state, w);
}
else if (x == 1 && y == 1)
{
//开始括号匹配,找到最后一对匹配的括号,把他们俩连起来。
for (int u = j + 1, s = 1;; u ++ )
{
int z = get(state, u);
if (z == 1) s ++ ;
else if (z == 2)
{
if ( -- s == 0)
{
insert(cur, state - set(j - 1, x) - set(j, y) - set(u, 1), w);
break;
}
}
}
}
else if (x == 2 && y == 2)
{
for (int u = j - 2, s = 1;; u -- )
{
int z = get(state, u);
if (z == 2) s ++ ;
else if (z == 1)
{
if ( -- s == 0)
{
insert(cur, state - set(j - 1, x) - set(j, y) + set(u, 1), w);
break;
}
}
}
}
else if (x == 2 && y == 1)
{
insert(cur, state - set(j - 1, x) - set(j, y), w);
}
else if (i == end_x && j == end_y)
res += w;
}
}
}
printf("%lld\n", res);
return 0;
}