2021.02.02【NOIP提高A组】模拟

5428. 【NOIP2017提高A组集训10.27】查询

Description:

给出一个长度为n的序列a[]
给出q组询问,每组询问形如<x,y>,求a序列的所有区间中,数字x的出现次数与数字y的出现次数相同的区间有多少个

这道题是一个优雅的暴力题,我们先处理掉特殊情况:x,y 中至少一者未出现在序列 a 中。 然后考虑怎么计算询问,一个显然正确的暴力是维护一个前缀值,扫一遍,如果遇到x就加一,遇到y就减一,然后看前面有多少个前缀值跟当前的值相同, 加进答案里。这样做的缺点在于每次都要重新 O(n)地扫一遍,但是其中有很多 位置是没有用的,所以我们可以把x的所有出现位置和y的所有出现位置拿出来, 排序之后从前往后扫,做法与前面的暴力类似,但是压缩了中间没有影响的位置。 由于这样做相当于将所有颜色不同的位置对都枚举了一遍,所以这样的复杂度是 O(n^2)的

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int i,j,n,m,k,l,a[80001],hash[8000001],bz[8000001],last[8000001],ans,x,y,num,num1,t[16001],wz,wz1;
struct nup{int next,to;}f[80001];
int add_hash(long long x,int z)
{
	long long y=x%7000007;
	while(hash[y]!=0&&hash[y]!=x) y++,y%=7000007;
	if(z==2)
	{
		if(hash[y]==0) hash[y]=x;return y;
	}
	else if(hash[y]==0) return 0;else return y;
}
int main()
{
	freopen("query.in","r",stdin);
	freopen("query.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		k=add_hash(a[i],2);
		if(bz[k]==0) bz[k]=i,f[i].to=-1,last[k]=i;
		else f[i].to=bz[k],f[bz[k]].next=i,bz[k]=i,last[k]=i;
	} 
	for(i=1;i<=n;i++) if(f[i].next==0) f[i].next=n+1;
	for(i=1;i<=m;i++)
	{
		if(i==43)
			k=0;
		scanf("%d%d",&x,&y);ans=0;
		wz=add_hash(x,1);wz1=add_hash(y,1);
		wz=last[wz];wz1=last[wz1];
		if(wz==0||wz1==0)
		{
			if(wz==0&&wz1==0) ans=(1+n)*n/2;
			else if(wz==0)
			{
				num=wz1;num1=n;
				while(num!=-1)
				{
					ans+=(1+(num1-num))*(num1-num)/2;
					num1=num-1;num=f[num].to;
				}
				num=0;ans+=(1+(num1-num))*(num1-num)/2;
			}
			else
			{
				num=wz;num1=n;
				while(num!=-1)
				{
					ans+=(1+(num1-num))*(num1-num)/2;
					num1=num-1;num=f[num].to;
				}
				num=0;ans+=(1+(num1-num))*(num1-num)/2;
			}
		}
		else
		{
			memset(t,0,sizeof t);
			ans+=(1+n-max(wz,wz1))*(n-max(wz,wz1))/2;
			t[0]++;
			t[0]+=n-max(wz,wz1);int cnt=0;
			while(wz!=-1||wz1!=-1)
			{
				if(wz>wz1)
				{
					cnt++;
					int kk=f[wz].to;if(kk==-1) kk=0;
					int ll=wz1;if(wz1==-1) wz1=0;
					if(kk>wz1)
					{
						if(cnt>=0) ans+=(t[cnt]+t[cnt]+wz-kk-1)*(wz-kk)/2,t[cnt]+=wz-kk;
						else ans+=(t[cnt*-1+8001]+t[cnt*-1+8001]+wz-kk-1)*(wz-kk)/2,t[cnt*-1+8001]+=wz-kk;
					}
					else
					{
						if(cnt>=0) ans+=(t[cnt]+t[cnt]+wz-wz1-1)*(wz-wz1)/2,t[cnt]+=wz-wz1;
						else ans+=(t[cnt*-1+8001]+t[cnt*-1+8001]+wz-wz1-1)*(wz-wz1)/2,t[cnt*-1+8001]+=wz-wz1;
					}
					wz=f[wz].to;wz1=ll;
				}
				else
				{
					cnt--;
					int kk=f[wz1].to;if(kk==-1) kk=0;
					int ll=wz;if(wz==-1) wz=0;
					if(kk>wz)
					{
						if(cnt>=0) ans+=(t[cnt]+t[cnt]+wz1-kk-1)*(wz1-kk)/2,t[cnt]+=wz1-kk;
						else ans+=(t[cnt*-1+8001]+t[cnt*-1+8001]+wz1-kk-1)*(wz1-kk)/2,t[cnt*-1+8001]+=wz1-kk;
					}
					else
					{
						if(cnt>=0) ans+=(t[cnt]+t[cnt]+wz1-wz-1)*(wz1-wz)/2,t[cnt]+=wz1-wz;
						else ans+=(t[cnt*-1+8001]+t[cnt*-1+8001]+wz1-wz-1)*(wz1-wz)/2,t[cnt*-1+8001]+=wz1-wz;
					}
					wz1=f[wz1].to;wz=ll;
				}
			}
		}
		printf("%d\n",ans);
	}
}

5429. 【NOIP2017提高A组集训10.27】排列

Description:

有两个长度为n的排列A和B,定义排列的价值f(A,B)为所有满足A[i]>B[i]的位置i的数量。
现给出n,A,B和S,其中A和B中有一些位置的数未知,问有多少种可能的填数的方案使得f(A,B)=S

对于这样一道题我们很显然可以将其拆成两个子问题来处理,一个是A为0,B不为0 || A不为0,B为0;然后提前减去两者都不为0的情况这对后面操作没有影响。最后再把两个子问题合并就行了
我们这里考虑填A的:
假设我们先设 DP方程:f[i][j]:做完前i个有j个A>B,转移显然,但我们会发现如果我满足了有j个A>B的方案 而后面的(i-j)个A要比B小,会存在一种情况使得(i-j)个A并不能完全小于B,这就会出现 有>j个A>B的情况。 所以我们把 f[i][j]的定义改为:做完前i个至少有j个A>B。
转移: f[i][j]=f[i-1][j]+f[i-1][j-1]*(d[i]-j),d[i]表示比A的第i个数小的B的个数,(剩下的d[i]-j个数中随机选一个的方案数);
由于我们只规定了必须有贡献的j个,剩下的随意分配,所以要f[n][i]*(n-i)!
但这样算的结果不是恰好贡献为j,而是至少贡献为j的方案,我们下面称为f[j]。我们设g[j]表示贡献恰好为j的方案。我们对f[y]的定义仔细研究后发现一个g[x]在f[y] (x>y)中计算了\(C_x^y\)次(因为你会在x个贡献中任意选取y个作为必须贡献)我们发现g[n]=f[n]的,然后我们倒推就行了,\(g[y]=f[y]-\sum_{x=y+1}^{n}g[x]*C_x^y\),最后将两个子问题合并即可。
代码太丑仅供参考:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
ll ans,x,j,n,m,k,l,dq[40001],dq1[40001],t1[40001],t[40001],g[4001],h[4001],f[4001][4001],f1[4001][4001],d[40001],d1[40001],dl[40001],dl1[40001],mo,js[40001],a[4001],b[4001];
ll ksm(ll x,ll y)
{
	ll res=1;
	while(y)
	{
		if(y%2==1) res*=x,res%=mo;
		x*=x;x%=mo;y/=2;
	}
	return res;
}
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
ll C(ll x,ll y)
{
	ll res=js[x]*ksm(js[x-y]*js[y]%mo,mo-2);res%=mo;return res;
}
int main()
{
	freopen("arrange.in","r",stdin);
	freopen("arrange.out","w",stdout);
	n=read();k=read();mo=1000000007;
	js[1]=1;js[0]=1;for(register int i=2;i<=n;++i) js[i]=js[i-1]*i,js[i]%=mo;
	for(register int i=1;i<=n;++i) a[i]=read(),t[a[i]]++;
	for(register int i=1;i<=n;++i) if(t[i]==0) dl[++dl[0]]=i;
	for(register int i=1;i<=n;++i) b[i]=read(),t1[b[i]]++;
	for(register int i=1;i<=n;++i) if(t1[i]==0) dl1[++dl1[0]]=i;
	for(register int i=1;i<=n;++i) 
	{
		if(a[i]!=0&&b[i]!=0) {if(a[i]>b[i])k--;} 
		else{if(a[i]==0) dq[++dq[0]]=b[i];else dq1[++dq1[0]]=a[i];}
	}
	sort(dq+1,dq+1+dq[0]);sort(dq1+1,dq1+1+dq1[0]);
	j=0;
	for(register int i=1;i<=n;++i)if (!t[i]){
        while(i>dq[j+1]&&j<dq[0]) ++j;
        d[++d[0]]=j;
    }
    j=0;
	for(register int i=1;i<=dq1[0];++i){
        while (dq1[i]>dl1[j+1]&&j<dl1[0])++j;
        d1[i]=j;
    }
	f[0][0]=1;
	for(register int i=1;i<=dq[0];++i)
        for(j=0;j<=i;++j)
        {
        	if(j<i) f[i][j]+=f[i-1][j],f[i][j]%=mo;
        	if(j!=0&&d[i]>=j) f[i][j]+=f[i-1][j-1]*(d[i]-j+1)%mo,f[i][j]%=mo;
		}
    for(register int i=dq[0];i>=0;i--){
        g[i]=f[dq[0]][i]*js[dq[0]-i]%mo;
        for(j=i+1;j<=dq[0];++j)
            g[i]=(g[i]-C(j,i)*g[j]%mo+mo)%mo;
    }
    f1[0][0]=1;
    for(register int i=1;i<=dq1[0];++i)
        for(j=0;j<=i;++j)
        {
        	if(j<i) f1[i][j]+=f1[i-1][j],f1[i][j]%=mo;
        	if(j!=0&&d1[i]>=j) f1[i][j]+=f1[i-1][j-1]*(d1[i]-j+1)%mo,f1[i][j]%=mo;
		}       
    for(register int i=dq1[0];i>=0;i--){
        h[i]=f1[dq1[0]][i]*js[dq1[0]-i]%mo;
        for(j=i+1;j<=dq1[0];++j)
            h[i]=(h[i]-C(j,i)*h[j]%mo+mo)%mo;
    }
    for(register int i=0;i<=k;++i)
    {
    	ans+=g[i]*h[k-i];ans%=mo;
	}
	printf("%lld",ans);
}

5448. 【NOIP2017提高A组冲刺11.3】机房比教室好多了

这个写的很好,至于O(N)的做法其实就是换根DP,每次换根只会改变两个 state;
代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int i,j,n,m,k,l,f[2000001],bz[2000001],e[4000011][2],h[2000001],a[2000001],tot,ans[2000001],x,y;
void add(int u,int v)
{
	e[++tot][0]=h[u];
	e[tot][1]=v;
	h[u]=tot;
}
void dfs(int x,int father)
{
	int cnt=0;
	for(int i=h[x];i;i=e[i][0])
		if(e[i][1]!=father) dfs(e[i][1],x),cnt++;
	if(cnt==0) {bz[x]=1;return;}
	for(int i=h[x];i;i=e[i][0])
		if(e[i][1]!=father)
		{
			if(a[x]>a[e[i][1]]&&bz[e[i][1]]==1) {bz[x]=2;return;}
		}
	bz[x]=1;return;
}
void bfs(int x,int father)
{
	int num=0,dq=0;
	for(int i=h[x];i;i=e[i][0]) if(a[x]>a[e[i][1]]&&bz[e[i][1]]==1) num++,dq=e[i][1];
	for(int i=h[x];i;i=e[i][0])
		if(e[i][1]!=father)
		{
			int wz=bz[x],wz1=bz[e[i][1]];
			if(num>1) bz[x]=2;else if(num==0) bz[x]=1;else if(e[i][1]!=dq) bz[x]=2;else bz[x]=1;
			if(bz[e[i][1]]==1) if(a[e[i][1]]>a[x]&&bz[x]==1) bz[e[i][1]]=2;
			if(bz[e[i][1]]==2) ans[++ans[0]]=e[i][1];
			bfs(e[i][1],x);
			bz[x]=wz;bz[e[i][1]]=wz1;
		}
}
int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	scanf("%d",&n);
	for(i=1;i<=n;i++) scanf("%d",&a[i]);
	for(i=1;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
	dfs(1,0);
	if(bz[1]==2) ans[++ans[0]]=1;
	bfs(1,0);
	sort(ans+1,ans+1+ans[0]);
	for(i=1;i<=ans[0];i++) printf("%d ",ans[i]);
}

完结撒花

posted @ 2021-02-02 22:18  *LZX*  阅读(140)  评论(0编辑  收藏  举报