第二节程序设计大赛部分题解

E:楼军的数学题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zqVuAtoI-1650807917519)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220417085440466.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nANKXyDr-1650807917520)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220417085451186.png)]

//最基础想法
for(i = 1;i <= n; i++){//前i种
    for(j = 0;j <= m; j++){
    //在前i种字符里面选了j个数字
        for(k = 0;k <= min(c[i],j); k++){//min的作用是保证不越界访问
            dp[i][j] = (dp[i][j] + dp[i-1][j-k])%mod;
            //对一个字符,它最多能用c[i]次,因为从j-c[i]到j都可以取这个字符,dp[i][j]可以由前面的转移过来,再说简单一点就是j前面连续选择k个c[i]对应的那个数字即i;
          //可以从dp[i-1][j-k]开始选择k个数i,组成dp[i][j];
        }
    }
}

当然这种做法最大时间复杂度达到了1e9,肯定会超时;

因为最内层的二重循环其实就是对于字符取或者不取的0/1背包问题,则我们可以优化;

//上一种写法种k取0是因为要将上一轮i-1的到的结果赋值到目前i这一轮来,所以要从0开始,而下面这种写法

//不用将上一轮赋值到下一轮,所以k不用从0开始;

dp[0] = 1;
for(i =1;i <= n; i++){
    for(j = m;j >= 0; j--){
        for(k = 1;k <= min(c[i],j); k++){
            dp[j] = (dp[j] + dp[j-k])%mod;
        }
    }
}

而对于最内层内层其实就是将dp[j-c[i]]到dp[j-1]这一段累加起来,因此我们可以采用前缀和来存储;

for(i = 1;i <= n; i++){
    for(j = m;j >= 1;j--){
        dp[j] = (dp[j]+sum[j-1]-sum[j-min(a[i],j)-1]+mod)%mod;
    }
    for(j = 1;j <= m; j++){//更新前缀和数组,为下一次计算做准备
        sum[j] = (sum[j-1] + dp[j])%mod;
    }
}

完整代码;

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e3+5,mod = 1e9+7;
int c[N];
ll sum[N],dp[N];

int main()
{
	int i,j;
	int n,m;scanf("%d%d",&n,&m);
	
	for(i = 1;i <= n; i++)
		scanf("%d",&c[i]);
	for(i = 0;i <= m; i++) 
		sum[i] = 1;
		
	dp[0] = 1;
	for(i = 1;i <= n; i++){
		
		for(j = m;j >= 1; j--){
			int k = j - min(j,c[i]) - 1;
			if(k < 0){
				dp[j] = (dp[j] + sum[j-1])%mod;
			}else{
				dp[j] = (dp[j] + sum[j-1] - sum[k] + mod)%mod;
			}	
		}
		//更新前缀和数组 
		for(j = 1;j <= m; j++){
			sum[j] = (sum[j-1] + dp[j])%mod;
		}
		
	}
	
	cout<<dp[m]<<endl;
	return 0;
}

K:上升的海平面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-effrSlTO-1650807917521)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220417093732565.png)]

记录山谷和山峰,山谷被淹没时,陆地被分割成两块陆地,山峰被淹没时,有一块陆地被淹没;

注意连续区域高度相等的情况;

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 1e5+5;
int a[N];
int hup[N];//山峰
int hdown[N];//山谷 
int main()
{
	int i,j;
	int t,n,n2;scanf("%d%d",&t,&n);
	
	for(i = 1,n2=1;i <= n; i++,n2++){
		scanf("%d",&a[n2]);
		if(a[n2] == a[n2-1]){
			n2--;
		}
	}
	a[n2] = -1;//1至n2-1为实际区域
	
	//找出山峰和山谷 
	int cntup = 0,cntdown = 0;
	for(i = 1;i < n2; i++){
		if(a[i]>a[i-1]&&a[i]>a[i+1]){
			hup[cntup++] = a[i];
		}	
		if(a[i]<a[i-1]&&a[i]<a[i+1]){
			hdown[cntdown++] = a[i];
		}
	}
	 
	sort(hup,hup+cntup);
	sort(hdown,hdown+cntdown);
	
	while(t--){
		int k;scanf("%d",&k);
		int q = upper_bound(hdown,hdown+cntdown,k)-hdown;
		int w = upper_bound(hup,hup+cntup,k)-hup;
		cout<<q-w+1<<endl;
	}
    return 0;
}

F:YY的最短路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T1tVuxFi-1650807917522)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220417162117491.png)]

思路:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CspKMMEx-1650807917522)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220417162232973.png)]

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 1e6+5;
const int mod = 1e9+7;
int a[N];

int main()
{
    int i,j;
    int n;scanf("%d",&n);
    int flag = 0;
    
    for(i = 1;i <= n; i++){
        scanf("%d",&a[i]);
        if(a[i] == 0) flag++;
    }
    if(flag!=1){
        cout<<0<<endl;
        return 0;
    }
    ll ans = 1;
    sort(a+1,a+n+1);
    
    for(i = 2;i <= n; i++){
        int idx = lower_bound(a+1,a+i,a[i])-a;
        ans = (ans*(idx-1))%mod;
    }
    cout<<ans<<endl;
    return 0;
}
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
int n;
int a[N];
void ex(int c[],int &l,int &r,int &total)
{
	total-=(r-l+1);
	total=max(0,total);
	for(int i=l;i<=r;i++){
		if(c[i]>1){
			c[i]--;	
		}else{
			l++;//左边的都用不了了 
		}
	}
}
bool check(int x,int k)
{
	int b[N] = {0};//用来记录 前面的点贡献的的攻击次数 
	for(int i=1;i<=n;i++) b[i] = 0;
	int cnt = 0;//总共攻击多少次 
	int red = 0,t = 0;//攻击值衰减应该减的数量,攻击值
	
	for(int i =n;i > 0; i--){
		red -= b[i];//到这个点为止,前面的点贡献的攻击次数已经没了 
		t-=red;//还有的攻击值 
		if(a[i] > t){//这个点的生命值大于前面所有点贡献的攻击伤害的话,也就是要多攻击一次 
			int ex = ceil((double)(a[i]-t)/x);//需要多攻击几次?;(a[i]-t-1)/m+1;
			red += ex;
			cnt += ex;
			if(cnt > k) return false;
			t += ex*x;
			if(i-x>1) b[i-x-1] = ex;//到i-x-1这个点那i这一次贡献的攻击衰减就没了 
		}
	} 
	return true;
}
int main()
{
	int i,j;
	int k;scanf("%d%d",&n,&k);
	int maxt = 0;//最大攻击力 
	for(i=1;i<=n;i++){
		scanf("%d",&a[i]);
		maxt = max(maxt,a[i]+n-i); 
	}
	int l = 1,r = maxt;
	while(l<r){
		int mid = (l+r)>>1;
		if(check(mid,k)) r = mid;
		else l = mid+1;
	}
	cout<<l;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值