1. 程式人生 > 其它 >noip模擬12 簡單的區間 簡單的玄學 簡單的填數

noip模擬12 簡單的區間 簡單的玄學 簡單的填數

第一場比賽(事實上當你看到這篇的時候可能連最後一場都考完了

考場上順序開題。

\(\mathrm{A.}\mathbb{簡單的區間}\):不知道

\(\mathrm{B.}\mathbb{簡單的玄學}\):不知道

\(\mathrm{C.}\mathbb{簡單的填數}\):不知道

一開始覺得 \(\mathrm{B}\) 是個數學題,於是 \(\mathrm{2h}\) 的時間就過去了(

後來開始肝 \(\mathrm{A}\),以一種神奇的方法來記錄區間最大值,真正的達到了複雜度O(1),正確性隨機。

最後連 \(\mathrm{C}\) 的暴力都沒打。

估分:\(0+100+0=100\)

實際:\(0+40+0=40\)

還是說正解吧(

\(\mathrm{A.}\mathbb{簡單的區間}\)

考場上想的是找每個點往兩邊最遠能延伸到哪兩個點,則在這兩個點組成的區間的含當前點的子區間的最大值也為當前這個點。

正解

分治

將原式轉化為 \(sum_r-sum_{l-1}-max\equiv0\pmod{k}\)

\(\mathrm{ST}\) 表記錄區間最大值以及最大值的編號,對於當前的區間 \([l,r]\),設區間的最大值為 \(\max\),它的編號為 \(mid\)。處理過 \(mid\) 的區間,然後往兩邊遞迴即可。

考慮優化,如果 \(r-mid<mid-l\),列舉右端點,否則列舉左端點,複雜度為 \(n\log_2 n\)

用連結串列+字首和的方式即可,最後記得把 \(l=r\) 的情況減去。

code
#include<iostream>
#include<cmath>
using namespace std;
const int N=3e5+5;
int n,k,cnt,ans;
int a[N],f[N][21],g[N][21],sum[N],tot[N*10];
int head[N];
struct Node
{
    int x,f;
    int nxt;
}e[(int)11e6];
void add(int id,int x,int f)
{
    if(id<0) return;
    e[++cnt].nxt=head[id];
    e[cnt].f=f;
    e[cnt].x=x;
    head[id]=cnt;
}
void solve(int l,int r)
{
    if(l>=r)
    {
        ans+=(l==r);
        return;
    }
    int lg=log(r-l+1)/log(2);
    int mx,mxid;
    if(f[l][lg]>f[r-(1<<lg)+1][lg]) mx=f[l][lg],mxid=g[l][lg];
    else mx=f[r-(1<<lg)+1][lg],mxid=g[r-(1<<lg)+1][lg];
    mx%=k;
    if(r-mxid<mxid-l)
    {
        for(int i=mxid;i<=r;i++)
        {
            add(mxid-1,(sum[i]-mx+k)%k,1);
            add(l-2,(sum[i]-mx+k)%k,-1);
        }
    }
    else
    {
        for(int i=l;i<=mxid;i++)
        {
            add(r,(sum[i-1]+mx)%k,1);
            add(mxid-1,(sum[i-1]+mx)%k,-1);
        }
    }
    solve(l,mxid-1);
    solve(mxid+1,r);
}
int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        sum[i]=(sum[i-1]+a[i])%k;
        f[i][0]=a[i];
        g[i][0]=i;
    }
    int lg=log(n)/log(2)+1;
    for(int j=1;j<=lg;j++)
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            if(f[i][j-1]>f[i+(1<<(j-1))][j-1]) f[i][j]=f[i][j-1],g[i][j]=g[i][j-1];
            else f[i][j]=f[i+(1<<(j-1))][j-1],g[i][j]=g[i+(1<<(j-1))][j-1];
        }
    }
    solve(1,n);
    for(int i=0;i<=n;i++)
    {
        tot[sum[i]]++;
        for(int j=head[i];j;j=e[j].nxt) ans+=tot[e[j].x]*e[j].f;
    }
    cout<<ans-n<<endl;
    return 0;
}

\(\mathrm{B.}\mathbb{簡單的玄學}\)

唯一一道想到了正解的題。

但是交之前手欠了一下,給指數模了個 \((mod-2)\),因此掛到 \(40\) 分。(悲)

正解

考慮從反面來解,只需求出都不相同的概率即可。

先考慮約分,分母中只含有因子 \(2\),因此只需要記錄分子中 \(2\) 的個數。

很顯然,除去首項的 \(2^n\)\(n\)\(2\) 的因子,其餘 \(2^n-a\)\(a\) 的因子 \(2\) 的個數相同。

因此就轉變成了求 \(m!\) 的因子 \(2\) 的個數,\(O(\log_2m)\) 即可。

分母共有 \(nm\) 個因子 \(2\),因此一定能約去。

設這個因子 \(2\) 的個數為 \(cnt\),接著再考慮取模。

\(2^{nm}\) 這一項,快速冪即可。

對於排列數,我們不難發現

而這題的模數又是很妙的 \(10^6+3\),對於大於模數直接消掉,小於模數直接 \(O(m)\) 計算。

code
#include<iostream>
#include<cmath>
using namespace std;
long long n,m,q,ans,mul=1;
const long long mod=1e6+3;
long long up,down;
long long p=1;
int cnt,flag;
const double eps=1e-6;
long long power(long long b,long long p)
{
    long long ans=1;
    while(p)
    {
        if(p&1) ans=ans*b%mod;
        p>>=1;
        b=b*b%mod;
    }
    return ans;
}
int main()
{
    cin>>n>>m;
	q=m-1;
	for(cnt=1;cnt<=60;cnt++)
	{
		p*=2;
		if(p>m) break;
		if(p==m)
		{
			flag=1;
			break;
		}
	}
	if(cnt>n||(cnt==n&&p<m)) up=down=1;
	else
	{
		ans=n;
		while(q)
		{
			q/=2;
			ans+=q;
		}
		long long invn=power(2,n),invmod=power(power(2,ans),mod-2);
		up=down=power(invn,m);
		if(m<mod)
		{
			long long left=invn;
			for(int i=1;i<=m;i++)
			{
				mul=mul*left%mod;
				left--;
				if(left<0) left+=mod;
			}
			up=(up-mul+mod)%mod;
		}
		up=up*invmod%mod;
		down=down*invmod%mod;
	}
	cout<<up<<" "<<down<<endl;
    return 0;
}

\(\mathrm{C.}\mathbb{簡單的填數}\)

這道題考場上完全沒看,直接說正解。

正解

設二元組 \(up_i\) 為每個 \(i\) 能填的最大的數以及連續的個數,\(down_i\) 為每個 \(i\) 能填的最小的數以及連續的個數。

正常轉移即可,正著掃一遍,求出最大的 \(a_n\)

\(a_i>up_i(val)\)\(a_i<down_i(val)\) 時,直接返回 \(-1\)

再倒著掃一遍,求出一種合法的序列 \(a\)

code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
#include<cstdlib>
#include<climits>
#include<bitset>
#define val first
#define cnt second
using namespace std;
const int N=2e5+5;
int n;
int a[N],ans[N],vis[N];
pair<int,int> up[N],down[N];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	if(a[1]>1)
	{
		cout<<-1<<endl;
		return 0;
	}
	a[1]=1;
	up[1]=down[1]=make_pair(1,1);
	for(int i=2;i<=n;i++)
	{
		up[i]=make_pair(up[i-1].val,up[i-1].cnt+1);
		down[i]=make_pair(down[i-1].val,down[i-1].cnt+1);
		if(up[i].cnt>2)
		{
			up[i].val++;
			up[i].cnt=1;
		}
		if(down[i].cnt>5)
		{
			down[i].val++;
			down[i].cnt=1;
		}
		if(a[i])
		{
			if(up[i].val<a[i]||down[i].val>a[i])
			{
				cout<<-1<<endl;
				return 0;
			}
			if(up[i].val>a[i]) up[i]=make_pair(a[i],2);
			if(down[i].val<a[i]) down[i]=make_pair(a[i],1);
		}
	}
	if(up[n].cnt==1)
	{
		up[n].val=up[n-1].val;
		up[n].cnt=up[n-1].cnt+1;
	}
	ans[n]=up[n].val;
	for(int i=n-1;i>=1;i--)
	{
		if(a[i]) ans[i]=a[i];
		else
		{
			ans[i]=min(ans[i+1],up[i].val);
			if(vis[ans[i]]==5) ans[i]--;
		}
		vis[ans[i]]++;
	}
	cout<<up[n].val<<endl;
	for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
	cout<<endl;
}