8.11考试总结(NOIP模拟36)[Dove 打扑克·Cicada 与排序·Cicada 拿衣服]
我会化作人间的风雨陪在你的身边
T1 Dove 打扑克
解题思路
考场上是想了一个树状数组维护的打法,但是竟然和 \(qn^2\) 的算法一样是 65pts
暴力就是对于每一次 2 询问重新建一下树状数组,进行计算。。
正解与暴力最大的区别就在于改变了枚举的东西
由枚举每一个堆的大小变为枚举牌堆大小,从同一大小或者不同大小堆的组合中计算答案。
大体思路就是维护一个 set 来保证堆的大小的有序。
同时开一个桶,维护不同大小的堆的个数,进行计算就好了。
在处理询问的时候直接双指针扫就好了。
分别记录当前扫到的点以及符合条件的边界。
code
65pts 树状数组
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=1e5+10,M=3e5+10;
int n,m,fa[N],siz[N],top,sta[N];
bool vis[N];
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
struct BIT
{
int num[N];
void clear(){memset(num,0,sizeof(num));}
int lowbit(int x){return x&(-x);}
void insert(int x,int tmp)
{
for(int i=x;i<=n;i+=lowbit(i))
num[i]+=tmp;
}
int query(int x)
{
int sum=0;
for(int i=x;i;i-=lowbit(i))
sum+=num[i];
return sum;
}
}tre;
signed main()
{
n=read(); m=read();
for(int i=1;i<=n;i++)
{
fa[i]=i;siz[i]=1;
tre.insert(1,1);
}
while(m--)
{
int opt,x,y,sum=0;
opt=read();
if(opt==1)
{
x=read(); y=read();
int fx=find(x),fy=find(y);
if(fx==fy) continue;
siz[fx]+=siz[fy];
fa[fy]=fx;
continue;
}
x=read();
tre.clear();
top=0;
for(int i=1;i<=n;i++)
{
int tmp=find(i);
if(vis[tmp]) continue;
vis[tmp]=true;
sta[++top]=siz[tmp];
}
sort(sta+1,sta+top+1);
for(int i=1;i<=top;i++)
{
if(sta[i]-x>0)
sum+=tre.query(sta[i]-x);
tre.insert(sta[i],1);
}
for(int i=1;i<=n;i++)
vis[fa[find(i)]]=false;
printf("%lld\n",sum);
}
return 0;
}
正解
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=1e5+10;
int n,m,all,fa[N],siz[N],sum[N];
multiset<int> s;
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
signed main()
{
n=read(); m=read();
for(int i=1;i<=n;i++)
{
fa[i]=i;
siz[i]=1;
}
sum[1]=n; all=n;
s.insert(1);
while(m--)
{
int opt,x,y,tot,ans;
opt=read();
if(opt==1)
{
x=read(); y=read();
int fx=find(x),fy=find(y);
if(fx==fy) continue;
all--;
auto it1=s.find(siz[fx]);
auto it2=s.find(siz[fy]);
s.erase(it1);
if(siz[fx]!=siz[fy]) s.erase(it2);
sum[siz[fx]]--;
sum[siz[fy]]--;
if(sum[siz[fx]]) s.insert(siz[fx]);
if(siz[fx]!=siz[fy]&&sum[siz[fy]]) s.insert(siz[fy]);
fa[fy]=fx;
siz[fx]+=siz[fy];
if(!sum[siz[fx]]) s.insert(siz[fx]);
sum[siz[fx]]++;
continue;
}
x=read(); tot=all; ans=0;
auto l=s.begin(),r=s.begin();
for(l=s.begin();l!=s.end();l++)
{
while((*r)-(*l)<x&&r!=s.end()) tot-=sum[(*r)],r++;
if(r==s.end()) break;
if((*l)==(*r))
{
ans+=(sum[(*l)]-1)*sum[(*l)]/2;
tot-=sum[(*r)]; r++;
if(r==s.end()) continue;
}
ans+=sum[(*l)]*tot;
}
printf("%lld\n",ans);
}
return 0;
}
T2 Cicada 与排序
解题思路
学习了 cty 的打法后我直接拍手称赞。。
模拟归并排序的过程,并且枚举同一数值的数来自左边区间的个数。
计算这个是总共的相同数值的第几个的概率。
最后根据概率算出期望就好了。
代码轻度压行,自我感觉良好。。。(逃
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=510,mod=998244353;int n,jc[N],vjc[N],inv[N],ans[N],cnt,lsh[N];
struct Node{
int id,dat,p[N];
bool friend operator < (Node x,Node y){return x.dat<y.dat;}
}s[N],fb[N];
int ksm(int x,int y){
int temp=1;
while(y){
if(y&1) temp=temp*x%mod;
x=x*x%mod;y>>=1;
}return temp;
}
void init(){
jc[0]=inv[0]=vjc[0]=1;
for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod,inv[i]=inv[i-1]*2%mod;
vjc[n]=ksm(jc[n],mod-2); inv[n]=ksm(inv[n],mod-2);
for(int i=n-1;i>=1;i--) vjc[i]=vjc[i+1]*(i+1)%mod,inv[i]=inv[i+1]*2%mod;
sort(lsh+1,lsh+cnt+1); cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
for(int i=1;i<=n;i++) s[i].dat=lower_bound(lsh+1,lsh+cnt+1,s[i].dat)-lsh;
}
int C(int x,int y){if(y>x) return 0;return jc[x]*vjc[y]%mod*vjc[x-y]%mod;}
void merge(int l,int r){
if(l==r) return ;
int mid=(l+r)>>1,len=r-l+1,q[N][2];
merge(l,mid); merge(mid+1,r);
memcpy(fb+l,s+l,sizeof(Node)*len); memset(q,0,sizeof(q));
for(int i=l;i<=mid;i++)q[s[i].dat][0]++;
for(int i=mid+1;i<=r;i++)q[s[i].dat][1]++;
for(int i=l;i<=r;i++) for(int j=1;j<=n;j++)s[i].p[j]=0;
for(int i=l;i<=mid;i++){
if(!q[s[i].dat][0]||!q[s[i].dat][1]) continue;
for(int j=1;j<=q[s[i].dat][0];j++){
for(int k=1;k<=q[s[i].dat][1]+j-1;k++)s[i].p[k]=(s[i].p[k]+C(k-1,j-1)*fb[i].p[j]%mod*inv[k]%mod)%mod;
for(int k=q[s[i].dat][1];k<=q[s[i].dat][1]+j-1;k++)s[i].p[q[s[i].dat][1]+j]=(s[i].p[q[s[i].dat][1]+j]+C(k-1,q[s[i].dat][1]-1)*fb[i].p[j]%mod*inv[k]%mod)%mod;
}
}
for(int i=mid+1;i<=r;i++){
if(!q[s[i].dat][0]||!q[s[i].dat][1]) continue;
for(int j=1;j<=q[s[i].dat][1];j++){
for(int k=1;k<=q[s[i].dat][0]+j-1;k++)s[i].p[k]=(s[i].p[k]+C(k-1,j-1)*fb[i].p[j]%mod*inv[k]%mod)%mod;
for(int k=q[s[i].dat][0];k<=q[s[i].dat][0]+j-1;k++)s[i].p[q[s[i].dat][0]+j]=(s[i].p[q[s[i].dat][0]+j]+C(k-1,q[s[i].dat][0]-1)*fb[i].p[j]%mod*inv[k]%mod)%mod;
}
}
for(int i=l;i<=r;i++)for(int j=1;j<=n;j++)if(!s[i].p[j]) s[i].p[j]=fb[i].p[j];
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
lsh[++cnt]=s[i].dat=read();s[i].id=i;
for(int j=1;j<=n;j++)s[i].p[j]=1;
}
init();merge(1,n);sort(s+1,s+n+1);
for(int i=1;i<=n;i++){
int j=i; while(s[j+1].dat==s[i].dat) j++;
for(int k=i;k<=j;k++)for(int l=i;l<=j;l++)ans[s[k].id]=(ans[s[k].id]+l*s[k].p[l-i+1]%mod)%mod;i=j;
}
for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
return 0;
}
T3 Cicada 拿衣服
题解
\(n^2\) 可以过 30000 的数据。。
我又用线段树手动套了一个 log 卡一卡范围就好了
(数据点分治) (雾
code
28pts 线段树
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=5e5+10,INF=1e18,base=2147483647;
int n,m,ans[N],s[N];
struct Segment_Tree
{
int mx,mn,ad,o;
}tre[N<<2];
void push_up(int x)
{
tre[x].ad=tre[ls].ad&tre[rs].ad;
tre[x].o=tre[ls].o|tre[rs].o;
tre[x].mn=min(tre[ls].mn,tre[rs].mn);
tre[x].mx=max(tre[ls].mx,tre[rs].mx);
}
void build(int x,int l,int r)
{
if(l==r)
{
tre[x].o=tre[x].mx=tre[x].mn=tre[x].ad=s[l];
return ;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
push_up(x);
}
int query_min(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R) return tre[x].mn;
int mid=(l+r)>>1,ans1=INF,ans2=INF;
if(L<=mid) ans1=query_min(ls,l,mid,L,R);
if(R>mid) ans2=query_min(rs,mid+1,r,L,R);
return min(ans1,ans2);
}
int query_max(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R) return tre[x].mx;
int mid=(l+r)>>1,ans1=0,ans2=0;
if(L<=mid) ans1=query_max(ls,l,mid,L,R);
if(R>mid) ans2=query_max(rs,mid+1,r,L,R);
return max(ans1,ans2);
}
int query_or(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R) return tre[x].o;
int mid=(l+r)>>1,ans1=0,ans2=0;
if(L<=mid) ans1=query_or(ls,l,mid,L,R);
if(R>mid) ans2=query_or(rs,mid+1,r,L,R);
return ans1|ans2;
}
int query_and(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R) return tre[x].ad;
int mid=(l+r)>>1,ans1=base,ans2=base;
if(L<=mid) ans1=query_and(ls,l,mid,L,R);
if(R>mid) ans2=query_and(rs,mid+1,r,L,R);
return ans1&ans2;
}
signed main()
{
int sum=0;
n=read(); m=read();
for(int i=1;i<=n;i++)
s[i]=read();
build(1,1,n);
for(int len=n;len>=1;len--)
{
if(sum==n) break;
for(int l=1;l+len-1<=n;l++)
{
if(sum==n) break;
int r=l+len-1;
int mn=query_min(1,1,n,l,r);
int mx=query_max(1,1,n,l,r);
int o=query_or(1,1,n,l,r);
int ad=query_and(1,1,n,l,r);
int temp=mn+o-mx-ad;
if(temp>=m)
for(int i=l;i<=r;i++)
{
if(!ans[i]) sum++;
ans[i]=max(ans[i],r-l+1);
}
}
}
for(int i=1;i<=n;i++)
if(ans[i]) printf("%lld ",ans[i]);
else printf("%lld ",-1ll);
return 0;
}
正解
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=1e6+10,INF=1e18,base=2147483647;
int n,m,ans[N],s[N];
signed main()
{
int sum=0;
n=read(); m=read();
for(int i=1;i<=n;i++)
s[i]=read();
for(int l=1;l<=n;l++)
{
int pos=-1,maxn;
int mn=s[l],mx=s[l],o=s[l],ad=s[l];
if(mn+o-mx-ad>=m) pos=l;
maxn=mn+o-mx-ad;
for(int r=l+1;r<=n;r++)
{
mn=min(mn,s[r]);
mx=max(mx,s[r]);
o|=s[r];
ad&=s[r];
int temp=mn+o-mx-ad;
maxn=max(maxn,temp);
if(temp>=m) pos=r;
if(n<=30000) continue;
if(r-l+1>700) break;
}
if(pos==-1) continue;
for(int i=l;i<=pos;i++)
ans[i]=max(ans[i],pos-l+1);
}
for(int i=1;i<=n;i++)
if(ans[i]) printf("%lld ",ans[i]);
else printf("%lld ",-1ll);
return 0;
}