题目链接
[蓝桥杯 2014 省 AB] 地宫取宝
题目描述
X 国王有一个地宫宝库。是 n × m n \times m n×m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。
地宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是 k k k 件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k k k 件宝贝。
输入格式
输入一行 3 3 3 个整数,用空格分开: n n n, m m m, k ( 1 ≤ n , m ≤ 50 , 1 ≤ k ≤ 12 ) k(1 \le n,m \le 50,1 \le k \le 12) k(1≤n,m≤50,1≤k≤12)。
接下来有 n n n 行数据,每行有 m m m 个整数 C i ( 0 ≤ C i ≤ 12 ) C_i(0 \le C_i \le 12) Ci(0≤Ci≤12) 代表这个格子上的宝物的价值。
输出格式
要求输出一个整数,表示正好取 k k k 个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 ( 1 0 9 + 7 ) 1000000007(10^9+7) 1000000007(109+7) 取模的结果。
样例 #1
样例输入 #1
2 2 2
1 2
2 1
样例输出 #1
2
样例 #2
样例输入 #2
2 3 2
1 2 3
2 1 5
样例输出 #2
14
提示
时限 1 秒, 256M。蓝桥杯 2014 年第五届省赛
题目分析:
思路一:
我们可以进行记忆化搜索,通过dfs+剪枝的操作理论上时间复杂度是不会超时的
思路二:
考虑dp。
我们只能向右或者向下行走,那么我们的状态只能有两种递推过来,从左边或者从上推过来,对于每一个位置,我们都应该考虑两个状态,size(宝贝的最大值),sum(已经取了的宝贝个数),
//dp[x][y][size][sum];
//x代表x坐标 y代表y坐标 size代表当前宝贝的最大值 sum代表宝贝的个数
int dp[55][55][15][15];
这是我创建的dp数组,四维数组,考虑到了坐标和两个状态。
创建以后就该考虑递推公式了,对于任意位置的宝贝,我们只有两种情况,拿还是不拿,不拿的话就是继承左右的数据,拿的情况比较复杂,我们需要先判断是否能不能拿下,如果能拿下,那么拿下的递归公式又是什么?
//有一个重点,题目要求的是各自的宝贝价值比小明手中任意宝贝价值都高才能拿下他,题目中宝贝的价值>=0,我们应该将宝贝的价值+1,然后0代表手上没有宝贝,否则对于0价值的宝贝,我们无法拿下。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+10;
const int mod = 1e9+7;
//dp[x][y][size][sum];
//x代表x坐标 y代表y坐标 size代表当前宝贝的最大值 sum代表宝贝的个数
int dp[55][55][15][15];
int sum[55][55];
int n,m,k,he;
int main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>sum[j][i];
sum[j][i]++;
}
}
dp[0][1][0][0]=1;
// dp[1][0][0][0]=1;
for(int x=1;x<=m;x++){
for(int y=1;y<=n;y++){
//size表示上一个位置宝贝的最大大小
for(int size=0;size<=12;size++){
//ans表示宝贝的个数
for(int ans=0;ans<=k;ans++){
//不拿这个宝贝
dp[x][y][size][ans] += (dp[x-1][y][size][ans]%mod+dp[x][y-1][size][ans]%mod)%mod;
dp[x][y][size][ans]%=mod;
//如果能拿这个宝贝
if(sum[x][y]>size) {
dp[x][y][sum[x][y]][ans+1] += (dp[x-1][y][size][ans]%mod+dp[x][y-1][size][ans]%mod)%mod;
dp[x][y][sum[x][y]][ans+1]%=mod;
}
}
}
}
}
for(int i=1;i<=12;i++){
he+=dp[m][n][i][k]%mod;
he%=mod;
}
cout<<he%mod;
return 0;
}
妖梦拼木棒
题目背景
上道题中,妖梦斩了一地的木棒,现在她想要将木棒拼起来。
题目描述
有 n n n 根木棒,现在从中选 4 4 4 根,想要组成一个正三角形,问有几种选法?
答案对 1 0 9 + 7 10^9+7 109+7 取模。
输入格式
第一行一个整数 n n n。
第二行往下 n n n 行,每行 1 1 1 个整数,第 i i i 个整数 a i a_i ai 代表第 i i i 根木棒的长度。
输出格式
一行一个整数代表答案。
样例 #1
样例输入 #1
4
1
1
2
2
样例输出 #1
1
提示
数据规模与约定
- 对于 30 % 30\% 30% 的数据,保证 n ≤ 5 × 1 0 3 n \le 5 \times 10^3 n≤5×103。
- 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 5 1 \leq n \le 10^5 1≤n≤105, 1 ≤ a i ≤ 5 × 1 0 3 1 \le a_i \le 5 \times 10^3 1≤ai≤5×103。
题目思路:
题目初看很简单,不就是分别枚举四个木棒的长度然后判断是否能成正三角形嘛,但是这样时间复杂度就是O(n^4),这样肯定会超时。
那么我们可以从另一个角度思考,ai的值在1~5e3之间,又可以知道我们所能拼出来的最大正三角形边长肯定是ai的最大值(大于需要两个木棒,但这样就无法用四根木棒拼出来),从这个角度思考,我们可以先确定两个木棒是不需要拼接的,然后剩下两个木棒拼接成大木棒,但这样我们任然需要枚举木棒。
我们又可以换位思考,在输入木棒的长度时,我们可以先进行存储,存储对应长度的木棒有多少根,那么我们就不需要枚举,直接知道答案了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e3+10;
const int mod = 1e9+7;
//枚举边的长度
//用map也行
int n,sum[N],maxn,ans;
int main(){
cin>>n;
for(int i=1,t;i<=n;i++){
cin>>t;
sum[t]++;
maxn=max(t,maxn);
}
//最大的边长就是木棒的最长边 不会出现大于其长度的边长
//枚举边长
for(int i=2;i<=maxn;i++){
if(sum[i]<2) continue;
for(int j=1;j<=i/2;j++){
int t=0;
if(sum[j]<1 || sum[i-j]<1) continue;
if(j==i-j) t=sum[j]*(sum[j]-1)/2;
else t=sum[j]*sum[i-j];
ans+=sum[i]*(sum[i]-1)*t/2;
ans%=mod;
}
}
cout<<ans%mod;
return 0;
}
//C A
//
[SCOI2005] 互不侵犯
题目描述
在 N × N N \times N N×N 的棋盘里面放 K K K 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 8 8 个格子。
输入格式
只有一行,包含两个数 N , K N,K N,K。
输出格式
所得的方案数
样例 #1
样例输入 #1
3 2
样例输出 #1
16
提示
数据范围及约定
对于全部数据, 1 ≤ N ≤ 9 1 \le N \le 9 1≤N≤9, 0 ≤ K ≤ N × N 0 \le K \le N\times N 0≤K≤N×N。
upd 2018.4.25 \text{upd 2018.4.25} upd 2018.4.25:数据有加强。
算法思路:
这题跟N皇后的题面很像,不是搜索就是dp,搜索肯定不行,我们考虑dp,我们可以一行一行考虑,对于国王来说,只会影响上下两行,那我们就以行为状态分析
三个状态,第几行,这行如何放,总共放了多少个国王。
我们可以发现,第几行和总共放了多少个国王都好理解,这行如何放该如何理解,那么我们就可以考虑状态压缩,对于第几行的国王方法通过二进制的方式来存储,例如1010(2),就是说明第一个格子,第三个没有国王,第二个格子,第四个格子有国王。(注意,二进制和格子的顺序是反着的)。
这样梳理以后,我们就可以先用dfs将一行所有国王的方法都列举出来并进行存储,然后再判断第i行该如何放国王。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,k,cnt,sit[2020],gs[2020];
//第i行放j(是下标 如果用状态存储数据过大)的情况下用了k个棋子的方案数
ll f[15][2020][110];
void dfs(int he,int size,int node){
if(node>=n){
sit[++cnt]=he;
gs[cnt]=size;
return;
}
dfs(he,size,node+1);
dfs(he+(1<<node),size+1,node+2);
}
int main(){
cin>>n>>k;
dfs(0,0,0);
for(int i=1;i<=cnt;i++) f[1][i][gs[i]]=1;
for(int i=2;i<=n;i++){
//j和tk表示的是i行和i-1行的关系
for(int j=1;j<=cnt;j++){
for(int tk=1;tk<=cnt;tk++){
if(sit[j]&sit[tk]) continue;
if((sit[j]<<1)&sit[tk]) continue;
if(sit[j]&(sit[tk]<<1)) continue;
//枚举s的情况
for(int s=k;s>=gs[j];s--)
f[i][j][s]+=f[i-1][tk][s-gs[j]];
}
}
}
ll ans=0;
for(int i=1;i<=cnt;i++)
ans+=f[n][i][k];
cout<<ans;
return 0;
}
初学状态压缩dp,状压dp本质上就是将一个块(状态)压缩成一个点(二进制表达),然后就与其他dp没有什么区别了,但需要注意,状压dp的计算有可能会考虑到二进制的计算(需要重点复习一下)