目录
1.P1062 [NOIP2006 普及组]高低位转换
自猜:二进制存储,翻转数组,二进制转化十进制后输出。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
using namespace std;
int main()
{
int a[35];
long long m;
string s;
cin>>m;
memset(a,0,sizeof(a));
while(m)
{
s=char(m%2+'0')+s;
m/=2;
}
int n=s.length();
for(int i=n-1,j=31;i>=0;i--,j--)
a[j]=s[i]-'0';
for(int i=15;i>=0;i--)
swap(a[i],a[i+16]);
long long sum=0;
for(int i=31;i>=0;i--)
if(a[i])
sum+=pow(2,31-i);
cout<<sum<<endl;
return 0;
}
评价:麻烦,多余的代码量,运行时间过长。
官解:使用位运算操作,将原数据与运算后左移右移,进行或运算输出结果。
#include<cstdio>
#include<iostream>
using namespace std;
int main()
{
unsigned long long x;
cin>>x;
cout<<((x&0x0000ffff)<<16|(x&0xffff0000)>>16)<<endl;//万无一失的做法
}
评价:简洁,高效,难以置信。
2.P1106 删数问题
自猜:无想法,认为先删大的,但很容易举出反例,不合理。
官解:观察删除数据可知,删除比后位大的数据,类似山峰时先删除山峰,而且越靠近前面的山峰越早删除,但有一点,注意出现前导0的情况,要删除0。
#include<iostream>
using namespace std;
int main()
{
string s;
int k;
cin>>s>>k;
int n=s.length();
while(k--)
{
for(int i=0;i<=n-2;i++)
{
if(s[i]>s[i+1])
{
s.erase(i,1);
break;
}
}
n--;
//这里注意若出现大于情况,就是正常长度减一
//如果没有出现大于情况,那么就是要删掉最后一位
//长度减一正常删掉
}
int j=0;
while(j<n&&s[j]=='0') j++;
if(j==n) cout<<"0";
else
for(int i=j;i<n;i++)
cout<<s[i];
cout<<endl;
return 0;
}
评价:要对于数据有足够的敏感度,观察数据被删除掉的规律,总结并修改,注意其中出现过的特殊情况。
3.P1138 第k小整数
自猜:使用set处理数据,完美解决去除重复数据和排序问题,已无需更改。
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int>s;
int n,k;
int temp;
cin>>n>>k;
for(int i=0;i<n;i++)
{
cin>>temp;
s.insert(temp);
}
if(k>s.size()) cout<<"NO RESULT"<<endl;
else
{
int i=1;
set<int>::iterator it;
for(it=s.begin(),i=1;it!=s.end();it++,i++)
if(i==k) cout<<*it<<endl;
}
return 0;
}
评价:比较简单的题目,无需过多思考,简洁做法解决问题。
4.P1025 [NOIP2001 提高组] 数的划分
自猜:使用先保证每个里面都有数,然后进行划分,但是没有想出来具体的方法。
官解:
方法一:动态规划(递推)
对于当前情况,转化为小球放进盒子内,盒子最终无空闲的情况,对此可分为两种情况组合而成:
1).如果场上至少有一个盒子内有一个小球,其余小球自由放入其余盒子内部,近似于将n-1个小球放入k-1个盒子内,保证无空盒子;
2).如果场上没有一个盒子有一个小球,那么先给每个盒子装进一个小球,后将n-k个小球装入k个盒子中,保证每个盒子最少为2个小球;
(面对这种写法是可以写出两种情况的递推表达式,进而建立本题的动态转移方程)
我们假设f[n][k]是将n个小球存入k个盒子中,且无空盒出现,则:
f[n][k]=f[n-1][k-1]+f[n-k][k](后面的建立在n>k的情况下)
对数据进行初始化确定边界
可发现当n==k时,f[n][k]=1;
当k==1ork==0时,f[n][k]=1;
当n<k时,f[n][k]=0;
代码如下:
#include<iostream>
using namespace std;
int main()
{
int n,k,a[205][10];
cin>>n>>k;
for(int i=1;i<=n;i++)
{
a[i][1]=1;
a[i][0]=1;
}
for(int i=2;i<=k;i++)
{
a[1][i]=0;
a[0][i]=0;
}
for(int i=2;i<=n;i++)
for(int j=2;j<=k;j++)
if(i>j) a[i][j]=a[i-1][j-1]+a[i-j][j];
else a[i][j]=a[i-1][j-1];
cout<<a[n][k];
return 0;
}
评价:把问题抽象出基本情况,试图使用数学语言概况总结,其中一定要通过寻找正确的状态分析出动态转移方程,这样这类题就可以说成功了一半。
方法二:dfs+剪枝
方法三:母函数(生成函数)
5.P1037 [NOIP2002 普及组] 产生数
自猜:统计每个数在输入数据中有多少个,出现变成其他的数就进行统计,但样例只过了一个。
评价:原因在于忽视一类情况,例:
1->2
2->3
则1->3同样成立
同时本题数据过大,使用高精度算法。
官解:
方法一:使用Floyd+高精度,先确定每个数究竟能变成多少个其他数字,然后进行逐项比较相乘,高精度乘低精度求出最终结果。
#include<iostream>
#include<string>
using namespace std;
string str;
int k,vis[10][10],f[10],num[101];
void floyd()//弗洛伊德算法求出一个数可以有多少种变化情况
{
for(int k=0;k<=9;k++)
for(int i=0;i<=9;i++)
for(int j=0;j<=9;j++)
vis[i][j] = vis[i][j] || (vis[i][k] && vis[k][j]) ;
}
int main()
{
cin>>str>>k;
int a,b;
while(k--)
{
cin>>a>>b;
vis[a][b] = true;
}
for(int i=0;i<=9;i++) vis[i][i]=true; //一个数可以变成自己本身
floyd();
for(int i=0;i<=9;i++)
for(int j=0;j<=9;j++)
if(vis[i][j]) f[i]++;//统计一个数能变成多少数(包括自己本身)
int len=2;//默认最终结果长度
num[1]=1;//如果没有任何数字可以发生改变,那么结果就是1,只有原数
int n=str.length();
for(int i=0;i<n;i++)
{
for(int j=1;j<=100;j++) num[j]*=f[str[i]-'0'];//每一项乘当前位置数可变数量
for(int j=1;j<=100;j++)
if(num[j]>=10)
{
num[j+1]+=num[j]/10;
num[j]%=10;
} //进行进位操作
while(num[len]) len++; //统计最终结果长度,便于输出
}
for(int i=len-1;i>=1;i--) cout<<num[i];
cout<<endl;
return 0;
}
评价:该解法将看似和图论毫不相关的题目与Floyd相联系,展现了算法的相通,对思维是一种新的拓展,但由于Floyd算法时间复杂度过高,当输入数据>500时将不能满足正常解题,该解法面对小输入情况可以说非常精妙了。
方法二:深搜+高精度
6.P1045 [NOIP2003 普及组] 麦森数
自猜:首先猜测使用高精度累乘判断位数,输出最后500位结果,然后发现数据过大,遍历必超时,猜测存在公式判断2^n-1位数,再猜测本题使用快速幂缩短运算时间,综合判断是一道高精快速幂题目。
评价:猜测正确,但代码能力不足,对于高精度乘法的理解力不足,无法自己敲出代码,对于此类答案要记忆。
官解:高精度+快速幂
位数判断:
假设2^n位数为k,由于2^n-1尾数不为0,则位数仍为k
我们可知10^n的位数为n+1
对于2^n如果我们也能将其转化为以10为底的数,那么指数再加1就是位数k了
易知10^=2,则k=p*。
计算过程:
使用高精度+快速幂,注意一点,快速幂在进行高精度运算时,不止底数进行高精度运算,指数同样进行高精度运算,同时面对题目要求,只计算后500位就可以了。
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
long long p,n,res[1001],sav[1001],f[1001];
void maze_1()//当指数为奇数时,我们计算底数*最终结果,并将结果赋给储存输出数组
{
memset(sav,0,sizeof(sav));
for(int i=1;i<=500;i++)
for(int j=1;j<=500;j++)
sav[i+j-1]+=res[i]*f[j];
for(int i=1;i<=1000;i++)
{
sav[i+1]+=sav[i]/10;
sav[i]=sav[i]%10;
}
for(int i=0;i<=1000;i++)
res[i]=sav[i];
}
void maze_2()//指数/2时,底数*底数,将结果赋给底数数组
{
memset(sav,0,sizeof(sav));
for(int i=1;i<=500;i++)
for(int j=1;j<=500;j++)
sav[i+j-1]+=f[i]*f[j];
for(int i=1;i<=1000;i++)
{
sav[i+1]+=sav[i]/10;
sav[i]=sav[i]%10;
}
for(int i=0;i<=1000;i++)
f[i]=sav[i];
}
int main()
{
cin>>p;
n=(int)(log10(2)*p)+1;//公式直接得出结果位数
res[1]=1;//这个储存最终结果,奇数时底数相乘
f[1]=2;//初始化,这个数组用来存储底数,偶数时,底数自乘
cout<<n<<endl;
while(p)
{
if(p&1) maze_1();
p>>=1;
maze_2();
}
res[1]--;
for(int i=500;i>=1;i--)
{
if(i%50==0&&i<500) cout<<endl;
cout<<res[i];
}
return 0;
}
评价:要明确在快速幂中,使用高精度的位置,而且要明确不同数组是存储底数还是存储最终结果,可以记忆一下这个模板。
7.P1159 排行榜
自猜:首先猜测为结构体排序,因为没有看到题目说明这个输入对应着现在的榜单,只想着上升就下降回去,下降就上升回去,交了一发,全wa;
后来想了想发现题目说明,在排序过程中应该不能移动位置不变的歌,将上升与下降的歌曲进行交换操作,交了一发,过了一个样例点(共十个);
再次思考,发现这个题不要使用排序方法,使用数组分开储存上升,下降的歌曲,使用最终答案数组储存不变歌曲,然后将下降歌曲全部填充进答案数组前部分空缺中,上升歌曲填入后半部分,输出即可,交了一发,AC。
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
string a[105],b[105],c[105];
int a1=0,b1=0;
int main()
{
int n;
cin>>n;
string temp,tamp;
for(int i=0;i<n;i++)
{
cin>>temp>>tamp;
if(tamp=="UP")
{
a[a1]=temp;
a1++;
}
else if(tamp=="DOWN")
{
b[b1]=temp;
b1++;
}
else
{
c[i]=temp;
}
}
int sum=0,svm=0;
for(int i=0;i<n;i++)
{
if(c[i]!="") continue;
else
{
if(sum<b1)
{
c[i]=b[sum];
sum++;
}
else
{
c[i]=a[svm];
svm++;
}
}
}
for(int i=0;i<n;i++) cout<<c[i]<<endl;
return 0;
}
评价:读题不够认真,这算一道很水的模拟题,题意不难,认真一点即可。
官解:面对数组代码过长,可以使用STL 中queue来储存数据,原理相同。
#include<bits/stdc++.h>
using namespace std;
string name[101],dos;
queue<int> fr,en;
int n;bool sa[101];
int main(){
cin>>n;for(int i=1;i<=n;i++){
cin>>name[i]>>dos;
if(dos=="UP")en.push(i);
if(dos=="DOWN")fr.push(i);
if(dos=="SAME")sa[i]=1;
}
for(int i=1;i<=n;i++){
if(sa[i]==1)cout<<name[i]<<endl;
else{
if(!fr.empty()){cout<<name[fr.front()]<<endl;fr.pop();}
else if(!en.empty()){cout<<name[en.front()]<<endl;en.pop();}
}
}
return 0;
}
8.P1869 愚蠢的组合数(涉及组合数公式)
自猜:面对较大数据,可以推测一定是有公式完成对奇偶的直接判断,搜索组合数学,找到了这个判断条件。
若C(n,m)为奇数,则n&m==n;
以下为证明过程:(洛谷大佬证明方法)
利用数学归纳法:
由C(n,k) = C(n,k-1) + C(n-1,k-1);
对应于杨辉三角:
1
1 2 1
1 3 3 1
1 4 6 4 1
………………
可以验证前面几层及k = 0时满足结论,
下面证明在C(n-1,k)和C(n-1,k-1) (k > 0) 满足结论的情况下,C(n,k)满足结论. (分类讨论并反证)
1).假设C(n-1,k)和C(n-1,k-1)为奇数:
则有:(n-1)&k == k;
(n-1)&(k-1) == k-1;
由于k和k-1的最后一位(在这里的位指的是二进制的位,下同)必然是不同的,所以n-1的最后一位必然是1.
现假设n&k == k.
则同样因为n-1和n的最后一位不同推出k的最后一位是1. 因为n-1的最后一位是1,则n的最后一位是0,所以n&k != k,与假设矛盾.
所以得n&k != k.
2).假设C(n-1,k)和C(n-1,k-1)为偶数:
则有:(n-1)&k != k;
(n-1)&(k-1) != k-1;
现假设n&k == k.
则对于k最后一位为1的情况:
此时n最后一位也为1,所以有(n-1)&(k-1) == k-1,与假设矛盾.
而对于k最后一位为0的情况:
则k的末尾必有一部分形如:10; 代表任意个0.
相应的,n对应的部分为:1{···}···; ···代表0或1.
而若n对应的{···}···中只要有一个为1,则(n-1)&k == k成立,所以n对应部分也应该是10.
则相应的,k-1和n-1的末尾部分均为01,所以(n-1)&(k-1) == k-1 成立,与假设矛盾.
所以得n&k != k.
由1)和2)得出当C(n,k)是偶数时,n&k != k.
3).假设C(n-1,k)为奇数而C(n-1,k-1)为偶数:
则有:(n-1)&k == k;
(n-1)&(k-1) != k-1;
显然,k的最后一位只能是0,否则由(n-1)&k == k即可推出(n-1)&(k-1) == k-1.
所以k的末尾必有一部分形如:10;
相应的,n-1的对应部分为:1{···}···;
相应的,k-1的对应部分为:01;
则若要使得(n-1)&(k-1) != k-1 则要求n-1对应的{*}*中至少有一个是0.
所以n的对应部分也就为 :1{···}···; (不会因为进位变1为0)
所以 n&k = k.
4).假设C(n-1,k)为偶数而C(n-1,k-1)为奇数:
则有:(n-1)&k != k;
(n-1)&(k-1) == k-1;
分两种情况:
当k-1的最后一位为0时:
则k-1的末尾必有一部分形如:10;
相应的,k的对应部分为 :11;
相应的,n-1的对应部分为 :1{···}0; (若为1{···}1,则(n-1)&k == k)
相应的,n的对应部分为 :1{···}1;
所以n&k = k.
当k-1的最后一位为1时:
则k-1的末尾必有一部分形如:01; (前面的0可以是附加上去的)
相应的,k的对应部分为 :10;
相应的,n-1的对应部分为 :01; (若为11,则(n-1)&k == k)
相应的,n的对应部分为 :10;
所以n&k = k.
由3),4)得出当C(n,k)为奇数时,n&k = k.
综上,结论得证!
#include<iostream>
using namespace std;
int main()
{
long long n;
cin>>n;
while(n--)
{
long long a,b;
cin>>a>>b;
cout<<((a&b)==b?"1":"0")<<endl;
}
return 0;
}
评价:组合数学包涵甚广,内容很多,还要学很多。
9.P1865 A % B Problem(筛法+前缀和)
自猜:一定区域内判断素数个数问题,想到先将数据预处理,使用欧拉筛O(n)级别时间复杂度,筛完之后使用前缀和储存每个位置前素数个数,最终根据输入位置相减即可。
#include<iostream>
#include<cstring>
using namespace std;
const long long maxn=1e6+5;
int prime[maxn];
int visit[maxn];
long long ans[maxn];
void Prime()
{
memset(visit,0,sizeof(visit));
memset(prime,0,sizeof(prime));
for(int i=2;i<=maxn;i++)
{
if(!visit[i])
prime[++prime[0]]=i;
for(int j=1;j<=prime[0]&&prime[j]*i<=maxn;j++)
{
visit[prime[j]*i]=1;
if(i%prime[j]==0)
break;
}
}
ans[1]=0;
for(int i=2;i<=maxn;i++)
{
if(!visit[i]) ans[i]=ans[i-1]+1;
else ans[i]=ans[i-1];
}
}
int main()
{
long long n,m;
cin>>n>>m;
Prime();
// for(int i=1;i<=m;i++) cout<<visit[i]<<" "<<ans[i]<<endl;
while(n--)
{
long long l,r;
cin>>l>>r;
if(l<1||r>m) cout<<"Crossing the line"<<endl;
else cout<<(ans[r]-ans[l-1])<<endl;//注意这里,被卡了好几发,最后看题解才发现
}
return 0;
}
评价:欧拉筛在处理判断素数方面非常方便,多背模板。
官解:这道题目没有要求保存素数,只保存素数个数,使用正常埃氏筛也可以,在筛的过程中保存前缀和即可。
#include<bits/stdc++.h>
using namespace std;
int f[1000001];
bool vis[1000001];
void shai(int n)
{
f[1]=0;
vis[1]=true;
for(int i=2;i<=n;i++)
{
if(vis[i]==false) //在筛里进行前缀和
{
f[i]=f[i-1]+1;//前缀和计算
for(int j=i+i;j<=n;j=j+i)
{
vis[j]=true;//标记操作
}
}
else f[i]=f[i-1];//前缀和转移
}
}
int main()
{
int n,m;
scanf("%d%d",&m,&n);
shai(n);
for(int i=1;i<=m;i++)
{
int l,r;
scanf("%d%d",&l,&r);
if(l<1 || r>n) cout<<"Crossing the line"<<endl;//判断是否超出区间
else
{
int y=f[r]-f[l-1];//此处已经修改
cout<<y<<endl;
}
}
return 0;
}
评价:适当面对题目修改模板,用最简单的代码解决最复杂的问题。
10.P1890 gcd区间
自猜:感觉应该先储存好gcd数据,然后直接访问即可,但一时没有想到方法。
官解:很简单的一道题,直接暴力二维数组储存两个数之间的gcd,一位一位的往下找,例如知道了第一个和第二个数据间gcd,那么当前gcd和第三个数据间的gcd就是第一个和第三个数据间的gcd。
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
long long gcd(long long a,long long b)
{
return a==0?b:gcd(b%a,a);
}
long long ans[1005][1005],a[1005];
int main()
{
long long n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
ans[i][i]=a[i];
}
for(int i=1;i<n;i++)
{
for(int j=i+1;j<=n;j++)
{
ans[i][j]=gcd(ans[i][j-1],a[j]);
}
}
while(m--)
{
long long s,v;
scanf("%lld %lld",&s,&v);
printf("%lld\n",ans[s][v]);
}
return 0;
}
评价:简单的问题,但是自身代码能力不足,没能第一时间想出答案。
11.P1920 成功密码
自猜:观察题目想到了泰勒展开,后被提醒应该是无穷级数,寻找后发现这是-log(1.0-x)函数泰勒展开后的结果,该函数收敛,可求得极限为当前函数值,则超过极限值后的数据不需考虑,即循环终止条件。
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
int main()
{
double n,x;
scanf("%lf %lf",&x,&n);
double lim=-log(1.0-x);
double ans=0,s=1.0;
for(int i=1;i<=(int)n;i++)
{
s*=x;
ans+=1.0*s/i;
if(round(ans * 10000.0) >= round(lim * 10000.0)) break;
}
printf("%.4lf\n",ans);
return 0;
}
评价:对于这个题,最关键一步是想到题目中公式为函数泰勒展开式,如果实在未发现,其实可以强行循环两万次,不过这个题很巧妙,可以记忆一下。
12.P1134 [USACO3.2]阶乘问题
自猜:
考虑题目要求中只考虑不为零最右值,首先只考虑最后一位,每次取模10保留最后一位,同时发现每十位数据后最后的数字是重复的,所以对n/10后重复累乘最后一位,再对n%10的位数分别相乘,最终只过了一个样例点。
官解:
查找原因发现,对于
14!=87178291200,此时若只考虑最后一位2,那么15!保留位数就变成了2*15=30
但是15!=1307674368000,也就是最终结果为8,从这里就已经出现错误了
翻看洛谷题解找到对于这个的暴力解法,也就是保留的位数多一点,不止保留一位,那么这道题可以通过,但这种方法不是解决这道题的正确做法。
暴力代码:
#include<iostream>
using namespace std;
int main()
{
long long result=1;
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
result*=i;
while(result%10==0)
result/=10;
result=result%100000000;
}
cout<<result%10;
}
分析这个题,发现出现问题的原因在于末位的零的出现,若当前乘法过程中没有出现0,可以一直保存最后一位即可,且最右非零数只可能为2,4,6,8,这四个数在乘6的情况下,最后一位数都与原数保持不变,那么只考虑最后一位数的情况下,4*2*8=4*2*5,这样将所有的5全部变成8,由于多次乘8会出现一定规律,所以用个数组直接调用即可,膜拜洛谷大佬。
洛谷代码:
#include<iostream>
#include<cmath>
using namespace std;
int a[4]={6,8,4,2};
int ans=1,n;
int main()
{
cin>>n;
while(n>0)
{
for(int i=1;i<=n%10;i++)
{
if(i!=5) ans=ans*i%10;
}
n/=5;
ans=ans*a[n%4]%10;
}
cout<<ans<<endl;
return 0;
}
评价:这个题算是和数论有那么一点点关系,关键在于先分析最后的结果呈现什么规律,可以使用打表的方式,然后根据规律发现在什么情况下该结果会出现改变,把特殊情况单独提出来,然后转化为一般情况,这样得到最终结果,很不错的一道题。
13.(普及+/提高)P1835 素数密度
自猜:
面对又是求区间素数个数的题,首先想到打表然后前缀和保存,但是这个这个题首先数据范围直接让一般打表绝望,所以必须采取别的方法。
官解:
寻找评论区,找到了有关方法
首先合数存在一个性质:可以分解为两个不为1且不等于本身的因子相乘,即n=a*b(n为合数),这样可以引申出的关键题解为:一个合数必定存在一个1-n内的素因子,也就是说我们把1-n
内的素数保留下来,然后直接用这些素数去排除查询区间内的合数,然后保留下来的数就是区间内的素数。
注意这道题是要求查询区间的素数个数,那么对于当前以及排除掉的数字,我们直接让当前数字减去下区间的数组位置定为合数,避免了要开过大数组爆栈且资源浪费(只使用了查询区间内的数组空间),最后遍历该数组确定所有素数个数即可。
PS:这个题中还有一点细节,就是对于下区间为1的情况,这时候手动调整区间为2,因为在题中1同样被确定为素数,这样造成最终答案错误。
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=1e6+5;
int prime[maxn] , cnt=0, ans=0;
bool vis[maxn];
bool isPrime[maxn];
void Euler() {
for(int i = 2 ; i <= 50000 ; ++ i) {
if(!vis[i]) {
prime[++cnt] = i;
for(int j = i + i ; j <= 50000 ; j += i) {
vis[j] = 1;
}
}
}
}
long long _max(long long a,long long b)
{
return a>b?a:b;
}
int main()
{
int l,r;
cin>>l>>r;
l+=(l==1);
if(l>r)
{
cout<<"0"<<endl;
return 0;
}
Euler();
for(long long i=1;i<=cnt;++i)
{
for(long long j=max(2,(l-1)/prime[i]+1)*prime[i];j<=r;j+=prime[i])
if(j-l>=0) isPrime[j-l]=1;
}
for(long long i=l;i<=r;i++) if(!isPrime[i-l]) ans++;
cout<<ans<<endl;
return 0;
}
评价:题解区都说是一道比较基础的筛法问题,但是码力不足想不出来,算是记忆一下吧,然后对于这种稍稍变形的筛法以及合数的定理也很重要,多多记忆,多多学习。
14.P1004 [NOIP2000 提高组] 方格取数
自猜:
首先想到了深度搜索,后来发现标签里有动态规划,就开始思考状态转移方程,如果只走一遍,会发现只需要二维dp即可,走两次就是两边,第一次回溯然后把路径上数据清0,动手实践。
void collection()
{
number[1][1]=map[1][1];
for(int i=0;i<=n+1;i++)
{
number[0][i]=number[i][0]=0;
}
for(int i=2;i<=n;i++)
{
number[1][i]=number[1][i-1]+map[1][i];
}
for(int i=2;i<=n;i++)
{
number[i][1]=number[i-1][1]+map[i][1];
for(int j=2;j<=n;j++)
{
number[i][j]=max(number[i-1][j],number[i][j-1])+map[i][j];
}
}
}
后来只过了60分。。。
官解:
分析后发现每次取最优情况,这种类似于贪心的思想,在解决这个问题时,每次的最优情况累加并不能产生最终的最优解,这种方法是不行的
四维dp:
看题解明确了,这是这道题最简单的方法
简单来说就是使用i,j和k,w来分别表示两个人同时前进
然后在一个人经历了当前位置后,当前位置值就赋为0,在同时走的情况下,最终结果对应的就是走两边同时能取到的最优结果
#include<iostream>
#include<algorithm>
using namespace std;
int map[15][15],dp[15][15][15][15];
int n;
int _max(int a,int b,int c,int d)
{
return max(max(a,b),max(c,d));
}
int main()
{
cin>>n;
int x,y,data;
while(cin>>x>>y>>data&&(x||y||data))
{
map[x][y]=data;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int k=1;k<=n;k++)
{
for(int w=1;w<=n;w++)
{
dp[i][j][k][w]=_max(dp[i-1][j][k-1][w],dp[i-1][j][k][w-1],dp[i][j-1][k-1][w],dp[i][j-1][k][w-1])+map[i][j]+map[k][w];
if(i==k&&j==w) dp[i][j][k][w]-=map[k][w];
}
}
}
}
cout<<dp[n][n][n][n]<<endl;
return 0;
}
思路简单,代码简洁,很完美。
但是这道题存在优化情况,可以优化到三维,二维dp,对应代码量也要增加。
评价:
很不错的一道题,使用的方法很多,且题目本身做法不难,难在优化后的情况,值得记忆的一道题。
15.P1006 [NOIP2008 提高组] 传纸条
本题与14题类似,原理基本相同。