状压dp
真就错题重错呗……
状态压缩的目的是保存状态的详细信息,定义就是把集合转化为整数记录在dp状态中的一类算法。其中有一种典型的题型是“填充网格图形”,在判断合法性的时候用到各种巧妙的位运算,不过好像也没有什么变化,可以积累成套路:
比如Corn Fields就比较基础,发生冲突的区间只有上下左右,用<<和&就可以实现;
升级一下就得到了互不侵犯,至于上一行的角把它左右移一下和当前行判断就可以,对于没有上一行的情况就相当于少了限制条件,可以用dfs预处理,不仅处理了第一行,同时是后面的行的备选状态,节省了不少枚举和判断,经验就是状态是可以预处理的,因为限制了个数所以还和数1有关;
继续升级有炮兵阵地,拓展到了上面的上面和左边的左边,前两行比较特殊所以预处理前两行,后面的可以直接枚举,经验是不要被循环层数吓到,对于这么小的数据范围真的没有多少,求最多于是还要数1(发现数1挺常用的);
还有像特殊方格棋盘一样整行整列的,确实够特殊的,于是按照套路枚举就不行了,发现他根本就没必要枚举了,其妙地发现状压不仅可以表示一行的状态,还可以表示整个矩阵。
不过状压当然不是只有这些填充图形,还可以各种综合,比如*字符串匹配、图论……应用广泛奇奇妙妙,所以遇到鬼畜题+鬼畜的数据范围它都可能用状压来求解,见到那些鬼畜题可能不看题解毫无思路但是友好的一面就是见到代码之后容易理解。
and状压思想不仅可以用来dp,对于某些题目搞一些暴力枚举的部分分也是很实用的qwq
A. 特殊方格棋盘
因为每行和每列都只能放一个车,所以可以把整个棋盘用一个一维的状态表示出来,通过1的个数来判断当前摆到了第几行,这个状态的由来可以是从其中减掉任意一个1,再判断一下是否合法——当前新加的这个车不能摆在禁止的位置上。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 50007; const int mod = 1e9 + 7; const int Inf = 0x3f3f3f3f; #define re register ll f[1<<21]; int s[21][21], state[21], n, m; inline ll read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int lowbit(int x) { return x & -x; } int main() { n = read(); m = read(); for(int i=1; i<=m; i++) { int x = read(), y = read(); s[x][y] = 1; } for(int i=1, x; i<=n; i++) { for(int j=1; j<=n; j++) { if(s[i][j] == 1) x = 0; else x = 1; state[i] = (state[i]<<1) + x; } } f[0] = 1; int Max = (1<<n); for(int i=0; i<Max; i++) { int x = i, hang = 0; while(x) x -= lowbit(x), hang++; for(int j=i; j; j-=lowbit(j)) { x = lowbit(j); //printf("j == %d x == %d\n", j, x); if(x != (state[hang] & x)) continue; f[i] += f[i-x]; } } printf("%lld", f[Max-1]); return 0; }
B. 互不侵犯
我当时COPY题解的时候想不明白它只判断了当前行和上一行的相对合法,但是它不能保证上一行是合法的,结论就是预处理的不能直接用还得继续判断。后来发现上一行状态不合法种类数为0就行了。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 50007; const int mod = 1e9 + 7; const int Inf = 0x3f3f3f3f; #define re register ll sta[2005], sit[2005], f[15][2005][105]; int n, k, cnt; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int lowbit(int x) { return x & -x; } void dfs(int x, int num, int cur)//x is 2进制state, num is 1的个数, cur is king的位置 { if(cur >= n) { sit[++cnt] = x; sta[cnt] = num; return; } dfs(x, num, cur+1);//这个位置没有king dfs(x + (1<<cur), num+1, cur+2);//这个位置有king } bool fair(int j, int x) { if(sit[j] & sit[x]) return false; if((sit[j]<<1) & sit[x]) return false; if(sit[j] & (sit[x]<<1)) return false; return true; } int main() { n = read(); k = read(); dfs(0, 0, 0);//the first line //不只得到了第一行的合法状态,也是不考虑行间关系的每一种情况 for(int j=1; j<=cnt; j++) { f[1][j][sta[j]] = 1; } for(int i=2; i<=n; i++)//1 has been finished { for(int j=1; j<=cnt; j++)//now { for(int x=1; x<=cnt; x++)//origin { if(!fair(j, x)) continue;//x+j is illegal for(int l=sta[j]; l<=k; l++) { f[i][j][l] += f[i-1][x][l-sta[j]]; } } } } ll ans = 0; for(int i=1; i<=cnt; i++) { ans += f[n][i][k]; } printf("%lld", ans); return 0; }
C. 炮兵阵地
注意一下空间的问题可以滚动数组,多数滚动数组好像当前行都是需要清空的,但是这种取最大值的而且能保证行少了一定不会更优,不清空也不会影响答案。滚动数组的另一个出错点就是把滚动的维和其它东西空间开反,RE却不报错只是扔出来一堆错误答案和离谱的中间过程,回去检查一下RE了吗。Third就是注意一下统计的答案到底是哪一维数组。
//这么多层循环,有一种暴力枚举的感觉,要不是因为看了题解我根本就不敢这么写 #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 50007; const int mod = 1e9 + 7; const int Inf = 0x3f3f3f3f; #define re register int n, m, Max, a[102], sum[(1<<10)], f[4][(1<<10)][(1<<10)], ans; char s[12]; inline ll read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } inline int lowbit(int x) { return x & -x; } inline int getsum(int x) { int ans = 0; while(x) { x -= lowbit(x); ans++; } return ans; } int main() { n = read(); m = read(); Max = (1<<m); for(int i=1; i<=n; i++) { scanf("%s", s+1); for(int j=1,x; j<=m; j++) { if(s[j] == 'H') x = 1; else x = 0; a[i] = (a[i] << 1) + x; } } for(int i=0; i<Max; i++) { sum[i] = getsum(i); } for(int S=0; S<Max; S++) { if((S & a[1]) || (S & (S<<1)) || (S & (S<<2))) continue; f[1][S][0] = sum[S]; } for(int L=0; L<Max; L++) { if((L & a[1]) || (L & (L<<1)) || (L & (L<<2))) continue; for(int S=0; S<Max; S++) { if((L & S) || S & a[2] || (S & (S<<1)) || (S & (S<<2))) continue; f[2][S][L] = sum[S] + sum[L]; } } for(int i=3; i<=n; i++) { for(int L=0; L<Max; L++) { if((L & a[i-1]) || (L & (L<<1)) || (L & (L<<2))) continue; for(int S=0; S<Max; S++) { if((L & S) || (S & a[i]) || (S & (S<<1)) || (S & (S<<2))) continue; for(int d=0; d<Max; d++) { if((d & L) || (d & S) || (d & a[i-2]) || (d & (d<<1)) || (d & (d<<2))) continue; f[i%3][S][L] = max(f[i%3][S][L], f[(i-1)%3][L][d] + sum[S]); } } } } for(int L=0; L<Max; L++) { for(int S=0; S<Max; S++) { ans = max(ans, f[n%3][S][L]); } } printf("%d", ans); return 0; }
E. 愤怒的小鸟
(0, 0)已经有了,再找两个点确定抛物线,可以预处理两个点确定的抛物线经过的点集和第一个没有被打掉的目标的位置---可以找到从左到右的第一个0安从右往左的顺序数的排行。
有一个解方程的好方法

美中不足就是他这个a的含义和题面上不一致,本来不带-号,但是代码里的a的含义和题面是一致的,解a的时候把分母上两个减数调换一下位置就解到了不带-号的a。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e3 + 4; const double eps = 1e-8; const int N = 60; const ll mod = 1000003; int T, n, m, start[(1<<18)+5], lines[20][20], f[(1<<18)+5]; double x[20], y[20]; 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 = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } void get_ab(double &a, double &b, int i, int j) { a = -(y[i]*x[j]-y[j]*x[i])/(x[j]*x[j]*x[i]-x[i]*x[i]*x[j]); b = (y[i]*x[j]*x[j]-y[j]*x[i]*x[i])/(x[i]*x[j]*x[j]-x[j]*x[i]*x[i]); } int main() { for(int i=0; i<(1<<18); i++) { int j = 1; for(; j<=18&&i&(1<<(j-1)); j++); start[i] = j; } T = read(); while(T--) { memset(lines, 0, sizeof(lines)); memset(f, 1, sizeof(f)); //=16843009 f[0] = 0; n = read(); m = read(); for(int i=1; i<=n; i++) scanf("%lf%lf", &x[i], &y[i]); for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { if(fabs(x[i]-x[j])<eps) continue; double a, b; get_ab(a, b, i, j); if(a > -eps) continue; for(int k=1; k<=n; k++) { if(fabs(a*x[k]*x[k]+b*x[k]-y[k]) < eps) { lines[i][j] |= (1<<(k-1)); } } } } for(int i=0; i<(1<<n); i++) { int j = start[i]; f[i|(1<<(j-1))] = min(f[i|(1<<(j-1))], f[i]+1); for(int k=1; k<=n; k++) { f[i|lines[j][k]] = min(f[i|lines[j][k]], f[i]+1); } } printf("%d\n", f[(1<<n)-1]); } return 0; }
F. 动物园
原来状压不仅可以压所有的,还可以选择一部分,而这些部分是有联系的,以每个位置为端点确定的状态的后4个会影响下一个位置。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e3 + 4; const double eps = 1e-8; const int N = 60; const ll mod = 1000003; int T, n, m, start[(1<<18)+5], lines[20][20], f[(1<<18)+5]; double x[20], y[20]; 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 = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } void get_ab(double &a, double &b, int i, int j) { a = -(y[i]*x[j]-y[j]*x[i])/(x[j]*x[j]*x[i]-x[i]*x[i]*x[j]); b = (y[i]*x[j]*x[j]-y[j]*x[i]*x[i])/(x[i]*x[j]*x[j]-x[j]*x[i]*x[i]); } int main() { for(int i=0; i<(1<<18); i++) { int j = 1; for(; j<=18&&i&(1<<(j-1)); j++); start[i] = j; } T = read(); while(T--) { memset(lines, 0, sizeof(lines)); memset(f, 1, sizeof(f)); //=16843009 f[0] = 0; n = read(); m = read(); for(int i=1; i<=n; i++) scanf("%lf%lf", &x[i], &y[i]); for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { if(fabs(x[i]-x[j])<eps) continue; double a, b; get_ab(a, b, i, j); if(a > -eps) continue; for(int k=1; k<=n; k++) { if(fabs(a*x[k]*x[k]+b*x[k]-y[k]) < eps) { lines[i][j] |= (1<<(k-1)); } } } } for(int i=0; i<(1<<n); i++) { int j = start[i]; f[i|(1<<(j-1))] = min(f[i|(1<<(j-1))], f[i]+1); for(int k=1; k<=n; k++) { f[i|lines[j][k]] = min(f[i|lines[j][k]], f[i]+1); } } printf("%d\n", f[(1<<n)-1]); } return 0; }

浙公网安备 33010602011771号