1. 程式人生 > 其它 >NOIP模擬12

NOIP模擬12

T1:簡單的區間

Description

基本思路:

  區間問題,我最不擅長了
  考慮分治:
  我們首先可以\(O(n)\)用單調棧搞出沒一個值作為最大值的區間的左右端點,並且可以處理出第i位字首和%k的餘數並記錄,用一個vector,空間複雜度為\(O(n)\),可以接受,由於我們是順序遍歷,所以在每一個vector中下標是升序的。
  然後我們遍歷\(1~n\),處理包含\(a[i]\)在內的區間
  我們不必左右兩個區間都列舉。只需列舉較短的區間,然後在vector中利用lower_bound與upper_bound\(O(logn)\)查詢即可;
  上程式碼:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
    #define ll long long 
    #define rr register 
    #define inf INT_MAX
    #define max(x,y) (x>y?x:y)
    const int SIZE=3e5+4;
    const int SIZEK=1e6+4;
    int n,k;
    ll ans;
    ll a[SIZE];
    int f[SIZE];
    int l[SIZE],r[SIZE];//l<r
    vector<int> b[SIZEK];
    int read()
    {
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read=-1;
            c_read=getchar();
        }
        while(c_read<='9'&&c_read>='0')
        {
            x_read=x_read*10+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
    struct pack
    {
        int fi,se;
        pack(){}
        pack(int fi_,int se_){this->fi=fi_;this->se=se_;}
    };
    class Stack
    {
        private:
            pack *Top;
            pack s[SIZE];
        public:
            Stack(){Top=s;}
            void push(pack x){*(++Top)=x;}
            void pop(){Top--;}
            bool empty(){return Top==s;}
            pack top(){return *Top;}
    }s;
};
using namespace STD;
int main()
{
    n=read(),k=read();
    b[0].push_back(0);
	//一定要提前把0壓入原因見下
    for(rr int i=1;i<=n;i++)
    {
        a[i]=read();
        f[i]=(f[i-1]+a[i]%k)%k;
        b[f[i]].push_back(i);
    }
	//單調棧
    for(rr int i=1;i<=n;i++)
    {
        if(s.empty()){s.push(pack(a[i],i));continue;}
        if(s.top().fi>=a[i]){s.push(pack(a[i],i));continue;}
        while(!s.empty()&&s.top().fi<a[i])
        {
            r[s.top().se]=i-1;
            s.pop();
        }
        s.push(pack(a[i],i));
    }
    while(!s.empty()){r[s.top().se]=n;s.pop();}
    for(rr int i=n;i;i--)
    {
        if(s.empty()){s.push(pack(a[i],i));continue;}
        if(s.top().fi>a[i]){s.push(pack(a[i],i));continue;}
        while(!s.empty()&&s.top().fi<=a[i])
        {
            l[s.top().se]=i+1;
            s.pop();
        }
        s.push(pack(a[i],i));
    }
    while(!s.empty()){l[s.top().se]=1;s.pop();}
	//
	//這個統計ans的式子是依據%k相同的數相減可以被k整除
	//就有j[r]-j[l-1]-a[i]==0
	//下面num的計算是依據這個式子移項來的
	//注意觀察與思考lower_bound第3個引數
    for(rr int i=1;i<=n;i++)
    {
        if(l[i]==r[i]) continue;//l<r
        //以i作為某一端點
        int num,st,en;
        if(i-l[i])
        {
            num=((f[i]-a[i]%k)+k)%k;
            st=lower_bound(b[num].begin(),b[num].end(),l[i]-1)-b[num].begin();
            en=upper_bound(b[num].begin(),b[num].end(),i-2)-b[num].begin();
			//一定要注意b[num]可能為空,如果不判空就呼叫會RE
			//為因為這個沒看出來調了一下午,這下要長記性了QAQ。
            if(b[num].size()&&b[num][st]<=i-2)
                ans+=(en-st);
        }
        if(r[i]-i)
        {
            num=(f[i-1]+a[i]%k)%k;
            st=lower_bound(b[num].begin(),b[num].end(),i+1)-b[num].begin();
            en=upper_bound(b[num].begin(),b[num].end(),r[i])-b[num].begin();
            if(b[num].size()&&b[num][st]<=r[i])
                ans+=(en-st);
        }
        if(!(i-l[i])||!(r[i]-i)) continue;
        if(i-l[i]<r[i]-i)
        {
            for(rr int j=l[i]-1;j<i-1;j++)
            {
                num=(f[j]+a[i]%k)%k;
                st=lower_bound(b[num].begin(),b[num].end(),i+1)-b[num].begin();
                en=upper_bound(b[num].begin(),b[num].end(),r[i])-b[num].begin();
                if(b[num].size()&&b[num][st]<=r[i])
                    ans+=(en-st); 
            }
        }
        else
        {
            for(rr int j=i+1;j<=r[i];j++)
            {
                num=((f[j]-a[i]%k)+k)%k;
                st=lower_bound(b[num].begin(),b[num].end(),l[i]-1)-b[num].begin();
                en=upper_bound(b[num].begin(),b[num].end(),i-2)-b[num].begin();
                if(b[num].size()&&b[num][st]<=i-2)
                    ans+=(en-st);
            }
        }
    }
	printf("%lld",ans);
}

T2:簡單的玄學

Description:


  很明顯,直接求是不可能的,所以我們要求\(1-P(一個相等的也沒有)\)
  式子就是:

\[1-\frac{2^{n}*(2^{n}-1)*(2^{n}-2)*...(2^{n}-m+1)}{2^{mn}} \]\[= \]\[1-\frac{\prod_{i=2^{n}-m+1}^{2^n-1}}{2^{n(m-1)}} \]

  其實式子很好推,但是難計算,考場上就是因為不會高效計算WA了。
  其實可以觀察,當m或n比較大時,分子是有可能變成1e6+3的倍數的,所以我們當乘到是模數的倍數時,就跳出,因為還有個“1-”呢,總不可能輸出一吧;
  2的冪次直接快速冪即可。
  至於說約分。
  我們可以發現,分母的因子全是2.
  因此統計分子有多少個2因子即可
  有一個性質:

\(2^n-x\)的2因子個數等於\(x\)中的個數,證明:
假設\(x=2^{m}*j\),\(j\)可以是分數

\[2^{n}-x= \]\[2^{m}*(2^{n-m}-j) \]

顯然\(j\)沒有2因子,因而\(2^{n-m}-j\)沒有2因子,因而該結論正確
又由於連乘因子無損失,因此我們直接求\(!(m-1)\)的2因子個數即可

  這有一個\(O(logn)\)求法,詳見程式碼;
  分子約分後值求解直接暴力計算即可。
  上程式碼:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
    #define ll long long
    #define rr register 
    #define inf INT_MAX
    const ll mod=1e6+3;
    ll n,m;
    ll read()
    {
        rr ll x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read=-1;
            c_read=getchar();
        }
        while(c_read<='9'&&c_read>='0')
        {
            x_read=x_read*10+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
    ll qpow(ll base,ll exp)
    {
        ll ret=1;
        while(exp)
        {
            if(exp&1) ret=ret*base%mod;
            base=base*base%mod;
            exp>>=1;
        }
        return ret;
    }
};
using namespace STD;
int main()
{
    n=read(),m=read();
    ll cnt=0;
    for(ll i=1;(1ll<<i)<=(m-1);i++)
        cnt+=((m-1)/(1ll<<i));
    ll b=qpow(2,n);
    ll n2=b;
    ll n3;
    b=qpow(b,m-1);
    n3=b;
    ll x=qpow(2,cnt);
    x=qpow(x,mod-2);
    b=b*x%mod;
    ll a=1ll;
    for(rr ll i=1;i<=m-1;i++)
    {
        a=a*((n2-i%mod)%mod)%mod;
        if(!a) break;
    }
    a=a*x%mod;
    a=(b-a+mod)%mod;//這裡一定要注意用b-a再取模,因為b才是約分後的分母
    printf("%lld %lld",a,b);
}

T3:簡單的填數

  沒調出來呢,先鴿掉。