2024暑假集訓測試2

卡布叻_周深發表於2024-07-10

前言

  • 比賽連結

image

T1、T4 比較簡單,打完基本就罰坐了,想了三個小時的 T2、T3 也沒想出來。

T1 酸鹼度中和

二分答案加貪心即可,先排序,每瓶可裝 \(a_i\sim a_i+2*m\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,l,r,mid,a[N],maxx,ans;
bool check(int x)
{
    int sum=0,now=a[1];
    for(int i=1;i<=n+1;i++)
        if(a[i]-now>2*x)
        {
            now=a[i];
            sum++;
            if(sum>m) return 0;
        }
    return (sum<=m);
}
signed main()
{
    read(n),read(m);
    for(int i=1;i<=n;i++)
        read(a[i]),
        maxx=max(maxx,a[i]);
    sort(a+1,a+1+n);
    a[n+1]=0x7f7f7f7f;
    l=0,r=maxx;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(check(mid)) r=mid-1,ans=mid;
        else l=mid+1;
    }
    write(ans);
}

T2 聰明的小明

  • 部分分 \(5pts\)\(k=m\) 時答案為 \(k!\)

  • 部分分 \(25pts\)\(n\le 20\& k=2\) 時爆搜。

  • 部分分 \(45pts\)\(k=2\) 時狀壓,如何狀壓在正解裡說。

  • 正解:

    鑑於 \(m\le 10\) 且記憶體給了 \(1G\),考慮狀壓。

    狀壓的思路比較巧妙,對於長度為 \(m\)\(01\) 串,\(1\) 表示某數在這 \(m\) 位中最後一次出現在此處,\(0\) 則表示該數在後面還出現過,如 011 可以表示為 112、221、121、212 等。

    之所以要這麼狀壓是因為需要處理第 \(m+1\) 位和第 \(1\) 位之間的影響。

    對於每一個狀態,若 \(1\) 的個數恰好 \(=k\) 且最後一位為 \(1\) 時為合法狀態。

    繼續考慮每一種狀態對應多少種情況,從後向前遍歷,記錄已經遍歷到的 \(1\) 的個數,若遇到一個 \(1\),答案 \(\times (k-num)\),然後 \(sum+1\),若為 \(0\)\(\times num\),正確性顯然。

    繼續考慮如何轉移,有 \(f_{i,s}=\sum f_{i-1,s'}\),其中 \(s\) 可以從 \(s'\) 轉移過來。

    繼續考慮一種狀態 \(s\) 可以轉化成什麼狀態,若 \(s\) 的第一位為 \(1\),說明該數僅出現過一次,則 \(m+1\) 位也必須為該數,如 101 儘可以轉化為 011

    對於其餘情況的狀態,每一個 \(1\) 都可以移到 \(m+1\) 位,此位變為 \(0\),如 011 可以轉化位 011101

    複雜度為 \(O(n2^m)\),若提前處理出所有合法狀態的話可將複雜度最佳化到 \(O(nC_m^k)\)\(m=10,k=5\) 時取到最大為 \(O(252n)\)

    點選檢視程式碼
    #include<bits/stdc++.h>
    #define int long long
    #define endl '\n'
    #define sort stable_sort
    using namespace std;
    const int N=1e5+2,P=998244353;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
    void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
    int n,k,m,f[N][1025],c[1025],ans;
    int check(int sta)
    {
        if(c[sta]!=-1) return c[sta];
        if((sta&1)==0) return c[sta]=0;
        int sum=0,ans=1,now=sta;
        for(int i=1;i<=m;i++)
        {
            if(sta&1)
                (ans*=(k-sum))%=P,
                sum++;
            else (ans*=sum)%=P;
            sta>>=1;
        }
        return c[now]=(sum==k)*ans;
    }
    void solve(int i,int sta)
    {
        if(check(sta)==0) return ;
        if((sta>>(m-1))&1) 
        {
            int now=sta;
            sta^=(1<<(m-1));
            sta<<=1;
            sta|=1;
            (f[i+1][sta]+=f[i][now])%=P;
            return ;
        }
        for(int j=1;j<=m;j++)
            if((sta>>(j-1))&1)
            {
                int now=sta;
                sta^=(1<<(j-1));
                sta<<=1;
                sta|=1;
                (f[i+1][sta]+=f[i][now])%=P;
                sta=now;
            }
    }
    signed main()
    {
        memset(c,-1,sizeof(c));
        read(n),read(k),read(m);
        for(int s=1;s<=(1<<m)-1;s+=2) 
            f[m][s]=check(s);
        for(int i=m;i<=n-1;i++)
            for(int s=1;s<=(1<<m)-1;s+=2)
                solve(i,s);
        for(int s=1;s<=(1<<m)-1;s+=2)
            (ans+=f[n][s])%=P;
        write(ans);
    }
    

T3 線段樹

定義 \(f_{l,r}\) 為區間 \(l\sim r\) 的貢獻,有 \(f_{l,r}\) 下界為 \(1\)

對於線段樹上一個區間 \([L,R]\),若存在一個詢問區間 \([l,r]\)\([L,R]\) 存在交集且 \([L,R]\) 不被 \([l,r]\) 包含,說明 \([l,r]\) 被分成了至少兩個區間,由此 \(f_{L,R}\) 的貢獻 \(+1\)

由此我們處理出每個斷點 \(i\) 被多少個詢問區間包含,記為 \(w_i\),以及每個線段樹上的區間 \([l,r]\) 被多少個詢問區間包含,記為 \(sum_{l,r}\)

其中 \(w_i\) 可以在輸入的時候直接差分處理,\(sum_{l,r}\) 可以類似於二維字首和和區間 \(DP\) 維護,有:

\[sum_{l,r}=sum_{l-1,r}+sum_{l,r+1}-sum_{l-1,r+1}+num_{l,r} \]

\(num_{l,r}\) 表示詢問中恰好區間 \([l,r]\) 的個數。

最後考慮 \(DP\) 轉移,\(f_{l,r}\) 為線段數上區間 \([l,r]\) 最少能提供多少除 \(m\) 個基本詢問外額外的貢獻,有:

\[f_{l,r}=\min\limits_{k=l}^{r-1}\{f_{l,k}+f_{k+1,r}+w_k-sum_{l,r}\} \]

初始值 \(f_{i,i}\)\(0\),其餘均賦成極大值,這與之前的 \(f_{l,r}\) 下界為 \(1\) 是矛盾的,但是鑑於對 \(f_{l,r}\) 的定義為額外貢獻,其初始值應該為 \(0\),最後答案為 \(+m\) 即可。

最後答案為 \(f_{1,n}+m\)

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=510;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,w[N],sum[N][N],f[N][N];
signed main()
{
    read(n),read(m);
    for(int i=1,l,r;i<=m;i++)
        read(l),read(r),    
        w[l]++,w[r]--,
        sum[l][r]++;
    for(int i=1;i<=n;i++) w[i]+=w[i-1];
    for(int i=n;i>=1;i--)
        for(int l=1;l+i-1<=n;l++)
        {
            int r=l+i-1;
            sum[l][r]=sum[l][r]+sum[l-1][r]+sum[l][r+1]-sum[l-1][r+1];
        }
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;i++) f[i][i]=0;
    for(int i=2;i<=n;i++)
        for(int l=1;l+i-1<=n;l++)
        {
            int r=l+i-1;
            for(int k=l;k<r;k++)
                f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+w[k]-sum[l][r]);
        }
    write(f[1][n]+m);
}

T4 公路

貪心加模擬即可。

當前對於到達了車站 \(i\),繼續向前找到一個 \(j\)\(j\) 分為兩種情況:

  1. 在油箱允許的範圍內找到的第一個油價小於 \(i\) 的,此時只需講油加到足夠支撐到車站 \(j\) 的即可。
  2. 在油箱允許的範圍內沒有找到任何一個油價小於 \(i\) 的,則在該範圍內找到油價最小的一個車站 \(j\),此時直接將油箱加滿,防止在更貴的地方買更多的油。

處理過後直接另 \(i=j\) 即可,同時維護一個變數儲存油箱內還剩下多少油。

鑑於雙指標做法,隨機資料下複雜度近似於 \(O(n)\),油價單調遞減資料下為嚴格 \(O(n)\),油價單調遞增且 \(c\ge\sum\limits_{i=0}^{n-1}v_i\) 資料下複雜度退化為 \(O(n^2)\),在資料比較水的情況下可以透過此題。

利用單調佇列最佳化可將複雜度最佳化為嚴格線性,懶得打了但是。

點選檢視暴力程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,d[N],a[N],f[N],ans,now;
signed main()
{
    read(n),read(m);
    for(int i=1,v;i<=n;i++)
        read(v),
        d[i]=d[i-1]+v;
    for(int i=0;i<=n-1;i++) read(a[i]);
    int j;
    for(int i=0;i<=n-1;i=j)
    {
        j=i+1;
        int k,minn=0x7f7f7f7f;
        while(j<=n)
        {
            if(d[j]-d[i]>m) {j--; break;}
            if(a[j]<a[i]) break;
            if(a[j]<minn) minn=a[j],k=j;
            j++;
        }
        if(a[j]<a[i])
            ans+=(d[j]-d[i]-now)*a[i],
            now=0;
        else 
        {
            j=k;
            ans+=(m-now)*a[i];
            now=m-d[j]+d[i];
        }
    }
    write(ans);
}
粘一個官方題解的單調佇列最佳化程式碼
#include<bits/stdc++.h>
#define ll long long
#define maxn 1000005
#define mod 1000000007
using namespace std;
ll n,C;
ll x[maxn],p[maxn];
pair<ll,ll> Q[maxn+maxn]; 
int head,tail;
int main()
{
	cin>>n>>C;
	for (int i=1;i<=n;i++) cin>>x[i];
	for (int i=1;i<=n;i++) cin>>p[i];
	head=1; tail=1;
	Q[head]={1ll<<30,C}; //為了保證佇列裡總大小是C
	ll sum=0;
	for (int i=1;i<=n;i++)
	{
		ll tt=0;
        //刪掉末尾不如p[i]的決策
		while (head<=tail && Q[tail].first>=p[i]) {tt+=Q[tail].second; tail--;}
		Q[++tail]={p[i],tt};
        //在佇列的開頭拿出x[i]的油來加
		ll ret=x[i];
		while (ret)
		{
			ll num=min(ret,Q[head].second);
			sum+=Q[head].first*num; Q[head].second-=num; ret-=num;
			if (Q[head].second==0) head++;
		}
		Q[++tail]={1ll<<30,x[i]}; //為了保證佇列裡總大小是C
	}
	cout<<sum<<endl;
}

總結

要學會面向資料範圍,確定正確的思路,如 T2 想出狀壓。

好好複習一下區間 DP。