牛客網暑期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;
}

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

就補這些吧!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章