牛客网暑期ACM多校训练营(第二场)部分题题解(A,D,G,I,J)

由于这次题比较难,大部分题都是参考了其他人的思路和代码做出来的。。。

题目链接:https://www.nowcoder.com/acm/contest/140#question

A题

算是这场比赛的签到题,过的人也最多,算是一道简单dp吧。

f[i][0]表示共走i米,最后一步走1米的结果;f[i][1]表示共走i米,最后一步跑k米的结果。

转移方程:f[i][0]=(f[i-1][0]+f[i-1][1])%MOD

                  f[i][1]=f[i-k][0] (i>=k)

代码:

#include<cstdio>
using namespace std;
#define MAXN 100009
const int MOD = 1e9+7;
int f[MAXN][2];
int main(){
    int Q, k;
    scanf("%d%d", &Q, &k);
    f[0][0]=1;
    f[1][0]=1;
    for(int i=1; i<MAXN; ++i){
        f[i][0]=(f[i-1][0]+f[i-1][1])%MOD;
        if(i-k>=0)   f[i][1]=f[i-k][0];
    }
    for(int i=1; i<MAXN; ++i){
        f[i][0]+=f[i][1];
        f[i][0]%=MOD; 
        f[i][0]+=f[i-1][0];
        f[i][0]%=MOD;
    }
    int L, R;
    while(Q--){
        scanf("%d%d", &L, &R);
        printf("%d\n", ((f[R][0]-f[L-1][0])%MOD+MOD)%MOD);
    }
    return 0;
} 

D题

这道题过的人也比较多

把每个位置的值看成一个个点,如图。

在极小值处买

入,极大值处卖出即可。遇到值连续相同的点,删掉即可。注意一下开始和结尾的特殊处理。

代码:

#include<cstdio>
#include<iostream>
#define maxn 100005
using namespace std;
int a[maxn];
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int n;
        int Max=0;
        scanf("%d",&n);
        a[0]=-1;
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            if(a[i]==a[i-1]){
                i--;n--;
            } 
            Max=max(a[i],Max);
        }
        a[0]=Max;
        a[n+1]=0;
        int down;
        down=-1;
        int time=0;
        long long ans=0;
        for(int i=1;i<=n;i++){
            if(a[i]<a[i-1]&&a[i]<a[i+1])down=a[i];
            if(a[i]>a[i-1]&&a[i]>a[i+1])
//                if(down!=-1)
				{
                    ans+=(a[i]-down);
                    time++;
                }
        }
        printf("%lld %d\n",ans,time*2);
    }
    return 0;
}

前面的题都是比赛时候做出来的,后面几道题就是窝补得了

I题

比赛的时候刚了很长时间,不过好像结论的方向猜错了。

其实示意图是这样的

这个是偶数时的情况:

这个是奇数时的情况,虚线表示四个里选一个。

结论是没陷阱时为2n-n%2

加上陷阱后除掉不符合条件的即可

#include<cstdio>
#include<algorithm>
using namespace std;
int c[100010];
int r[100010];
int main()
{
    int n,m;
    int x,y;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		r[x]=1;
		c[y]=1;
	}
	int ans;
	if(n%2==1)
	{
		ans=2*n-1;
		if(r[(n+1)/2]&&c[(n+1)/2])
		ans--;
	}
	else
	ans=2*n;
	for(int i=1;i<=n;i++)
	{
		if(i==(n+1)/2)
		continue;
		else
		{
			if(c[i])
			ans--;
			if(r[i])
			ans--;
		}
	}
	ans=max(ans,0);
	printf("%d\n",ans);
	return 0;
 } 

J题

二维前缀和+位运算。

参考:https://www.cnblogs.com/OIerShawnZhou/p/7348088.html

里面对二维前缀和及其运算讲的很清楚,代码里也基本上用的这个思想。

做法就是官方给的做法

代码(参考某位神犇):

#include<cstdio>
#include<cstring> 
using namespace std;
int map[2000005], s[2000005],num[2000005][2],x1[2000005],y1[2000005],x2[2000005],y2[2000005],kk[2000005];
int main() {
    int n, m, t,res=0;
    scanf("%d%d%d", &n, &m, &t);
    int i, j;
    for (i=1;i<=n;i++) 
	{
       for(j=1;j<=m;j++)
       scanf("%d",&map[i*m+j]);
    }
    for (i=1;i<=t;i++)
        scanf("%d%d%d%d%d",&x1[i],&y1[i],&x2[i],&y2[i],&kk[i]);
    for (int k = 1; k <= 21; k++) 
	{
        for (i = 1; i <= n; i++)
            for (j = 1; j <= m; j++)
            num[i*m + j][0] = num[i*m + j][1] = 0;
          for(int l=1;l<=t;l++)
          {
          	   int e=(kk[l]>>(k-1))&1;
          	   num[x1[l]*m+y1[l]][e]++;
          	   num[(x2[l]+1)*m+y2[l]+1][e]++;
          	   num[x1[l]*m+y2[l]+1][e]--;
          	   num[(x2[l]+1)*m+y1[l]][e]--;
		  }
        for (i = 1; i <= n; i++) {
            for (j = 1; j <= m; j++) {
                int c = 0, d = 0;
                if (((map[i*m+j]>>(k-1))&1) == 0)
                    c++;
                else
                    d++;
                num[i*m + j][0] = num[i*m + j][0] + num[(i - 1)*m + j][0] + num[i*m + j - 1][0] - num[(i - 1)*m + j - 1][0];
                num[i*m + j][1] = num[i*m + j][1] + num[(i - 1)*m + j][1] + num[i*m + j - 1][1] - num[(i - 1)*m + j - 1][1];
                c += num[i*m + j][0]; 
				d += num[i*m + j][1];
                if (c > 0 && d > 0)
                    s[i*m + j]++;
            }
        }
    }
  
    for (i = 1; i <= n; i++) {
        for (j = 1; j <= m; j++) {
            if (s[i*m + j])
                res++;
        }
    }
    printf("%d\n", res);
    return 0;
}

 G题

总体思路:二分+尺取。细节较多。以下内容转载地址:https://blog.csdn.net/LSD20164388/article/details/81152298

题意:有n个位置,每个位置的坐标为x[i],有桶a[i]个。你现在要把若干个桶移动到同一个位置,求在移动总距离不超过T/2的情况下,最多可以将多少个木桶移动到同一个位置?(n<=5e5,T<=1e18,x[i]<=1e9,a[i]<=1e5)

思路:二分。如官方题解所说,我们要使移动的总距离最小,那么最终被移动的桶在数轴上一定是一段连续的区间。如果固定了这个区间,那么最优方案就是把这个区间的所有桶移动到这个区间的某个位置。而这个位置在这个区间内满足先单减再单增,在总体区间满足单调递增。我们固定这段连续区间的起点,那么区间的终点也是总体单增的。因此只需要O(n)枚举区间左端点。

我们用s[i]表示前i个位置有多少个桶,t[i]表示前i个位置的所有桶从0坐标移动过来的距离和。

假设我们二分到可以搬x个桶到同一个位置,当前判断的连续区间为[lp+1,rp],则首先s[rp]-s[lp]>=x。此时会有两种情况,就是我们在这个区间内要挑出x个桶。显然多余的桶一定在a[rp]或者在a[lp](因为我们单调枚举左端点)。当在a[rp]时,我们就不需要搬那多余的(s[rp]-s[lp]-x)个桶了。于是我们枚举这x个桶搬到某个位置x[i]时,区间下标[lp+1,i]的桶搬到x[i]的总距离为:

(s[i]-s[lp])*d[i]-(t[i]-t[lp]);

区间下标[i,rp]的桶搬到x[i]的总距离为(除去多余的桶和上一步已经搬了的桶):

t[rp]-t[lp]-(s[rp]-s[lp]-x)*d[rp]-(t[i]-t[lp])-(x-(s[i]-s[lp]))*d[i];

以上式子需要好好思考一下。

当在a[lp]时与以上情况类似。此时需要从右往左枚举区间右端点。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+10;
int n;
ll T,o,p,lp,rp,a[maxn],d[maxn],t[maxn],s[maxn],mp;
ll c1(int i)
{
    return (s[i]-s[lp])*d[i]-(t[i]-t[lp])+(o-t[i]+t[lp])-(p-s[i]+s[lp])*d[i];
}
ll c2(int i)
{
    return -(s[rp]-s[i])*d[i]+(t[rp]-t[i])-(o-t[rp]+t[i])+(p-s[rp]+s[i])*d[i];
}
bool jud(ll x)
{
    p=x;
    lp=0;rp=1;mp=1;
    while(1)
    {
        while(rp<n&&s[rp]-s[lp]<x) rp++;
        if(s[rp]-s[lp]<x) break;
        o=t[rp]-t[lp]-(s[rp]-s[lp]-x)*d[rp];
        while(mp<n&&c1(mp)>c1(mp+1))mp++;
        if(c1(mp)<=T)return 1;
        lp++;
    }
    lp=n-1;rp=n;mp=n;
    while(1)
    {
        while(lp>0&&s[rp]-s[lp]<x) lp--;
        if(s[rp]-s[lp]<x) break;
        o=t[rp]-t[lp]-(s[rp]-s[lp]-x)*d[lp+1];
        while(mp>1&&c2(mp)>c2(mp+1))mp--;
        if(c2(mp)<=T)return 1;
        rp--;
    }
    return 0;
}
int main()
{
    scanf("%d %lld",&n,&T);
    T>>=1;
    for(int i=1;i<=n;i++) 
    scanf("%lld",&d[i]);
    for(int i=1;i<=n;i++) 
    scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++) 
    {
       s[i]=s[i-1]+a[i];t[i]=t[i-1]+a[i]*d[i];
    }
    ll l=0,r=s[n]+1;
    ll ans=0;
    while(l<=r)
    {
        ll mid=(l+r)>>1;
        if(jud(mid)) 
        {
           l=mid+1;
           ans=max(ans,mid);
        }
        else r=mid-1;
    }
    printf("%lld\n",ans);
    return 0;
}

 ---------------------------------------------------------------------------------------------------------------------------------------------------------------

就补这些吧!

猜你喜欢

转载自blog.csdn.net/star_moon0309/article/details/81207718