嗯,所有需要的代码(包括全部7个例题及注释中的实现)都有,还附赠了两三个练习。希望能对需要的人有帮助(只要不是淹没到百度里根本搜不出来就好了)
2022 update
原来搞acm青春版(指noip)的某人现在已经去搞ctf了(也可以说是黑客技术赛~),但是水acm招新群的时候 (什么群都水不是常识么) 看学弟学妹好像学到这了?这不让这篇文章发挥发挥下余热~
其实是难得的一个完成度高的博客,古村里的希望了属于是
顺便修补一下 (csdn改markdown模块了、很多oj也早没了欸)
如果现在问我这篇文章的问题的话,等我研究下自己的不知道什么时候写的代码应该… …能回答
前言
先确认我们说的是同一个东西
论文开头为
网上应该都下得到吧,就不放链接了
2021/11/21 update
现在好像还真下不到,传网盘了
链接:https://pan.baidu.com/s/1K04RAqzM0MXhEzsEbYfhaQ
提取码:0qf5
有论文的话我就不赘述原文中的相关解释了,但是也会大致说明一下原题是什么,再放个人代码
其实代码很多是%了各位dalao码出来的,所以代码中我也会说一些我发现的好的地方
csdn的代码片有点反人类啊,实在不行的话,麻烦各位自行用编辑器自动缩进了
若能在oj上找到的我也尽量直接贴上oj题号
还会有很少的练习题
某日update
【例7】完成!对于进制的理解可以更深一些
正文
【引例】
题目描述
在 n * n
(n≤20)的方格棋盘上放置 n 个车(可以攻击所在行、列),求使它们不能互相攻击的方案总数。
代码
#include <cstdio>
#include <cstring>
const int maxn = 10;
int f[1<<maxn + 10] = {0}, lim;
inline int lowbit(int x){return -x&x;} //取出一个数最低的二进制位
int main (){
int n; scanf ("%d", &n);
f[0] = 1, lim = (1<<n);
for (int s = 1, lk; s < lim; ++s){
for (int k = s; k > 0; k -= lk){
f[s] += f[s ^ (lk = lowbit(k))]; //这种打法应该很有效率啦
}
}
printf ("%d\n", f[lim - 1]);
return 0;
}
【例1】
题目描述
在 n * n
(n≤20)的方格棋盘上放置 n 个车,某些格子不能放,求使它们不能互相攻击的方案总数。
代码
#include <cstdio>
#include <cstring>
const int maxn = 10;
int f[1<<maxn + 10], out[maxn], lim;
inline int lowbit(int x){return -x&x;}
int main (){
freopen ("1.in", "r", stdin);
int n, m; scanf ("%d%d", &n, &m);
for (int i = 1, x, y; i <= m; ++i){
scanf ("%d%d", &x, &y);
out[x] |= 1 << --y; //这行在网上看到的有些代码打得奇奇怪怪的...
}
f[0] = 1, lim = (1 << n) - 1;
for (int s = 1, st, h, lk; s < lim; ++s){
st = s, h = 0;
while (st) st -= lowbit(st), ++h;
for (int j = s ^ out[h]; j > 0; j -= lowbit(j)){
f[s] += f[s ^ (lk = lowbit(j))];
}
}
printf ("%d\n", f[lim - 1]);
return 0;
}
poj1321好像是例1的一点点拓展,这个练习是临时加的
棋盘问题 POJ1321(练习)
题目描述
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
具体输入输出什么的自己去看就好了,中文题
代码
#include <cstdio>
#include <cstring>
const int maxn = 10;
char tmps[maxn];
int lim, f[maxn][1<<maxn], c[1<<maxn], rs[75], rst, out[maxn];
inline int Cal(int x){
int ans = 0;
while (x){if(x & 1) ++ans; x >>= 1;}
return ans;
}
inline int lowbit(int x){return -x & x;}
int dp(int rti, int rts){
if (f[rti][rts]) return f[rti][rts];
if (!rti) return 0;
for (int s = out[rti] ^ rts; s > 0; s -= lowbit(s))
f[rti][rts] += dp(rti - 1, lowbit(s) ^ rts);
return f[rti][rts] += dp(rti - 1, rts);
}
int main (){
freopen ("p1321.in", "r", stdin);
for (int s = 0; s < (1 << maxn); ++s) c[s] = Cal(s);
int n, k, ans;
while (scanf ("%d%d", &n, &k), n > 0){
rst = ans = 0;
memset(out, 0, sizeof out), memset(f, 0, sizeof f);
for (int i = 1; i <= n; ++i){
scanf ("%s", tmps);
for (int j = 0; j < n; ++j)
if (tmps[j] == '.')
out[i] |= 1 << j;
}lim = 1 << n;
for (int s = 0; s < lim; ++s)
if (c[s] == k) rs[rst++] = s;
f[0][0] = 1;
for (int i = 0; i < rst; ++i) ans += dp(n, rs[i]);
printf ("%d\n", ans);
}
return 0;
}
【例2】(伪)
题目描述
为什么说是伪呢,因为我强行改题目,就求方案数就好吧
给出一个 n * m
的棋盘(n、m≤80,n * m
≤80),要在棋盘上放 k(k≤20)个棋子,使得任意两个棋子不相邻。求方案数
代码
关于注释中的递推程序
论文第四页页末注释处
#include <cstdio>
const int maxn = 1010;
int g[maxn];
int main (){
int m, d; scanf ("%d%d", &m, &d);
for (int i = 1; i <= d; ++i) g[i] = 1;
for (int j = d + 1; j <= m + d + 1; ++j) g[j] = g[j - d - 1] + g[j - 1];
printf ("%d", g[m + d + 1]);
return 0;
}
题目实现
#include <cstdio>
typedef long long ll;
const int maxnum = 150, maxn = 10, maxk = 25;
int num = 0, s[maxnum], c[maxnum];
ll f[maxn][maxnum][maxk];
template <typename T>
inline void Swap(T &a, T &b){int t = a; a = b, b = t;}
inline int cal(int x){
int ans = 0;
while (x){if (x & 1) ++ans; x>>=1;}
return ans;
}
int main (){
freopen ("2.in", "r", stdin);
freopen ("2.out", "w", stdout);
int n, m, k; scanf ("%d%d%d", &n, &m, &k);
if (m > n)Swap(n, m);
int collim = 1 << m;
for (int i = 0; i < collim; ++i)
if (!(i & (i << 1))) s[++num] = i, c[num] = cal(i);
//, f[1][num][c[num] = cal(i)] = 1;
f[0][1][0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= num; ++j){
for (int pn = c[j]; pn <= k; ++pn)
for (int p = 1; p <= num; ++p){
if (!(s[j] & s[p]))
f[i][j][pn] += f[i - 1][p][pn - c[j]];
}
}
ll ans = 0;
for (int i = 1; i <= num; ++i) ans += f[n][i][k];
printf ("%lld", ans);
return 0;
}
【例3】
题目描述
在 n * n
(n≤10)的棋盘上放 k 个国王(可攻击相邻的 8 个格子),求使它们无法互相攻击的方案数。
代码
#include <cstdio>
typedef long long ll;
const int maxn = 13, maxnum = 150, maxk = 25;
ll f[maxn][maxnum][maxk];
int s[maxnum], c[maxnum], num = 0;
inline int cal(int x){
int ans = 0;
while (x){if (x & 1) ++ans; x >>= 1;}
return ans;
}
int main (){
freopen ("3.in", "r", stdin);
//freopen ("3.out", "w", stdout);
int n, k; scanf ("%d%d", &n, &k);
int lim = 1 << n;
for (int i = 0; i < lim; ++i)
if (!(i & (i << 1))) s[++num] = i, c[num] = cal(i);
f[0][1][0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= num; ++j)
for (int pn = c[j]; pn <= k; ++pn)
for (int p = 1; p <= num; ++p)
if (!(s[j] & s[p]) && !((s[j] << 1) & s[p]) && !((s[j] >> 1) & s[p]))
f[i][j][pn] += f[i - 1][p][pn - c[j]];
ll ans = 0;
for (int i = 1; i <= num; ++i) ans += f[n][i][k];
printf ("%lld", ans);
return 0;
}
【例4】(POJ1185)
题目描述
给出一个 n*m(n≤100,m≤10)的棋盘,一些格子不能放置棋子。求最多能在棋盘上放置多少个棋子 ,
使得每一行每一列的任两个棋子间至少有两个空格
代码
#include <cstdio>
const int maxn = 105, maxm = 12, maxs = 65;
char tmps[maxm];
int out[maxn], f[maxn][maxs][maxs], s[maxs], c[maxs], num = 0, lim;
inline int Cal(int x){
int ans = 0;
while (x){if (x & 1) ++ans; x >>= 1;}
return ans;
}
int main (){
int n, m; scanf ("%d%d", &n, &m);
for (int i = 1; i <= n; ++i){
scanf ("%s", tmps);
for (int j = 0; j < m; ++j)
if (tmps[j] == 'H') out[i] |= 1 << j;
}
lim = 1 << m;
for (int i = 0, ci; i < lim; ++i)
if (!(i & (i << 2)) && !(i & (i << 1))) s[++num] = i, c[num] = Cal(i);
for (int i = 1; i <= num; ++i)
if (!(s[i] & out[1])){
f[1][i][1] = c[i];
for (int j = 1; j <= num; ++j)
if (!(s[j] & s[i]) && !(s[j] & out[2]))
f[2][j][i] = c[i] + c[j];
}
for (int i = 3, mx; i <= n; ++i)
for (int j = 1; j <= num; ++j)
if (!(s[j] & out[i]))
for (int k = 1; k <= num; ++k)
if (!(s[j] & s[k]) && !(s[k] & out[i - 1])){
mx = 0;
for (int l = 1; l <= num; ++l)
if (!(s[l] & out[i - 2]))
if (!(s[j] & s[l]) && !(s[k] & s[l]) && f[i - 1][k][l] > mx)
mx = f[i - 1][k][l];
f[i][j][k] = mx + c[j];
}
int ans = 0;
for (int j = 1; j <= num; ++j)
if (!(s[j] & out[n]))
for (int k = 1; k <= num; ++k)
if (!(s[j] & s[k]) && !(s[k] & out[n - 1]) && f[n][j][k] > ans)
ans = f[n][j][k];
printf ("%d", ans);
return 0;
}
【骑士】(练习)
强行夹在中间233
题目描述
骑士 (knight.pas/c/cpp )
国际象棋中骑士的移动规则和中国象棋中的马是类似的,它先沿着一个方向移动两格,
再沿着与刚才移动方向垂直的方向移动一格。路径上的棋子并不会影响骑士的移动,但是如
果一个骑士走到了一个放有棋子的格子,它就会攻击那个棋子。现在有一个 n * n
的棋盘,有
k 个骑士需要被摆到棋盘上去。那么使所有骑士互不攻击的摆放方式一共有多少种呢?
输入数据
一行:两个整数,n,k
输出数据
一行:一个整数,为摆放的方式数
样例
输入:knight.in
3 2
输出:knight.out
28
输入:knight.in
4 4
输出:knight.out
412
数据范围 ,分值 与时间限制
第一次用这个表格
数据编号 | 数据范围 | 时间限制 | 单个测试点分值 | 总分值 |
---|---|---|---|---|
1~15 | 1≤n≤4 | 1.0s | 3 | 45 |
16~20 | 5≤n≤6 | 1.0s | 5 | 25 |
21~25 | 7≤n≤8 | 5.0s | 6 | 30 |
对于所有的数据,0<=k<=n
保证答案不超过 double 的精度。
代码
#include <cstdio>
#include <cstring>
typedef long long ll;
const int maxn = 9, maxm = 260;//maxm is cal_ed
ll f[2][1<<maxn][1<<maxn][maxn];
int cal[1<<maxn], nx[1<<maxn][maxm], num[1<<maxn];
inline int Cal(int x){
if (cal[x]) return cal[x]; int tx = x, ans;
for (ans = 0; x; ){ if (x & 1) ++ans; x >>= 1;}
return cal[tx] = ans;
}
int main (){
freopen ("knight.in", "r", stdin);
freopen ("knight.out", "w", stdout);
int n, pk, lim; scanf ("%d%d", &n, &pk);
if (n == 1){printf ("%d", pk <= 1 ? 1 : 0); return 0;}
lim = 1 << n;
for (int i = 0; i < lim; ++i){
f[0][i][0][Cal(i)] = 1;
for (int j = 0; j < lim; ++j)
if (!((i << 2) & j) && !((i >> 2) & j)){
f[1][j][i][Cal(j) + Cal(i)] = 1;
nx[i][++num[i]] = j;
}
}
for (int i = 3, k, l, rt; i <= n; ++i){
rt = (i ^ 1) & 1;
memset(f[rt], 0, sizeof f[rt]);
for (int j = 0; j < lim; ++j)
for (int pn = cal[j]; pn <= pk; ++pn)
for (int nk = 1; nk <= num[j]; ++nk){
k = nx[j][nk];
for (int nl = 1; nl <= num[k]; ++nl){
l = nx[k][nl];
if (!((l << 1) & j) && !((l >> 1) & j)){
f[rt][j][k][pn] += f[rt ^ 1][k][l][pn - cal[j]];
}
}
}
memset(f[rt^1], 0, sizeof f[rt^1]);
}
ll ans = 0;
for (int j = 0, k; j < lim; ++j)
for (int nk = 1; nk <= num[j]; ++nk){
k = nx[j][nk];
ans += f[(n ^ 1) & 1][j][k][pk];
}
printf ("%lld", ans);
return 0;
}
【例5】
题目描述
给出 n * m
(1≤n、m≤11)的方格棋盘,用 1 * 2
的长方形骨牌不重叠地覆盖这个棋盘,求覆盖满的方案
数。
代码
其实并没有完全按论文里打,是参考dalaoKB代码,更为清晰明了
#include <cstdio>
typedef long long ll;
const int maxn = 12;
ll f[maxn][1 << 11];
int n, m;
template <typename T>
void Swap (T &a, T &b){T t = a; a = b, b = t;}
void dp (int li, int co, int rt, int la){//line, column, root, last
if (co > m) return;
if (co == m){ f[li][rt] += f[li - 1][la]; return ;}
dp (li, co + 1, rt << 1, (la << 1) | 1);
dp (li, co + 1, (rt << 1) | 1, la << 1);
dp (li, co + 2, (rt << 2) | 3, (la << 2) | 3);
}
int main (){
freopen ("5.in", "r", stdin);
freopen ("5.out", "w", stdout);
int lim; scanf ("%d%d", &n, &m);
if (m > n) Swap(n, m);
lim = 1 << m;
f[0][lim - 1] = 1;
for (int i = 1; i <= n; ++i)
dp(i, 0, 0, 0);
printf ("%lld", f[n][lim - 1]);
return 0;
}
【例6】
题目描述
给出 n * m
(1≤n、m≤9)的方格棋盘,用 1 * 2
的矩形的骨牌和 L 形的(2 * 2
的去掉一个角)骨牌不重叠地
覆盖,求覆盖满的方案数。
代码
%%%KB%%%
#include <cstdio>
typedef long long ll;
const int maxn = 10;
ll f[maxn][1 << 9];
int n, m;
template <typename T>
void Swap (T &a, T &b){T t = a; a = b, b = t;}
void dp (int li, int co, int rt, bool fr, int la, bool fl){
//line, column, root, fluence of root, last, fluence of last
if (co == m){
if (!fr && !fl) f[li][rt] += f[li - 1][la];
return ;
}
dp (li, co + 1, rt << 1 | fr, 0, la << 1 | (fl ^ 1), 0); //no putting
if (!fr && !fl){
dp (li, co + 1, rt << 1 | 1, 0, la << 1, 0); //10/10
dp (li, co + 1, rt << 1 | 1, 1, la << 1, 0); //10/11
dp (li, co + 1, rt << 1 | 1, 0, la << 1, 1); //11/10
}
if (!fr){
dp (li, co + 1, rt << 1 | 1, 1, la << 1 | (fl ^ 1), 0); //00/11
dp (li, co + 1, rt << 1 | 1, 1, la << 1 | (fl ^ 1), 1); //01/11
}
if (!fl) dp (li, co + 1, rt << 1 | fr, 1, la << 1, 1); //11/01
}
int main (){
freopen ("6.in", "r", stdin);
freopen ("6.out", "w", stdout);
int lim; scanf ("%d%d", &n, &m);
if (m > n) Swap(n, m); lim = 1 << m;
f[0][lim - 1] = 1;
for (int i = 1; i <= n; ++i) dp(i, 0, 0, 0, 0, 0);
printf ("%lld", f[n][lim - 1]);
return 0;
}
【例7】(COGS 1520)
2021/11/21 update
好像这个oj早就无了,默哀1s
题目描述
给出 n * m
(n,m≤10)的方格棋盘,用 1 * r
的长方形骨牌不重叠地覆盖这个棋盘,求覆盖满的方案数
代码
感谢COGS中野生神犇mikumikumi分享的代码,基本框架都是他/她的,自己打了一遍,稍加优化
附神犇注释
#include <cstdio>
#include <cstring>
typedef long long ll;
const int maxn = 11, maxr = 1e7;
ll f[2][maxr] = {0};//5 ^ 10 -> maxr
ll powr[maxn] = {1}, lim;//r进制状态压缩
int n, m, r;
template <typename T>
void Swap (T &a, T &b){T t = a; a = b, b = t;}
void dp(int li, int p, int s1, int s2){//s1是当前行的状态,s2是上一行的
if (p > m) return ;
if (p == m){f[li][s1] += f[li ^ 1][s2]; return ;}
for (int i = 0; i < r - 1; ++i) dp(li, p + 1, s1 * r + i, s2 * r + i + 1);
dp(li, p + 1, s1 * r + r - 1, s2 * r);
dp(li, p + r, s1 * powr[r] + powr[r] - 1, s2 * powr[r] + powr[r] - 1);
}
int main (){
freopen ("examseven.in", "r", stdin);
freopen ("examseven.out", "w", stdout);
scanf ("%d%d%d", &r, &n, &m);
if (m % r && n % r){printf ("0"); return 0;}
if (m > n) Swap(n, m);
for (int i = 1; i <= m; ++i) powr[i] = powr[i - 1] * r;
f[0][(lim = powr[m]) - 1] = 1;
for (int i = 1; i <= n; ++i){
dp(i & 1, 0, 0, 0);
memset(f[(i & 1) ^ 1], 0, sizeof (f[0]));
}
printf ("%lld", f[n & 1][lim - 1]);
return 0;
}
后记
这篇论文其实2016年11月就有计划全部打完的
,但一直都拖拖拖拖拖拖拖于是我就laji了 好的我知道这梗太冷…
代码完成过程确实发现自己很多不足
以后要有类似这种有必要学习的,一定要当时就决心代码实现
2022/1/7 update
真的吗
咳咳,以后要有类似这种有必要学习的,一定要当时就决心代码实现
- 至少得放在to-do计划里带截止日期,不准删
再贴下关于这个的sum吧一堆XX错误
1
没有直接枚举s^out
1 0意义不清
用s错用j
2
一直错用s[j] 为 j
poj1321
rti为零时
(~ &) == ^ ?
4
答案求最大却求和
比较大小后赋的又不是所比较的值
5
floor第三次打还把方程弄错,其他倒是一点没错了
6
永远都不记得当前放的会影响到上一行,上一行的当前列不要放
7
lim 写成 powr[n]
陷入几乎抄代码样例也不过模式…
用两个gdb一行行查,查出来是dp里for中dp参数li写成i