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]);
}
完结撒花