noip模拟测试36
这次考试,题目比较有难度,考场上我只有T1有65分的思路,T3有一个暴力的思路,T2完全不会打,但是这次我把自己应该得到的分数都拿到了,也算是一个进步吧。
T1 Dove 打扑克
这道题,考试时我想到可以用一个权值线段树的思想,查询当前位置之前的与当前数值差值为 x的数的个数,每次重构一颗线段树 nlogn 进行计算,但是这只能拿到65分,算法的瓶颈在于我每次都重构了一颗线段树,我当时想半天如何才能不重构树,但没什么思路,那么正解就和我的思路不太一样,正解是维护了两个东西,size[i],表示 i 位置的大小,cnt[i] 表示这个大小的数量,那么我们就可以通过这两个数组,再利用一个前缀和就可以 o(qn) 得到结果,但是这样只能得到80分,因为我们还可以再优化,介绍一个引理:
给定n个非负整数组成的可重集合,{x1,...,xn},满足\(\sum\limits_{i=1}^{n}x_i=m\),那么一定有
\(diff_x\) <=\(sqrt(m)\), \(diff_x\)表示集合中本质不同的数字的数量。
知道了这个结论,那么这道题就可以进行优化了,因为当前人数不同的组的数量不超过\(sqrt(n)\),所以我们可以使用一个set单点插入,删除,并在查询过程中使用一个vector,记录前缀和,这样我们就可以AC了,总复杂度 \(o\times sqrt(n) \times log(sqrt(n))\)
代码如下:
65pts_code
#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
#define lc (rt<<1)
#define rc (rt<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int N=201000;
int n,m,ans;
int size[N],be[N],vis[N];
bool no[N];
ii read()
{
int x=0;
bool f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=0;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?x:(-x);
}
ii gett(int x)
{
if(x==be[x])
return x;
return be[x]=gett(be[x]);
}
struct Segment_Tree
{
int sum[360010];
iv pp(int rt)
{
sum[rt]=sum[lc]+sum[rc];
}
iv insert(int rt,int l,int r,int p)
{
if(l==r)
{
sum[rt]++;
return;
}
if(mid>=p)
insert(lc,l,mid,p);
else
insert(rc,mid+1,r,p);
pp(rt);
}
ii query(int rt,int l,int r,int L,int R)
{
if(L>R)
return 0;
if(L<=l&&r<=R)
return sum[rt];
if(mid>=R)
return query(lc,l,mid,L,R);
if(mid<L)
return query(rc,mid+1,r,L,R);
return query(lc,l,mid,L,R)+query(rc,mid+1,r,L,R);
}
iv ql(int rt,int l,int r)
{
sum[rt]=0;
if(l==r)
return;
ql(lc,l,mid);
ql(rc,mid+1,r);
}
}T;
signed main()
{
int opt,x,y,fx,fy,fi;
n=read();
m=read();
for(re i=1;i<=n;i++)
size[i]=1;
for(re i=1;i<=n;i++)
be[i]=i;
while(m--)
{
opt=read();
if(opt==1)
{
x=read();
y=read();
fx=gett(x);
fy=gett(y);
if(fx==fy)
continue;
be[fy]=fx;
size[fx]+=size[fy];
size[fy]=0;
}
else
{
T.ql(1,1,n);
x=read();
ans=0;
if(x!=0)
{
for(re i=1;i<=n;i++)
{
fi=gett(i);
if(vis[fi]==m)
continue;
ans+=T.query(1,1,n,size[fi]+x,n);
if(size[fi]-x>0)
ans+=T.query(1,1,n,1,size[fi]-x);
T.insert(1,1,n,size[fi]);
vis[fi]=m;
}
}
else
{
for(re i=1;i<=n;i++)
{
fi=gett(i);
if(vis[fi]==m)
continue;
ans+=T.query(1,1,n,size[fi],n);
ans+=T.query(1,1,n,1,size[fi]-1);
T.insert(1,1,n,size[fi]);
vis[fi]=m;
}
}
printf("%d\n",ans);
}
}
return 0;
}
AC_code
#include<bits/stdc++.h>
#define int long long
#define re register int
#define ii inline int
#define iv inline void
#define lc (rt<<1)
#define rc (rt<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int N=201000;
struct node
{
int posi,sum;
friend bool operator < (node a,node b)
{
return a.posi < b.posi;
}
};
set<int> T;
int n,m,ans;
int size[N],be[N],vis[N],cnt[N],p[N];
bool no[N];
ii read()
{
int x=0;
bool f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=0;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?x:(-x);
}
ii gett(int x)
{
if(x==be[x])
return x;
return be[x]=gett(be[x]);
}
signed main()
{
int opt,x,y,z,fx,fy,fi,zero;
n=read();
m=read();
T.insert(1);
cnt[1]=n;
for(re i=1;i<=n;i++)
{
be[i]=i;
size[i]=1;
}
while(m--)
{
ans=0;
opt=read();
if(opt==1)
{
x=read();
y=read();
fx=gett(x);
fy=gett(y);
if(fx==fy)
continue;
cnt[size[fx]]--;
cnt[size[fy]]--;
if(!cnt[size[fx]])
T.erase(size[fx]);
if(!cnt[size[fy]])
T.erase(size[fy]);
size[fx]+=size[fy];
if(T.find(size[fx])==T.end())
T.insert(size[fx]);
cnt[size[fx]]++;
be[fy]=fx;
size[fy]=0;
}
else
{
x=read();
zero=0;
if(x==0)
{
int col=0;
for(auto pos:T)
{
ans+=zero*cnt[pos];
ans+=(cnt[pos])*(cnt[pos]-1)/2;
zero+=cnt[pos];
}
}
else
{
vector<node>v;
for(auto pos:T)
{
zero+=cnt[pos];
v.push_back((node){pos,zero});
if(pos>x)
{
int o=upper_bound(v.begin(),v.end(),(node){pos-x,0})-v.begin();
if(o-1>=0)
ans+=v[o-1].sum*cnt[pos];
}
}
}
printf("%lld\n",ans);
}
}
return 0;
}
T2 Cicada 与排序
思路:我们设\(f_{i,j,k}\),表示第 i 层,原来的位置为 j ,排序后的位置为 k 的概率,那么答案很好求。现在的问题是如何进行状态转移,我们引入一个辅助数组\(g_{i,j}\)表示归并排序过程中两个指针分别指向i,j的概率,那么我们根据归并排序的过程进行转移即可。具体实现见代码:
AC_code
#include<bits/stdc++.h>
#define int long long
#define re register int
#define ii inline int
#define iv inline void
#define lc (rt<<1)
#define rc (rt<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int mo=998244353;
const int inv=499122177;
const int N=510;
int n;
int a[N];
int f[N][N][N],g[N][N];
ii read()
{
int x=0;
bool f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=0;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?x:(-x);
}
iv merge(int rt,int l,int r)
{
if(l==r)
{
f[rt][l][r]=1;
return;
}
merge(rt+1,l,mid);
merge(rt+1,mid+1,r);
memset(g,0,sizeof(g));
g[0][0]=1;
for(re i=0;i<=mid-l+1;i++)
{
for(re j=0;j<=r-mid;j++)
{
if(i==mid-l+1)
g[i][j+1]=(g[i][j+1]%mo+g[i][j]%mo)%mo;
else if(j==r-mid)
g[i+1][j]=(g[i+1][j]%mo+g[i][j]%mo)%mo;
else if(a[l+i]>a[mid+j+1])
g[i][j+1]=(g[i][j+1]%mo+g[i][j]%mo)%mo;
else if(a[l+i]<a[mid+j+1])
g[i+1][j]=(g[i+1][j]%mo+g[i][j]%mo)%mo;
else
{
g[i][j+1]=(g[i][j+1]%mo+g[i][j]%mo*inv%mo)%mo;
g[i+1][j]=(g[i+1][j]%mo+g[i][j]%mo*inv%mo)%mo;
}
}
}
for(re i=l;i<=r;i++)
{
for(re j=0;j<=mid-l+1;j++)
{
for(re k=0;k<=r-mid;k++)
{
if(j==mid-l+1 and k==r-mid)
continue;
if(j==mid-l+1)
f[rt][i][j+l+k]=(f[rt][i][j+l+k]+f[rt+1][i][mid+k+1]%mo*g[j][k]%mo)%mo;
else if(k==r-mid)
f[rt][i][j+l+k]=(f[rt][i][j+l+k]+f[rt+1][i][j+l]%mo*g[j][k]%mo)%mo;
else if(a[j+l]>a[mid+k+1])
f[rt][i][j+l+k]=(f[rt][i][j+l+k]+f[rt+1][i][mid+k+1]%mo*g[j][k]%mo)%mo;
else if(a[j+l]<a[mid+k+1])
f[rt][i][j+l+k]=(f[rt][i][j+l+k]+f[rt+1][i][j+l]%mo*g[j][k]%mo)%mo;
else
{
f[rt][i][j+l+k]=(f[rt][i][j+l+k]+f[rt+1][i][j+l]%mo*g[j][k]%mo*inv%mo)%mo;
f[rt][i][j+l+k]=(f[rt][i][j+l+k]+f[rt+1][i][mid+k+1]%mo*g[j][k]%mo*inv%mo)%mo;
}
}
}
}
sort(a+l,a+r+1);
}
signed main()
{
n=read();
for(re i=1;i<=n;i++)
a[i]=read();
merge(1,1,n);
for(re i=1;i<=n;i++)
{
int ans=0;
for(re j=1;j<=n;j++)
ans=(ans%mo+f[1][i][j]%mo*j%mo)%mo;
printf("%lld ",ans);
}
return 0;
}
T3 Cicada 拿衣服
思路:\(n^2\)暴力+乱搞,首先说一下暴力,我们以每个点为左端点向右查询满足条件的右端点,然后每次都进行一次更新答案的过程,这样就可以拿到64分,然后进行乱搞,猜测答案不会很大,于是我们枚举的右端点就到 i+700 ,即可通过此题......
AC_code
#include<bits/stdc++.h>
#define int long long
#define re register int
#define ii inline int
#define iv inline void
#define lc (rt<<1)
#define rc (rt<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int N=1010000;
const int INF=1e9;
int n,k;
int ans[N],a[N];
ii read()
{
int x=0;
bool f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=0;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?x:(-x);
}
iv spe_1()
{
for(re i=1;i<=n;i++)
{
int minn=INF,maxx=0,Or=0,And=(1<<30)-1,r=-INF;
for(re j=i;j<=n;j++)
{
Or|=a[j];
And&=a[j];
if(a[j]>maxx)
maxx=a[j];
if(a[j]<minn)
minn=a[j];
if(minn-maxx+Or-And>=k)
r=j;
}
int len=r-i+1;
for(re j=i;j<=r;j++)
if(ans[j]<len)
ans[j]=len;
}
}
iv spe_2()
{
for(re i=1;i<=n;i++)
{
int minn=INF,maxx=0,Or=0,And=(1<<30)-1,r=-INF;
for(re j=i;j<=min(n,700+i);j++)
{
Or|=a[j];
And&=a[j];
if(a[j]>maxx)
maxx=a[j];
if(a[j]<minn)
minn=a[j];
if(minn-maxx+Or-And>=k)
r=j;
}
int len=r-i+1;
for(re j=i;j<=r;j++)
if(ans[j]<len)
ans[j]=len;
}
}
signed main()
{
n=read();
k=read();
for(re i=1;i<=n;i++)
{
a[i]=read();
ans[i]=-1;
}
if(n<=30000)
spe_1();
else
spe_2();
for(re i=1;i<=n;i++)
printf("%lld ",ans[i]);
return 0;
}