有一场没一场的233
T1:
胡乱分析一下题意,发现和为n的x个正整数,不同的数字种类不会超过√n个。假设这x个数字都不同,最多也就是(x+1)*x/2=n。
所以可以维护现有的size值以及对应的数目cnt。修改的时候用并查集维护牌堆,然后在储存size值和cnt的数组里暴力进行修改,为了使记录size值的val数组有序,可能需要把数组整体平移的操作,复杂度O(√n)。
询问的时候维护两个指针l,r。r指向与val[l]的差距第一个大于等于c的位置,每次移动l的时候维护r以及r位置及以后的后缀和num,ans+=cnt[l]*num。c=0的时候特殊处理一下。由于使用了双指针,复杂度为O(√n)。
整体复杂度O(√n)。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> using namespace std; const int N=1e5+10; int n,m,fa[N],siz[N],val[N],cnt[N],tot; int get(int x){ if(x==fa[x])return x; else return fa[x]=get(fa[x]); } void remove(int x){ int pos=lower_bound(val+1,val+tot+1,x)-val; cnt[pos]--; if(!cnt[pos]){ for(int i=pos;i<tot;i++){ val[i]=val[i+1]; cnt[i]=cnt[i+1]; } tot--; } } void add(int x){ int pos=lower_bound(val+1,val+tot+1,x)-val; if(val[pos]==x)cnt[pos]++; else{ for(int i=tot+1;i>pos;i--){ val[i]=val[i-1]; cnt[i]=cnt[i-1]; } tot++; val[pos]=x; cnt[pos]=1; } } long long work(int c){ long long ans=0; if(!c){ c++; long long num=0; int l=1; int r=lower_bound(val+1,val+tot+1,val[l]+c)-val; for(int i=r;i<=tot;i++)num+=cnt[i]; while(l<=tot&&r<=tot&&l<=r){ while(val[r]-val[l]<c&&r<=tot){ num-=cnt[r]; r++; } if(r>tot)break; ans+=1ll*cnt[l]*num; l++; } for(int i=1;i<=tot;i++){ ans+=(1ll*cnt[i]*(cnt[i]-1))/2; } } else{ long long num=0; int l=1; int r=lower_bound(val+1,val+tot+1,val[l]+c)-val; for(int i=r;i<=tot;i++)num+=cnt[i]; while(l<=tot&&r<=tot&&l<=r){ while(val[r]-val[l]<c&&r<=tot){ num-=cnt[r]; r++; } if(r>tot)break; ans+=1ll*cnt[l]*num; l++; } } return ans; } int main() { // freopen("1.in","r",stdin); // freopen("1.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<n;i++)fa[i]=i,siz[i]=1; fa[n]=n,siz[n]=1; val[++tot]=1,cnt[tot]=n; for(int i=1,opt,x,y;i<=m;i++){ scanf("%d",&opt); if(opt==1){ scanf("%d%d",&x,&y); int x1=get(x),y1=get(y); if(x1==y1)continue; fa[y1]=x1; remove(siz[x1]); remove(siz[y1]); add(siz[x1]+siz[y1]); siz[x1]+=siz[y1]; } else{ scanf("%d",&x); printf("%lld ",work(x)); } } return 0; }
T2:
额啊…这题卡了很久才在大佬的帮助下弄出来orz
对于原序列的每一个数字分别考虑,每次只处理指定的这一个数字。设dp[i][j]为在归并排序的第i层,这个数字排在第j位的概率。这个位置j是每一层中在同样的数字里的相对位置,和其它不同的数字的位置无关。dp数组计算出来的概率只影响同种相同的数字,最后计算答案的时候,只需要加上有多少个数字比它小,即相同的这一段数字会排在哪个位置的偏移量。
考虑每一次合并一个包含当前处理数字的区间,设左儿子区间有p个相同的数字,右儿子区间有q个。枚举i和j,设i代表包含目标数字的这段区间,当前要使一边的第i个被放下,另一边已经放了j个。转移方程是dp[dep][i+j]+=dp[dep+1][i]*g[i][j+1]*1/2,因为只有i这一边包含目标数字所以只有dp[dep+1][i]表示目标数字在第dep+1层的i位置的概率,方程的含义即从i位置转移到i+j位置。g[i][j]表示两个指针分别指在i位置和j位置的概率,g数组可以预处理。指针指在某个位置,代表这个位置的数字还没有被选择,指针的每一种指向可以转移到两种后续的指针指向,所以要*1/2。特殊情况,如果j=q,这时j一边的指针已经指向最后一个数字的下一位,即选完j这边的数字,那么转移的时候要将g数组替换成g数组的前缀和sum[i][j],sum[i][j]=g[1][j]+g[2][j]+...+g[i][j]。考虑j一边已经选完以后i的转移,dp[dep][i+j]+=dp[dep+1][i]*g0[i][j+1],这里的g0[i][j+1]表示一边已经选完j个,另一边指向i的概率,g0的一边被卡死了,显然与g的数值不同。g0[i][j+1]=g[i][j]*1/2+g0[i-1][j+1],把后边的g0递推回去,可得等式右边=sum[i][j]*1/2。
使i代表包含目标数字的那段区间,则i从1开始,j从0开始。需要讨论目标数字在左儿子区间还是右儿子区间。当操作区间的左右儿子区间只有一边含有与目标数字相同的数字的时候,相对位置的概率比起深一层是不变的,直接把下一层的dp数组复制上来就好。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int mod=998244353; int n; long long sum[510][510],g[510][510],tw,dp[15][510],ans; int h,f,a[510],b[510]; long long ks(long long x,int k){ long long num=1; while(k){ if(k&1)num=num*x%mod; x=x*x%mod; k>>=1; } return num; } void pre(){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(i==1&&j==1)g[i][j]=1; else if((i==1&&j==2)||(i==2&&j==1)){ g[i][j]=tw; } else{ g[i][j]=(g[i-1][j]*tw%mod+g[i][j-1]*tw%mod)%mod; } } } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++)sum[j][i]=(sum[j-1][i]+g[j][i]*tw%mod)%mod; } } int work(int l,int r,int val,int dep){ if(l==r){ if(l==val)dp[dep][1]=1; return a[l]==a[val]; } int mid=(l+r)/2; int p=work(l,mid,val,dep+1); int q=work(mid+1,r,val,dep+1); if(l<=val&&val<=r){ if(!p||!q){ for(int i=1;i<=p+q;i++)dp[dep][i]=dp[dep+1][i]; } else{ if(val>mid)swap(p,q); for(int i=1;i<=p+q;i++)dp[dep][i]=0; for(int i=1;i<=p;i++){ for(int j=0;j<=q;j++){ if(j==q){ dp[dep][i+j]=(dp[dep][i+j]+dp[dep+1][i]*sum[i][j]%mod)%mod; } else dp[dep][i+j]=(dp[dep][i+j]+dp[dep+1][i]*g[i][j+1]%mod*tw%mod)%mod; } } } } return p+q; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i]; tw=ks(2,mod-2); pre(); sort(b+1,b+n+1); for(int i=1;i<=n;i++){ f=work(1,n,i,1); h=lower_bound(b+1,b+n+1,a[i])-b-1; ans=0; for(int j=1;j<=f;j++){ ans=(ans+dp[1][j]*(h+j)%mod)%mod; } printf("%lld ",ans); } return 0; }
T3:
被满足k的最长区间覆盖的点,答案一定是这个最长区间的长度。由此拓展,如果能找到每一个右端点对应的最远左端点,除非这个右端点无解,否则每个点一定都有被覆盖到的机会。从覆盖它的这些区间中选择最长的作为答案即可。
于是考虑枚举右端点。发现从一个右端点往左处理的时候,min-max值是递减的,而or-and值是递增的。把数字都看成二进制,那么在从右往左扫的过程中不同的or值只会有log个,and值也是如此。那么把到右端点的or值和到右端点的and值都相等的节点们看作同一段,用链表维护。每次把所有的or和and值都合并上新的右端点,把新的相同的一段合并起来。因为对于每个右端点要求最远的左端点,所以从左往右扫每个链表节点,如果当前这一段有可能产生满足答案的左端点,由于or-and在同一段中不变,min-max递减,所以二分即可找到最远的左端点。判断这一段是否可能产生左端点以及二分check都要利用st表查询最大最小值。
把所有右端点和对应的最远左端点扔进优先队列q里,左端点为第一关键字。最后查询答案的时候从左往右扫整个序列,如果q中有左端点满足位置的区间,就把它扔进另一个优先队列q1里,r-l+1位第一关键字,r为第二关键字。每次扫到一个新的位置,检查q1的队头的r是否已经不满足位置。最后如果q1里存在区间就输出队头的区间长度,没有就输出-1。
#include<iostream> #include<cstdio> #include<cmath> #include<queue> using namespace std; const int N=1e6+10; int n,k,a[N],sts[N][21],stb[N][21],top,fir,maxn,cnt=1; struct node{ int l,r,an,o,nxt,lst; }t[N]; priority_queue<pair<int,int> >q,q1; int work(int l,int r){ if(l==r)return 0; int minn=2147483647,maxx=0; int lens=r-l+1,logn; for(int i=0,j=1;j<=lens;i++,j<<=1)logn=i; minn=min(sts[l][logn],sts[r-(1<<logn)+1][logn]); maxx=max(stb[l][logn],stb[r-(1<<logn)+1][logn]); return minn-maxx; } int main() { scanf("%d%d",&n,&k); for(int i=0,j=1;j<=n;i++,j<<=1)maxn=i; for(int i=1;i<=n;i++)scanf("%d",&a[i]),sts[i][0]=stb[i][0]=a[i]; for(int i=1;i<=maxn;i++){ for(int j=1;j+(1<<i)-1<=n;j++){ sts[j][i]=min(sts[j][i-1],sts[j+(1<<(i-1))][i-1]); stb[j][i]=max(stb[j][i-1],stb[j+(1<<(i-1))][i-1]); } } for(int i=1;i<=n;i++){ top++; t[top].l=t[top].r=i; t[top].lst=top-1,t[top].nxt=0; t[top].o=t[top].an=a[i]; t[top-1].nxt=top; int x=top; while(x){ if(t[x].lst==0){ fir=x; break; } t[t[x].lst].an&=a[i],t[t[x].lst].o|=a[i]; if(t[t[x].lst].an==t[x].an&&t[t[x].lst].o==t[x].o){ t[x].l=t[t[x].lst].l; t[t[t[x].lst].lst].nxt=x; t[x].lst=t[t[x].lst].lst; } else x=t[x].lst; } for(int j=fir;j;j=t[j].nxt){ if(t[j].o-t[j].an+work(t[j].r,i)>=k){ int l=t[j].l,r=t[j].r,ans=0; int val=t[j].o-t[j].an; while(l<=r){ int mid=(l+r)/2; if(val+work(mid,i)>=k){ ans=mid; r=mid-1; } else l=mid+1; } q.push(make_pair(-ans,-i)); break; } } } for(int i=1;i<=n;i++){ while(q.size()&&-q.top().first<=i){ q1.push(make_pair(-q.top().second+q.top().first+1,-q.top().second)); q.pop(); } while(q1.size()&&q1.top().second<i)q1.pop(); if(q1.size())printf("%d ",q1.top().first); else printf("-1 "); } return 0; }
我是个憨憨吧…我st表居然一开始是log级别的查询23333