P2196 [NOIP1996 提高组] 挖地雷
题目链接
难度:普及/提高-
一. 思路简述:
本题也不难,就是细节问题又坑了我好久。
1. “状态”的初步确定:
本题“状态”下的值是最大地雷数,而影响该值的“状态变量”显然就是每个地窖的地雷数和连通情况。另外一个“状态”属性是每个地窖的序号。
2. 子问题的分解:
本问题看上去可能挺复杂,首先起始点不确定,可以从任意地窖开始挖。其次每个地窖的连通情况是不同的。如果不认为规定起始点的分析顺序,显然是很难做的。
这里首先要明确递归本质上就是一种暴力穷举,并且递归可以用树或森林的形式表示。而动规就是“剪枝”/从森林中选择最优树,只取最优的分支。顺着这个思路,我们很容易想到本题中子节点就是分别以每个地窖作为最终点/起始点的树,而所谓具有最优结果的子问题分解就是先穷举出所有树的最大地窖数,再剪枝就可以了。
这种思路的问题是,而我们之前做的题不同,原问题与子问题的关系不再一棵树中双亲结点和孩子节点的关系,而是森林与树的关系。这在递归时有些不方便,在每次递推中都涉及循环遍历。但是以递推形式来写时仍是二重循环,相对递归形式简单很多。
3. 状态转移方程:
正如前面所说,本题我采用的思路中,原问题与子问题的关系是森林与树的关系,递推时很清楚,递归时反而麻烦,完全可以不显式写出方程直接递推。除了子树的递归问题,还涉及森林的选择问题(以for循环遍历实现)。故这里直接看代码。
三. 代码:
没有进行优化,写的比较乱
1. 记忆递推型:
#include <bits/stdc++.h>
#define LEN 20
using namespace std;
long dp[LEN+1][LEN+1] = {0};
void init(int n){
for (int i = 0; i <= LEN;i++)
{
for (int j = 0; j <= LEN;j++)
dp[i][j] = 0;
}
for (int i = 0; i < n;i++)
for (int j = 1; j <= n-i;j++)
cin >> dp[i][j];
}
long num(int n){
if(n==1&&dp[n][0]==0)
{
dp[n][0] = dp[0][n];
}
else if(dp[n][0]==0)
{
for (int i = 1; i<n;i++)
{
if(dp[i][0]==0)
num(i);
if(dp[i][n-i])
{
if(dp[i][0]>dp[n][0])
{
dp[n][0] = dp[i][0];
dp[n][LEN] = i;
}
}
}
dp[n][0] += dp[0][n];
}
return dp[n][0];
}
int main(){
int n;
while(cin>>n)
{
init(n);
num(n);
int m =0,k=0,i=1;
for (int i = 1; i <= n;i++)
if (dp[i][0]>m)
{
m = dp[i][0];
k = i;
}
m = dp[k][LEN];
while (m)
{
dp[0][i++] = m;
m = dp[m][LEN];
}
while(i>1)
cout << dp[0][--i] << " ";
cout << k << endl;
cout << dp[k][0]<<endl;
}
return 0;
}
2. “人人为我”型:
#include <bits/stdc++.h>
#define LEN 20
using namespace std;
long dp[LEN+1][LEN+1] = {0};
void init(int n){
for (int i = 0; i <= LEN;i++)
{
for (int j = 0; j <= LEN;j++)
dp[i][j] = 0;
}
for (int i = 0; i < n;i++)
for (int j = 1; j <= n-i;j++)
cin >> dp[i][j];
}
int main(){
int n;
while(cin>>n)
{
init(n);
dp[1][0] = dp[0][1];
for (int i = 2; i <= n;i++)
{
for (int j = 1; j < i;j++)
{
if(dp[j][i-j])
{
if(dp[j][0]>dp[i][0])
{
dp[i][0] = dp[j][0];
dp[i][LEN] = j;
}
}
}
dp[i][0] += dp[0][i];
}
int m = 0, k = 0, i = 1;
for (int i = 1; i <= n;i++)
if (dp[i][0]>m)
{
m = dp[i][0];
k = i;
}
m = dp[k][LEN];
while (m)
{
dp[0][i++] = m;
m = dp[m][LEN];
}
while(i>1)
cout << dp[0][--i] << " ";
cout << k << endl;
cout << dp[k][0]<<endl;
}
return 0;
}
3. “我为人人”型:
#include <bits/stdc++.h>
#define LEN 20
using namespace std;
long dp[LEN+1][LEN+1] = {0};
void init(int n){
for (int i = 0; i <= LEN;i++)
{
for (int j = 0; j <= LEN;j++)
dp[i][j] = 0;
}
for (int i = 0; i < n;i++)
for (int j = 1; j <= n-i;j++)
cin >> dp[i][j];
}
int main(){
int n;
while(cin>>n)
{
init(n);
for (int i = 1; i <= n;i++)
{
dp[i][0] += dp[0][i];
for (int j = 1; j <= n-i;j++)
{
if(dp[i][j])
{
if(dp[i+j][0]<dp[i][0])
{
dp[i+j][0] = dp[i][0];
dp[i+j][LEN] = i;
}
}
}
}
int m = 0, k = 0, i = 1;
for (int i = 1; i <= n;i++)
if (dp[i][0]>m)
{
m = dp[i][0];
k = i;
}
m = dp[k][LEN];
while (m)
{
dp[0][i++] = m;
m = dp[m][LEN];
}
while(i>1)
cout << dp[0][--i] << " ";
cout << k << endl;
cout << dp[k][0]<<endl;
}
return 0;
}
“我为人人”型和“人人为我”型各有自身特点且差距不大,本题感觉“我为人人”型更简单。