2022年上海5月月赛乙组题解

本文介绍了如何使用动态规划和并查集解决一系列组合问题,包括天平砝码、数山峰面积计算和狼人游戏。文章详细解析了每个问题的思路,提供了多种解法,如暴力枚举、状态转移和矛盾判断等,帮助读者深入理解这两种算法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IAI上海月赛系列文章目录

T1 天平砝码V2

在这里插入图片描述
在这里插入图片描述

思路一 暴力枚举法O(3^n)

#include<stdio.h> 
int n;
int res;
int w[1000000]; 
bool st[100000];
void dfs(int k,int sum)//表示k个的砝码,重量是sum 
{
   
	if(k>n)//k>n 说明选完n个砝码 
	{
   
		if(sum>0&&!st[sum])// 判断选出来的n个砝码的重量是否没被标记过 ,如没标记则答案加1 
		{
   
			res++;
			st[sum]=true;//标记这个重量 
			return;
		}	
	}
	//还没选够n个砝码 
	else
	{
   
		dfs(k+1,sum-w[k]);//砝码放右边 
		dfs(k+1,sum);//跳过,不适用当前的砝码 
		dfs(k+1,sum+w[k]);//砝码放左边 
	}
}
int main()
{
   
	scanf("%d",&n); 
	for(int i=1;i<=n;i++)scanf("%d",&w[i]); 
	
	dfs(0,0);
	printf("%d",res); 
}

思路二 动态规划dp

进入第i个砝码时候可以,我们需要先更新dp(i, ai) = 1;

当dp(i-1,j)=1时,第i个砝码重量为ai,

状态转移如下:

  1. dp (i , j) = 1,第i个砝码不使用

  2. dp (i,j+ai)=1,第i个砝码和之前能称出的重量j相加

  3. dp(i,j-ai)=1,两相减,注意可能小于0.需要加绝对值。

  4. dp(i,ai-j)=1,两相减,注意可能小于0.需要加绝对值。

普通版本(内存未优化)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
bool dp[105][100005];	//dp[i][j]=1表示i个砝码可以称出重量j 
int a[105];
int sum;
int main() 
{
   
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
   
		cin>>a[i];
		sum+=a[i];	//计算出可以称出的最大重量 
	}
	dp[1][a[1]]=1; 
	for(int i=2;i<=n;i++)	//枚举每个砝码 (加入每个砝码) 
	{
   	
	    dp[i][a[i]]=1;	//当前砝码的重量一定可以称出来
		for(int j=1;j<=sum;j++)	//复制状态,加入当前砝码前可以称得的重量加入后也可以得到 
			if(dp[i-1][j])//如果i-1个砝码可以称出j,那么枚举加入新的砝码可以称得的重量
            {
   
               	dp[i][j]=dp[i-1][j]; 
                dp[i][j+a[i]]=1;
				dp[i][abs(j-a[i])]=1; 
            }
	}
	int ans=0;
	for(int j=1;j<=sum;j++)
	{
   
		if(dp[n][j]!=0)
			ans++;
	}
	cout<<ans<<endl;
	return 0;
}
内存优化版本

实际上只要用到dp(i-1) 和dp(i) 两个状态。因此我们可以把空间做一次优化。

#include<iostream>
#include<algorithm>
using namespace std;

//bool dp[107][100005];//表示前i个砝码,可以表示的重量j
bool dp[100005],f[100005];//空间优化
int a[107];
int sum;
int main()
{
   
    freopen("11.txt", "r", stdin) ;
    int n;
    scanf("%d", &n);

    for (int i = 1;i<=n;i++)
    {
   
        cin >> a[i];
        sum += a[i];
    }
        
    
    for (int i = 1;i<=n;i++)
    {
   
        //把dp中的值给f
        for (int i = 0;i < sum;i++)
            f[i] = dp[i];
        for (int j = 1; j <= sum; j++)
        {
   
           if (f[j])
            {
   
                dp[abs(j - a[i])] = 1;
                dp[j + a[i]] = 1;
            }
        }
        dp[a[i]] = 1;
    }
    int res = 0;
    for (int i = 1;i<100005;i++)
         if(dp[i])res++;
    cout << res << endl;
    return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

//bool dp[107][100005];//表示前i个砝码,可以表示的重量j
bool dp[2][100005];//空间优化
int main()
{
   
    int n;
    scanf("%d", &n);
    for (int i = 1;i<=n;i++)
    {
   
        int ai;
        scanf(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值