• [考试反思]0423省选模拟78:打卡


    考了四个半小时,签了四个半小时的到。

    吸取昨天的教训,打卡打卡,把签到分拿全然后就能混个名次啥的。

    (其实$T2$还有$9$分的大力分类讨论是懒得写了,码量大分数少写它干啥

    考场上尝试想$T2$的正解来着,结果事实证明它又是个结论题。。。全场最高分$16$就没啥好说的

    $T1$拿完暴力就跑路,$T3$乖乖签到然后没再看一眼,总是选错题这可咋整。。

    T1:Arithmetic

    大意:对于任意一个数列,你可以花费$1$代价添加一个元素,$0$代价进行排序。设你把一个数列$A$经过操作变成以$d$为等差数列的代价为$f(A)$

    多次询问求$sumlimits_{i=l}^{r} sumlimits_{j=l}^{r} f(s[l...r])$。特别的,如果怎么操作都不可能成为等差数列,$f(A)=0$。$n le 3 imes 10^5,1 le d,s_i le 10^7$

    首先比较明显的是,如果一个子区间有相同元素则不可能是等差数列。

    其次,如果一个子区间内$forall i,j$满足$A_i equiv A_j mod d$则合法否则也构造不出等差数列。

    如果能构造出来,那么一个等差数列的代价就是$frac{max-min}{d} -(r-l)$。

    因为这样一个等差数列一共有$frac{max-min}{d} +1$项。你已有的有$r-l+1$项。所以就是这个东西。

    于是我们考虑用线段树维护答案。枚举右端点,线段树下标表示左端点,存储这样一个区间的代价。

    离线询问后,发现需要做的就是查询历史版本和。

    同时维护两个单调栈以更新区间最值。右端点每次右移的时候所有区间代价$-1$

    然后发现如果以当前位置为右端点,某个左端点(及编号更小的左端点)非法了,就需要把前缀的代价都置为$0$

    预处理对于每个左端点,第一个非法的右端点即可。

    所以弄一个线段树,支持区间加,前缀清空,查询版本和。

    假设我们在$t_i$时刻进行了一次区间加$h_i$,如果在查询的时候时间为$T$那么我们要查询的值就是

    $sum (T-t_i)h_i = Tsum h_i  - sum t_ih_i$

    分别维护两节就行了。然后清空的话暴力清就可以(记录上一次清空的右端点,这次从$R+1$开始,同时所有操作的左端点都要与$R+1$取$max$)

    每个点都只会被清一次。所以总复杂度还是$O(nlogn)$

    有点卡常,没有懒标记的时候不要$down$

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define S 1333333
     4 #define ll long long
     5 struct qs{int l,r,o;friend bool operator<(qs a,qs b){return a.r<b.r;}}Q[S];
     6 int RM[S],w[S],n,d,q,R[S],lst[10000001],mx[S],mn[S],mxt,mnt,mxp[S],mnp[S],R0,rm[S],T;
     7 ll ans[S],B[S],A[S],lzA[S],lzB[S];
     8 #define lc p<<1
     9 #define rc lc|1
    10 #define md (L+R>>1)
    11 void down(int p,int L,int R){
    12     if(!lzA[p]&&!lzB[p])return;
    13     A[rc]+=lzA[p]*(R-md);A[lc]+=lzA[p]*(md-L+1);
    14     B[rc]+=lzB[p]*(R-md);B[lc]+=lzB[p]*(md-L+1);
    15     lzA[lc]+=lzA[p];lzB[lc]+=lzB[p];lzA[rc]+=lzA[p];lzB[rc]+=lzB[p];
    16     lzA[p]=lzB[p]=0;
    17 }
    18 void add(int l,int r,int d,int p=1,int L=1,int R=n){
    19     if(l<=L&&R<=r){A[p]+=d*(R-L+1ll);B[p]+=T*(R-L+1ll)*d;lzA[p]+=d;lzB[p]+=1ll*T*d;return;}
    20     down(p,L,R);
    21     if(l<=md)add(l,r,d,lc,L,md); if(r>md)add(l,r,d,rc,md+1,R);
    22     A[p]=A[lc]+A[rc]; B[p]=B[lc]+B[rc];
    23 }
    24 ll ask(int l,int r,int p=1,int L=1,int R=n){
    25     if(l<=L&&R<=r)return A[p]*T-B[p];
    26     down(p,L,R);
    27     return (l<=md?ask(l,r,lc,L,md):0)+(r>md?ask(l,r,rc,md+1,R):0);
    28 }
    29 void reset(int l,int r,int p=1,int L=1,int R=n){
    30     if(L==R){B[p]-=A[p]*T;A[p]=0;return;}
    31     down(p,L,R);
    32     if(l<=md)reset(l,r,lc,L,md); if(r>md)reset(l,r,rc,md+1,R);
    33     A[p]=A[lc]+A[rc]; B[p]=B[lc]+B[rc];
    34 }
    35 void Add(int l,int r,int d){l=max(l,R0+1);if(l<=r)add(l,r,d);}
    36 int main(){
    37     //freopen("arithmetic3.in","r",stdin);freopen("0.out","w",stdout);
    38     cin>>n>>d>>q; R[0]=-1; mx[0]=10000001;
    39     for(int i=1;i<=n;++i)scanf("%d",&w[i]),RM[lst[w[i]]]=i,lst[w[i]]=i,R[i]=w[i]%d,w[i]/=d,RM[i]=n+1;
    40     for(int i=2;i<=n;++i)if(R[i]!=R[i-1])for(int j=i-1;R[j]==R[i-1];--j)RM[j]=min(RM[j],i);
    41     for(int i=1;i<=n;++i)rm[RM[i]]=i;
    42     for(int i=1,l,r;i<=q;++i)scanf("%d%d",&l,&r),Q[i]=(qs){l,r,i};
    43     sort(Q+1,Q+1+q); int _=1;
    44     for(T=1;T<=n;++T){
    45         if(rm[T]>R0)reset(R0+1,rm[T]),R0=rm[T];
    46         Add(1,T-1,-1);
    47         while(mx[mxt]<w[T])Add(mxp[mxt-1]+1,mxp[mxt],-mx[mxt]),mxt--; mx[++mxt]=w[T]; mxp[mxt]=T; Add(mxp[mxt-1]+1,T,w[T]);
    48         while(mn[mnt]>w[T])Add(mnp[mnt-1]+1,mnp[mnt],mn[mnt]),mnt--; mn[++mnt]=w[T]; mnp[mnt]=T; Add(mnp[mnt-1]+1,T,-w[T]);
    49         while(Q[_].r==T)T++,ans[Q[_].o]=ask(Q[_].l,T),_++,T--;
    50     }for(int i=1;i<=q;++i)printf("%lld
    ",ans[i]);
    51 }
    View Code

    T2:Epidemic

    大意:有$n$个$A_i$和$m$个$B_i$,在第$i$轮(从$0$开始),$A_{i mod n}=B_{i mod m}=A_{i mod n} or B_{i mod m}$。给出最开始为$1$的点不超过$2 imes 10^5$个。

    求多少轮后所有的$A,B$均为$1$。$n,m le 10^9$

    首先非常明显的是,当$n,m$不互质的时候,可以把整个局面划分为$gcd$个子局面再合并答案,所以只考虑互质的情况即可。

    首先有一个神仙结论:所有点被传染的方式(以$A$为例),传染它的$B$城市,要么最开始就是$1$,要么被一个最开始就是$1$的$A$类点

    这样的话,我们考虑所有$A$类点是如何被传染的。

    那么有关的点一共只有两种:初始就是$1$的,以及如果存在初始时$B_j=1$那么在$A_{j mod n}$也可能是个传染源。

    按照上面的传染规则,我们把所有的传染源排在左边,然后发现,对于所有节点编号$Id m-rn equiv i$。

    那么对于一个点编号为$x$的点一定是它在排列里的前驱传染的他,判一下各种边界就好了。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define ll long long
     4 int g,n,m;ll ans,a,b;
     5 vector<int>A[200005],B[200005];
     6 struct pt{int p,o;friend bool operator<(pt x,pt y){return (x.p<<1|x.o)<(y.p<<1|y.o);}}p[200005];
     7 int gcd(int x,int y){return y?gcd(y,x%y):x;}
     8 void exgcd(ll&x,ll&y,int a,int b){
     9     if(!b){x=1;y=0;return;}
    10     exgcd(x,y,b,a%b);int r=x;x=y;y=r-a/b*y;
    11 }
    12 int main(){
    13     cin>>n>>m; g=gcd(n,m); n/=g;m/=g; p[0].p=-1;
    14     if(g>200000)return puts("-1"),0;
    15     cin>>a; for(int i=1,x;i<=a;++i)scanf("%d",&x),A[x%g].push_back(x/g);
    16     cin>>b; for(int i=1,x;i<=b;++i)scanf("%d",&x),B[x%g].push_back(x/g);
    17     exgcd(a,b,n,m); a=(a%m+m)%m; b=(b%n+n)%n;
    18     for(int _=0;_<g;++_){
    19         if(A[_].empty()&&B[_].empty())return puts("-1"),0;
    20         int t=0;
    21         for(int i=0;i<A[_].size();++i)p[++t]=(pt){A[_][i]*1ll*b%n,1};
    22         for(int i=0;i<B[_].size();++i)p[++t]=(pt){B[_][i]*1ll*b%n,0};
    23         sort(p+1,p+1+t);
    24         p[t+1]=p[1];p[t+1].p+=n;
    25         for(int i=1;i<=t;++i)if(p[i+1].p-p[i].p>1)ans=max(ans,((p[i+1].p-p[i].p-1ll)*m+(1ll*p[i].p*m%n))*g+_);
    26         for(int i=1;i<=t;++i)if(p[i].p!=p[i+1].p&&p[i].o==0)ans=max(ans,(1ll*p[i].p*m%n)*g+_);
    27         
    28         t=0;
    29         for(int i=0;i<A[_].size();++i)p[++t]=(pt){A[_][i]*1ll*a%m,0};
    30         for(int i=0;i<B[_].size();++i)p[++t]=(pt){B[_][i]*1ll*a%m,1};
    31         sort(p+1,p+1+t);
    32         p[t+1]=p[1];p[t+1].p+=m;
    33         for(int i=1;i<=t;++i)if(p[i+1].p-p[i].p>1)ans=max(ans,((p[i+1].p-p[i].p-1ll)*n+(1ll*p[i].p*n%m))*g+_);
    34         for(int i=1;i<=t;++i)if(p[i].p!=p[i+1].p&&p[i].o==0)ans=max(ans,(1ll*p[i].p*n%m)*g+_);
    35     }cout<<ans<<endl;
    36 }
    View Code

    T3:sort

    大意:有一个长为$n$的有$k元素未知的排列。称一次操作为 选定偶数个下标然后两两配对然后交换其中的每对元素的位置。

    设$f,g$表示把排列排成单位排列的最少步数以及这个步数下的操作方案数。对所有填充排列的方式求$f,g$的和。$n le 10^5,k le 12$

    先考虑$k=0$

    排列的题好像有几种常用的方法,这次是把所有元素看成边连起来,会形成若干环。

    如果全是大小为$1$的环,那么步数为$0$方案为$1$

    如果全是大小为$le2$的环,那么步数是$1$方案是$1$

    然后观察大样例之类的发现好像有$f = 2$。这的确成立。

    手模一下发现,如果只有一个环,那么你只要选定其中一个元素在第一轮操作后的位置,那么其余元素就都能依次确定。所以方案数是$size$

    如果只有两个等大的环,那么你第一轮操作可能会跨过两个环,发现这样的方案是$size$的,还要加上操作并不跨过环的$size^2$

    对于更多等大的环,可以有一个$dp_{i,j}$表示大小为$i$的环有$j$个的方案数。$dp_{i,j}=dp_{i,j-1} imes i + dp_{i,j-2} imes i imes (j-1)$

    对于不等大的环,发现两边如果有跨环交换则一定不合法,所以只需要对每种长度分别考虑最后答案相乘。

    如果有未知元素那么相当于有若干环和$k$条链,你要拿这些链组成新的环,搜索就行了。

    预处理上面的$dp$(只有$n ln n$和值)和逆元,然后每次对于一个新加的环除一下乘一下就好。

    $O(n ln n +k imes Bell(k))$

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define mod 1000000007
     4 #define S 100005
     5 int F,G=1,a[S],n,k,cnt[S],al[S],d[S],L[13],lc,tot,X[S],Z[S];
     6 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
     7 vector<int>dp[S],Iv[S];
     8 void sch(int x,int C){
     9     if(x==k+1){
    10         int r=1;
    11         for(int i=1;i<=C;++i)for(int j=2;j<Z[i];++j)r*=j;
    12         int g=G;
    13         for(int i=1;i<=C;++i)x=X[i],g=1ll*g*dp[x][cnt[x]+1]%mod*Iv[x][cnt[x]]%mod,cnt[x]++;
    14         if(cnt[1]==n)tot+=r;
    15         else if(cnt[1]+(cnt[2]<<1)==n)F+=r,tot+=r;
    16         else F+=2*r,tot=(tot+g*1ll*r)%mod;
    17         for(int i=1;i<=C;++i)cnt[X[i]]--;
    18         return;
    19     }
    20     for(int i=1;i<=C;++i)X[i]+=L[x],Z[i]++,sch(x+1,C),X[i]-=L[x],Z[i]--;
    21     X[++C]=L[x];Z[C]=1;sch(x+1,C);
    22 }
    23 int main(){//freopen("sort5.in","r",stdin);
    24     cin>>n>>k;
    25     for(int i=1;i<=n;++i){
    26         dp[i].resize(n/i+1); Iv[i].resize(n/i+1);
    27         dp[i][0]=Iv[i][0]=1; dp[i][1]=i; Iv[i][1]=qp(dp[i][1],mod-2);
    28         for(int j=2;j<=n/i;++j)dp[i][j]=(dp[i][j-1]+dp[i][j-2]*(j-1ll))%mod*i%mod,Iv[i][j]=qp(dp[i][j],mod-2);
    29     }
    30     for(int i=1;i<=n;++i)scanf("%d",&a[i]);
    31     for(int i=1;i<=n;++i)if(a[i])d[i]++,d[a[i]]++;
    32     for(int i=1;i<=n;++i)if(d[i]==1&&a[i]){
    33         int l=0,p=i;
    34         while(p)al[p]=1,p=a[p],l++;
    35         L[++lc]=l;
    36     }
    37     for(;lc<=k;)L[++lc]=1;
    38     for(int i=1;i<=n;++i)if(!al[i]&&d[i]==2){
    39         int l=0,p=i;
    40         while(!al[p])al[p]=1,p=a[p],l++;
    41         cnt[l]++;
    42     }
    43     for(int i=1;i<=n;++i)G=(G*1ll*dp[i][cnt[i]])%mod;
    44     sch(1,0);
    45     cout<<F<<endl<<tot<<endl;
    46 }
    View Code
  • 相关阅读:
    Mongodb常用操作(转载)
    java中对象转换工具类,很好用
    IDEA中配置tomcat出现乱码的解决
    小山博客--面试题答案
    Redis简单配置和使用
    线程的控制和线程池
    进程与线程详细解释
    Quartz .Net(定时框架):
    C#面向对象几组关键字的详解(this,base,dynamic,var,const,readonly,is,as)
    C#设计模式--抽象工厂模式(创建型模式)
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12765070.html
Copyright © 2020-2023  润新知