【例题1】拔河比赛
我们在 d f s dfs dfs 的过程中记录当前选了几个人,这些人的体重是多少,然后当遍历完所有人并且满足条件的时候,更新答案
#include
#include
#include
#include
#include
#include
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
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;
}
const int M = 200;
int T,n,sum,ans = 1e9;
int a[M];
inline void dfs(int cur,int x,int tot){
if(x == n/2){
ans = min(ans,abs(tot*2-sum));
return;
}
if(cur > n) return;
dfs(cur+1,x+1,tot+a[cur]);
dfs(cur+1,x,tot);
}
inline void work(){
n = read();
sum = 0,ans = 1e9;
rep(i,1,n) a[i] = read(),sum += a[i];
dfs(1,0,0);
printf("%d\n",ans);
}
signed main(){
T = read();
while(T--) work();
return 0;
}
【例题2】数独游戏
Y b t Ybt Ybt 书上讲的做法没看懂,就写了一个非常暴力的 d f s dfs dfs。用三个 v i s vis vis 数组分别记录行,列,宫是否有这个数字,在 d f s dfs dfs 的过程中判断是否可行,往下搜索即可。
#include
#include
#include
#include
#include
#include
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
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;
}
const int M = 10;
int a[M][M],vish[M][M],visl[M][M],vis[M][M];
int fl;
inline void pr(){
rep(i,1,9) rep(j,1,9) printf("%d",a[i][j]);
puts("");
}
inline void init(){
memset(vish,0,sizeof(vish));
memset(visl,0,sizeof(visl));
memset(vis,0,sizeof(vis));
memset(a,0,sizeof(a));
fl = 0;
}
inline int pos(int x,int y){
if(x <= 3 && y <= 3) return 1;
if(x <= 3 && y <= 6) return 2;
if(x <= 3 && y <= 9) return 3;
if(x <= 6 && y <= 3) return 4;
if(x <= 6 && y <= 6) return 5;
if(x <= 6 && y <= 9) return 6;
if(x <= 9 && y <= 3) return 7;
if(x <= 9 && y <= 6) return 8;
if(x <= 9 && y <= 9) return 9;
}
inline void dfs(int x,int y){
if(fl == 1) return;
if(x > 9) { pr(); fl = 1; return; }
if(a[x][y] != 0){
if(y == 9) dfs(x+1,1);
else dfs(x,y+1);
}
else{
int p = pos(x,y);
rep(i,1,9){
if(vish[x][i] || visl[y][i] || vis[p][i]) continue;
vish[x][i] = visl[y][i] = vis[p][i] = 1;
a[x][y] = i;
if(y == 9) dfs(x+1,1);
else dfs(x,y+1);
vish[x][i] = visl[y][i] = vis[p][i] = 0;
a[x][y] = 0;
}
}
}
signed main(){
while(1){
string s;
cin >> s;
if(s == "end") break;
init();
int l = 1,h = 0;
rep(i,0,81){
if(i%9 == 0) h++,l=1;
if(s[i]>='1' && s[i]<='9'){
a[h][l] = s[i] - '0';
vish[h][a[h][l]] = visl[l][a[h][l]] = vis[pos(h,l)][a[h][l]] = 1;
}
l++;
}
dfs(1,1);
}
return 0;
}
【例题3】虫食算
这道题爆搜会 T L E TLE TLE,考虑如何进行剪枝。
由于是加法,进位最多只能是 1 1 1,记第一行的数为 a a a,第二行的数为 b b b,如果 a + b ≠ c a + b \ne c a+b=c 并且 a + b + 1 ≠ c a + b + 1 \ne c a+b+1=c,说明当前方案不合法,可以直接返回。
如果这一列三个数字已经确定,记当前情况进位为 j w jw jw,如果 a + b + j w ≠ c a + b + jw \ne c a+b+jw=c,同样也是不合法的,可以直接返回。
如果最高位有进位,显然也不合法,直接返回。
我们从右往左,从上往下枚举每一个字母可以填的数字,如果合法就继续往下填,最后判断即可。
注意,行和列别弄反了,由于是读入字符串,需要一行一行读,不能直接类似数组可以随意变 x x x 和 y y y 的位置。一开始传参的时候,行列传反了,导致 d e b u g debug debug 了很长时间。
#include
#include
#include
#include
#include
#include
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
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;
}
const int M = 30;
int n;
char s[M][M];
int vis[M],mp[M];
inline int change(char a) { return a - 'A' + 1; }
inline void pr() { rep(i,1,n) printf("%d ",mp[i]); }
inline void dfs(int x,int y,int jw){
if(x == 0){
if(jw == 0) { pr(); exit(0); }
return;
}
drep(i,x-1,1){
int w1 = mp[change(s[1][i])],w2 = mp[change(s[2][i])],w3 = mp[change(s[3][i])];
if(w1==-1 || w2==-1 || w3==-1) continue;
if((w1+w2)%n != w3 && (w1+w2+1)%n != w3) return;
}
if(mp[change(s[y][x])] == -1){
drep(i,n-1,0){
if(!vis[i]){
if(y != 3){
mp[change(s[y][x])] = i;
vis[i] = 1;
dfs(x,y+1,jw);
mp[change(s[y][x])] = -1;
vis[i] = 0;
}
else{
int w = mp[change(s[1][x])] + mp[change(s[2][x])] + jw;
if(w%n != i) continue;
mp[change(s[3][x])] = i;
vis[i] = 1;
dfs(x-1,1,w/n);
mp[change(s[3][x])] = -1;
vis[i] = 0;
}
}
}
}
else{
if(y != 3) dfs(x,y+1,jw);
else{
int w = mp[change(s[1][x])] + mp[change(s[2][x])] + jw;
if(w%n != mp[change(s[3][x])]) return;
dfs(x-1,1,w/n);
}
}
}
signed main(){
n = read();
rep(i,1,3) scanf("%s",s[i]+1);
memset(mp,-1,sizeof(mp));
dfs(n,1,0);
return 0;
}
1. 1. 1. 生日蛋糕
一开始没太理解题目,看懂之后,就是一个搜索加上剪枝。
( 1 ) (1) (1) 当前体积 v v v 加上前面层的最小体积 > n > n >n,可以直接返回。
( 2 ) (2) (2) 当前表面积 s s s 加上前面层的最小侧面积 > > > 已经搜到的答案,可以直接返回。
( 3 ) (3) (3) 倒着循环更快。
( 4 ) (4) (4) 我们每次枚举 r r r 和 h h h,不需要从 1 1 1 枚举到 n n n,由于要小于上一层,可以直接从一个区间内转移。
#include
#include
#include
#include
#include
#include
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
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;
}
const int M = 1e4+10;
int n,m,ans = 1e9;
inline void dfs(int dep,int s,int v,int prer,int preh){
if(s >= ans) return;
if(dep == m+1 && v == n){
ans = min(ans,s);
return;
}
if(v >= n) return;
int k = m-dep+1;
if(k * prer * prer * preh + v < n) return;
if(k * 2 + s > ans) return;
if(dep == 1) { drep(r,prer,m) rep(h,m,preh) dfs(dep+1,s+r*r+2*r*h,v+r*r*h,r,h); }
else { drep(r,prer-1,m-dep+1) rep(h,m-dep+1,preh-1) dfs(dep+1,s+2*r*h,v+r*r*h,r,h); }
}
signed main(){
n = read(),m = read();
dfs(1,0,0,(int)sqrt(n),n);
printf("%d\n",ans);
return 0;
}
2. 2. 2. 最大费用
如果 n ≤ 20 n \leq 20 n≤20,就是一个很简单的爆搜。但这道题 n ≤ 40 n \leq 40 n≤40,需要进行剪枝。我们注意到,选第 i i i 个物品和选第 j j j 个物品互不影响,所以我们可以进行分治。前 n 2 \frac{n}{2} 2n 个物品进行一次 d f s dfs dfs,后 n − n 2 n - \frac{n}{2} n−2n 个物品进行一次 d f s dfs dfs,最后把两个数组拼凑成答案。
我们可以将两个数组从小到大排序。对于第一个数组 f 1 f1 f1,从 f 2 f2 f2 中找到 ≤ m \leq m ≤m 的最大位置,可以用 u p p e r upper upper_ b o u n d bound bound 实现,将这两个数加一起更新答案。
#include
#include
#include
#include
#include
#include
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
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;
}
const int M = 1e5+10;
int a[M],b[M],f1[M],f2[M];
int n,m,n1,n2,ans,cnt1,cnt2;
inline void dfs1(int cur,int x,int sum){
if(sum > m) return;
if(cur == n1+1){
f1[++cnt1] = sum;
return;
}
dfs1(cur+1,x+1,sum+a[cur]);
dfs1(cur+1,x,sum);
}
inline void dfs2(int cur,int x,int sum){
if(sum > m) return;
if(cur == n2+1){
f2[++cnt2] = sum;
return;
}
dfs2(cur+1,x+1,sum+b[cur]);
dfs2(cur+1,x,sum);
}
signed main(){
n = read(),m = read();
n1 = n>>1,n2 = n-n1;
rep(i,1,n1) a[i] = read();
dfs1(1,0,0);
rep(i,1,n2) b[i] = read();
dfs2(1,0,0);
sort(f1+1,f1+cnt1+1);
sort(f2+1,f2+cnt2+1);
rep(i,1,cnt1){
int x = upper_bound(f2+1,f2+cnt2+1,m-f1[i])-f2-1;
ans = max(ans,f1[i] + f2[x]);
}
printf("%d\n",ans);
return 0;
}
3. 3. 3. 骑士精神
#include
#include
#include
#include
#include
#include
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
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;
}
const int M = 10;
const int dx[] = {1,1,-1,-1,2,2,-2,-2};
const int dy[] = {2,-2,2,-2,1,-1,1,-1};
int mp[M][M];
int T,sx,sy,fl,success;
const int goal[7][7] = {
{0,0,0,0,0,0},
{0,1,1,1,1,1},
{0,0,1,1,1,1},
{0,0,0,2,1,1},
{0,0,0,0,0,1},
{0,0,0,0,0,0},
};
inline int evaluate(){
int cnt = 0;
rep(i,1,5) rep(j,1,5) if(mp[i][j] != goal[i][j]) cnt++;
return cnt;
}
inline bool check(int x,int y){
if(x<1 || y<1 || x>5 || y>5) return false;
return true;
}
inline void dfs(int dep,int x,int y,int mx){
if(dep == mx){
if(!evaluate()) success = 1;
return;
}
rep(i,0,7){
int xx = x + dx[i];
int yy = y + dy[i];
if(!check(xx,yy)) continue;
swap(mp[xx][yy],mp[x][y]);
if(evaluate() + dep <= mx) dfs(dep+1,xx,yy,mx);
swap(mp[xx][yy],mp[x][y]);
}
}
inline void work(){
fl = success = 0;
rep(i,1,5) rep(j,1,5){
char ch;
cin >> ch;
if(ch == '*') mp[i][j] = 2,sx = i,sy = j;
else mp[i][j] = ch - '0';
}
if(!evaluate()) { puts("0"); return; }
rep(step,1,15){
dfs(0,sx,sy,step);
if(success) { printf("%d\n",step); fl = 1; break; }
}
if(!fl) puts("-1");
}
signed main(){
T = read();
while(T--) work();
return 0;
}
4. 4. 4. 折纸问题
由于有翻转和翻折操作,我们只需要枚举前半部分的折线,因为后半部分的折线可以通过先翻转再翻折达到同样的效果。
#include
#include
#include
#include
#include
#include
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
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;
}
const int M = 30;
int T,n,m,pd;
int a[M],b[M],c[M];
inline void dfs(int len,int reverse){
if(pd) return;
if(len == m){
bool f1=1,f2=1;
rep(i,1,len){
if(f1 || f2){
if(a[i] != b[i]) f1 = false;
if(a[i] != c[i]) f2 = false;
}
else break;
}
if(f1 || f2) pd = true;
return;
}
int d[M];
rep(i,1,len) d[i] = a[i];
int mid = (len+1)>>1,p = (mid > m) ? mid : m;
rep(i,p,len-1){
int l = i,r = i+1;
while(r<=len && l>=1) a[l--] += a[r++];
dfs(i,0);
rep(j,1,len) a[j] = d[j];
}
if(!reverse){
rep(i,1,len) a[i] = d[len-i+1];
dfs(len,1);
}
}
inline void work(){
pd = 0;
rep(i,1,n) a[i] = read();
m = read();
rep(i,1,m) b[i] = read();
rep(i,1,m) c[i] = b[m-i+1];
dfs(n,0);
if(pd) puts("S");
else puts("N");
}
signed main(){
while(scanf("%d",&n) != EOF) work();
return 0;
}
5. 5. 5. 最多约数
如果不知道算数基本定理,做这道题可能有点困难。
具体的定理就是,一个正整数 N N N 可以唯一分解为有限个质数的乘积 N = p 1 c 1 p 2 c 2 ⋅ ⋅ ⋅ p m c m N = p_1^{c_1} p_2^{c_2} ··· p_m^{c_m} N=p1c1p2c2⋅⋅⋅pmcm,期中 c i c_i ci 都是正整数, p i p_i pi 都是质数。那么 N N N 的约数个数为 ∏ i = 1 m ( c i + 1 ) \prod_{i=1}^m(c_i+1) ∏i=1m(ci+1)。
这道题我们可以枚举质数,记录每一个质数选了几次,根据算数基本定理来求出因数个数。
#include
#include
#include
#include
#include
#include
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
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;
}
int n,ans,cnt;
const int len = 15;
int a[len] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47};
inline void dfs(int cur,int mul,int mx,int tot,int sum){
int res = sum * (tot+1);
if(res > cnt || (res == cnt && mul < ans)) ans = mul,cnt = res;
if(cur+1 <= mx && a[cur] * mul <= n) dfs(cur,mul*a[cur],mx,tot+1,sum);
if(a[cur+1] * mul <= n) dfs(cur+1,mul*a[cur+1],mx,1,sum*(tot+1));
}
signed main(){
n = read();
dfs(0,1,50,0,1);
printf("%lld\n",ans);
return 0;
}