题目列表:
⭐第四题
P1194 买礼物
考点:最小生成树prim,kruskal
未做出来的原因:
理解错了题意,当i和j,k都有优惠时,三者一起买,理解成了买i和j可以用优惠价,但是k此时没有礼物一起“组队”,此时不能用优惠价,其实实际上此时k可以利用G[i][k]与G[j][k]的优惠价。
因此由此可以将已经买的作为已经visit的集合放在一起,逐步挑选其他的未visit的且与已经买了这些商品“组队”后优惠价最低的货物。没加一条边,其实就是加一个礼物,当礼物总数sum=B时算法结束
分析:
本题在scanf矩阵的时候,将i<j的全部都放入图G中,很巧妙也是很关键的点是,当G[i][j]的值=0或者>A时,即不优惠或者优惠后还比原价贵,则按原价A来算,这样做的好处就是图最终一定是连通图,且是任意两点之间必有连线的连通图,这样一定可以得到一个最小生成树
代码来源
方法一:prim算法
#include<bits/stdc++.h>
using namespace std;
long long A,B,a,N,ans,G[505][505],w[505],k,sum;
bool pd[505];
int main()
{
// freopen("in.txt","r",stdin);
cin>>A>>B;
for(int i=1;i<=B;i++)
{
for(int j=1;j<=B;j++)
{
cin>>G[i][j];
if(G[i][j]==0)G[i][j]=A;
if(G[i][j]>A)G[i][j]=A;
}
}
for(int i=2;i<=B;i++)
{
w[i]=G[1][i];
}
k=1;
pd[1]=true;
sum=1;
ans+=A;
while(sum<=B)
{
long long mmin=1e9;
for(int i=2;i<=B;i++)
{
if(pd[i]==false&&mmin>w[i])
{
mmin=w[i];
k=i;
}
}
if(mmin==1e9)break;
ans+=mmin;
pd[k]=true;
for(int i=2;i<=B;i++)
{
if(pd[i]==false&&w[i]>G[i][k])w[i]=G[i][k];
}
}
cout<<ans<<endl;
return 0;
}
方法二:kruskal
//-------------以下代码是借鉴的优秀代码
#include<bits/stdc++.h>
#define maxn 505
#define pb push_back
using namespace std;
struct edge{
int u,v,w;
edge(int u,int v,int w):u(u),v(v),w(w){}
//结构体内嵌比较函数 -----------------
bool operator < (const edge& x) const{
// 从小到大排序
return w<x.w;
}
//这样写在使用sort()算法时,不用后面带cmp
//且同样的排序,比 cmp算法运行更快
};
int A,n;
int p[maxn];
int mp[maxn][maxn];
vector<edge>e;
int ans=0;
int cnt=0;
//以下find根的写法很简洁----------------
int find(int x){
return x==p[x]?x:p[x]=find(p[x]);
}
int kruskal(){
sort(e.begin(),e.end());
for(int i=0;i<=n;i++) p[i]=i;
for(int i=0;i<e.size();i++){
int x=find(e[i].u),y=find(e[i].v);
if(x!=y){
if(e[i].w<A) ans+=e[i].w;
else ans+=A;
p[x]=y;
cnt++;
}
if(cnt==n-1) break;
}
return ans;
}
int main(){
// freopen("in.txt","r",stdin);
//以下两句可以解决cin,cout效率比scanf和printf效率低的问题
//但是此时不能出现cout和printf混用的情况
//------------
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
//---------
cin>>A>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) cin>>mp[i][j];
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(mp[i][j]) e.pb(edge(i,j,mp[i][j]));
}
}
//cnt其实就是边的数量,若除去权值为0的边能组成最小生成树,则(n-cnt)=1
//若出去权值为0的边组成的连通分量不只一个,比如是2个连通分量,最后应该+2*A,即每个连通分量的起点都是原价购买且没有算在内
//但是其实这题的所有测试点都是一个连通分量,所以最后的题解里面有几个用kruskal的其实答案是不严谨的,应该考虑以上情况
cout<<kruskal()+A*(n-cnt)<<endl;
return 0;
}
以下是自己写的测试点:
//7 6
//0 2 0 0 0 0
//2 0 3 0 0 0
//0 3 0 0 0 0
//0 0 0 0 4 0
//0 0 0 4 0 5
//0 0 0 0 5 0
正确答案:28,有两个连通分量
若最后只是+A,则结果为21
最后两种代码的运行时间几乎一样,说明测试点的数据比较小,是稀疏图。如果是稠密图应该用prim,如果是稀疏图应该kruskal
第五题
P1123 取数游戏
考点:DFS,回溯
方法一:
循环遍历图的时候遵循从左至右,从上往下的顺序,因此只有判断四个位置而不是八个
另外注意dfs递归的两个位置
//251ms
#include <bits/stdc++.h>
using namespace std;
const int maxn=10;
int t,a[maxn][maxn],ans=0;
bool h[maxn][maxn];
int n,m;
void dfs(int x,int y,int sum){
if(sum>ans)ans=sum;
if(y==m+1){
x++;
y=1;
}
if(x>n)return;
//注意这个判断方法---------------------
if(!h[x][y-1]&&!h[x-1][y]&&!h[x-1][y-1]&&!h[x-1][y+1]){
h[x][y]=true;
dfs(x,y+1,sum+a[x][y]);
h[x][y]=false;
}
dfs(x,y+1,sum);
}
int main(){
cin>>t;
while(t--){
fill(h[0],h[0]+maxn*maxn,false);
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
dfs(1,1,0);
cout<<ans<<endl;;
ans=0;
}
return 0;
}
方法二: 常规方法,每取一个数,将相邻的数标记+1.
这个方法比第一种方法耗时长的原因可能是在取数的情况里面多了两个for循环
//650ms
#include<bits/stdc++.h>
using namespace std;
const int d[8][2]= {1,0,-1,0,0,1,0,-1,1,1,-1,1,1,-1,-1,-1};
int t,n,m,s[8][8],mark[8][8],ans,mx;
void dfs(int x,int y) {
if(y>m) { //当y到边界时,搜索下一行
dfs(x+1,1);
return;
}
if(x>n) { //当x到边界时,搜索结束,刷新最大值
mx=max(ans,mx);
return;
}
dfs(x,y+1); //不取此数的情况
if(mark[x][y]==0) { //取此数的情况
ans+=s[x][y];
for(int fx=0; fx<8; fx++)
mark[x+d[fx][0]][y+d[fx][1]]++;
dfs(x,y+1);
for(int fx=0; fx<8; fx++)
mark[x+d[fx][0]][y+d[fx][1]]--;
ans-=s[x][y];
}
}
int main() {
cin>>t;
while(t--) {
memset(s,0,sizeof(s));
memset(mark,0,sizeof(mark));
cin>>n>>m;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
cin>>s[i][j];
mx=0;
dfs(1,1);
cout<<mx<<endl;
}
return 0;
}
第六题
P1164 小A点菜
考点:动态规划
DFS方法5/10的检测点超时,改用dp
#include<bits/stdc++.h>
using namespace std;
int visit[110]={false};
int n,m,a[110],ans=0;
void DFS(int s,int num){
if(s==n+1){
if(num==0) ans++;
return ;
}else{
visit[s]=1;
DFS(s+1,num-a[s]);
visit[s]=0;
DFS(s+1,num);
}
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
DFS(1,m);
printf("%d",ans);
}
正解:
/*------6------*/
#include<bits/stdc++.h>
using namespace std;
int dp[110][10010]={0};
int main(){
// dp[i][j]表示前i件物品花光j元有多少种方法;
int n,m,a[110];
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int j=1;j<=m;j++){
dp[0][j]=0;
}
for(int i=0;i<=n;i++){
dp[i][0]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j<a[i]) dp[i][j]=dp[i-1][j];
else dp[i][j]=dp[i-1][j]+dp[i-1][j-a[i]];
}
}
printf("%d",dp[n][m]);
}