3.14省选模拟
3.14省选模拟
$60+60+91=211$
$T1$想出正解了,然后素数我预处理素数处理少了,还有一个变量原本是$ull->int$,$100->60$
$T2$评测机的$spj$寄了,$100->60$
$T3$写了个没有势能保护的线段树,由于当年省选数据过水$40->91$
算是这几天打的最好的一次比赛了
$T1$
简单的树同构+换根$dp$问题
#include<bits/stdc++.h> #define ull unsigned long long #define MAXN 200005 using namespace std; ull hasha[MAXN],hash1[MAXN],Mid[MAXN]; int head[MAXN],nxt[MAXN],to[MAXN],Ans=MAXN,tot; int pri[MAXN],siz[MAXN],du[MAXN],cnt; vector<int>rd[MAXN]; bool vis[MAXN*10]; map<ull,bool>mp; void add(int u,int v) { tot++; to[tot]=v; nxt[tot]=head[u]; head[u]=tot; } void Init() { for(int i=2;i<=MAXN*10-5;i++) { if(!vis[i]) { pri[++cnt]=i; } for(int j=1;j<=cnt&&i*pri[j]<=MAXN*10-5;j++) { vis[i*pri[j]]=1; if(i%pri[j]==0) break; } } } void dfs(int now,int fa) { siz[now]=1; hasha[now]=1ull; for(int i=head[now];i;i=nxt[i]) { int y=to[i]; if(y==fa) continue; dfs(y,now); siz[now]+=siz[y]; hasha[now]+=hasha[y]*pri[siz[y]]; } } void dfs_gen(int now,int fa) { int now_siz=siz[now]; ull now_hash=hasha[now]; for(int i=head[now];i;i=nxt[i]) { int y=to[i]; if(y==fa) continue; hasha[now]-=(1ull*hasha[y]*pri[siz[y]]); siz[now]-=siz[y]; siz[y]+=siz[now]; hasha[y]+=hasha[now]*pri[siz[now]]; Mid[y]=hasha[y]; mp[hasha[y]]=true; dfs_gen(y,now); siz[now]=now_siz; hasha[now]=now_hash; } } void dfs_b(int now,int fa) { siz[now]=1; hash1[now]=1ull; for(int i=0;i<rd[now].size();i++) { int y=rd[now][i]; if(y==fa) continue; dfs_b(y,now); siz[now]+=siz[y]; hash1[now]+=hash1[y]*pri[siz[y]]; } } void dfs_genb(int now,int fa) { int now_siz=siz[now]; ull now_hash=hash1[now]; for(int i=0;i<rd[now].size();i++) { int y=rd[now][i]; if(y==fa) continue; if(du[y]==1) { if(mp[hash1[now]-pri[1]]) { Ans=min(Ans,y); } } } for(int i=0;i<rd[now].size();i++) { int y=rd[now][i]; if(y==fa) continue; hash1[now]-=(1ull*hash1[y]*pri[siz[y]]); siz[now]-=siz[y]; siz[y]+=siz[now]; // cout<<"y: "<<y<<" "<<hash1[y]<<" "; hash1[y]+=hash1[now]*pri[siz[now]]; // cout<<hash1[y]<<endl; dfs_genb(y,now); if(du[now]==1&&now==1) { if(mp[hash1[y]-pri[1]]) { Ans=min(Ans,now); } } siz[now]=now_siz; hash1[now]=now_hash; } } int n,u,v; int main() { // freopen("a.in","r",stdin); // freopen("a.out","w",stdout); scanf("%d",&n); Init(); for(int i=1;i<n;i++) { scanf("%d%d",&u,&v); add(u,v);add(v,u); } dfs(1,1); Mid[1]=hasha[1]; dfs_gen(1,1); // for(int i=1;i<=n;i++) // { // cout<<"rt: "<<i<<" "<<Mid[i]<<endl; // } n++; for(int i=1;i<n;i++) { scanf("%d%d",&u,&v); rd[u].push_back(v); rd[v].push_back(u); du[u]++; du[v]++; } dfs_b(1,1); dfs_genb(1,1); cout<<Ans<<endl; }
$T2$
随机化,此时就是让求每个节点能到达多少叶子节点,暴力跑肯定不行,这个时候用随机化搞事情
首先对于每个节点不能存所有能到达的叶子,那么就对于能到的叶子存其中$k$个叶子,对于每个叶子随机一个权值,满足$W_i\in[1,RANDMAX]$
首先我们定义我们能取到的叶子的第$k$小记录下来
$\frac{F_k}{RANDMAX}=\frac{k}{ans}$
这个式子的意思是,你有多少叶子,那么第$k$小的叶子的期望权值也是把所有叶子平均分到所有值域上,第$k$个的位置也是对应的区间
#include<bits/stdc++.h> using namespace std; const int MAXN=1000004,S=50; int f[MAXN][S],val[MAXN][2],n,m; double Ans[MAXN]; int main() { srand(20050323); cin>>n>>m; for(int i=m+1;i<=n;i++) { cin>>val[i][0]>>val[i][1]; } for(int t=1;t<=2;t++) { for(int i=1;i<=m;i++) { for(int j=0;j<S;j++) { f[i][j]=rand(); } } for(int i=m+1;i<=n;i++) { for(int j=0;j<S;j++) { Ans[i]+=(f[i][j]=min(f[val[i][0]][j],f[val[i][1]][j])); } } } for(int i=m+1;i<=n;i++) { cout<<(int)(RAND_MAX/Ans[i]*S*2.0-0.5)<<endl; } return 0; }
$T3$
显然吉老师线段树,由于正解过于繁琐,我在考场上写了一个没有势能保护的线段树
可以获得$91pts$的好成绩
#include<bits/stdc++.h> #define int long long #define rs ((now<<1)|1) #define ls (now<<1) #define MAXN 100005 using namespace std; struct node { int l,r,Min,sum,lsum,rsum,mxsum; }tr[MAXN<<2]; int a[MAXN],n,q; void upd(int now) { tr[now].sum=(tr[ls].sum+tr[rs].sum); tr[now].Min=min(tr[ls].Min,tr[rs].Min); tr[now].lsum=max(tr[ls].sum+tr[rs].lsum,tr[ls].lsum); tr[now].rsum=max(tr[rs].sum+tr[ls].rsum,tr[rs].rsum); tr[now].mxsum=max(max(tr[ls].mxsum,tr[rs].mxsum),tr[rs].lsum+tr[ls].rsum); } void build(int now,int l,int r) { tr[now].l=l,tr[now].r=r; if(l==r) { tr[now].Min=a[l]; tr[now].sum=a[l]; tr[now].lsum=max(0ll,a[l]); tr[now].rsum=max(0ll,a[l]); tr[now].mxsum=max(0ll,a[l]); return ; } int mid=(l+r)>>1ll; build(ls,l,mid); build(rs,mid+1,r); upd(now); // cout<<"now: "<<l<<" "<<r<<" "<<tr[now].lsum<<" "<<tr[now].rsum<<" "<<tr[now].mxsum<<endl; } void change(int now,int l,int r,int x) { if(tr[now].Min>=x) return ; if(tr[now].l==tr[now].r) { tr[now].Min=x; tr[now].sum=x; tr[now].lsum=max(0ll,x); tr[now].rsum=max(0ll,x); tr[now].mxsum=max(0ll,x); return ; } int mid=(tr[now].l+tr[now].r)>>1ll; if(l<=mid) change(ls,l,r,x); if(r>mid) change(rs,l,r,x); upd(now); } node query(int now,int l,int r) { if(tr[now].l>=l&&tr[now].r<=r) { return tr[now]; } int mid=(tr[now].l+tr[now].r)>>1ll; if(r<=mid) return query(ls,l,r); if(l>mid) return query(rs,l,r); node a=query(ls,l,r); node b=query(rs,l,r); node c; c.sum=a.sum+b.sum; c.lsum=max(a.sum+b.lsum,a.lsum); c.rsum=max(b.sum+a.rsum,b.rsum); c.mxsum=max(max(a.mxsum,b.mxsum),b.lsum+a.rsum); return c; } int opt,l,r,x; signed main() { // freopen("c.in","r",stdin); // freopen("c.out","w",stdout); scanf("%lld%lld",&n,&q); for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); } build(1,1,n); for(int i=1;i<=q;i++) { scanf("%lld",&opt); if(opt==0) { scanf("%lld%lld%lld",&l,&r,&x); change(1,l,r,x); } else { scanf("%lld%lld",&l,&r); printf("%lld\n",query(1,l,r).mxsum); } } } /* 5 7 2 -4 6 -5 5 1 1 5 0 1 5 -4 1 1 5 0 3 4 -1 1 1 5 0 1 3 -1 1 1 5 */
正解:
吉老师线段树基本操作,区间取$min/max$
这个的操作是维护最大子段和
这个东西无非是记录三个东西,$lmax,rmax,Max$
到了这大概就豁然开朗了,我们还是照样维护这几个东西,只不过你还需要维护一个这三个区间内最小值出现的个数,那么更改的时候,找到了更改值在最小值和次小值之间,就可以停止递归操作了(并不是)
主要问题是我并不知道当前节点的$pre,bed$会不会变或者当前节点的最大值会不会变,首先最大值肯定是前面的,其实最大区间的不需要记录的,改变的时候跟着改,然后最后询问的时候直接问就好了
至于我们不知道是否$pre$和$bed$是否会变,那就设个阈值就好了
写法很$nb,$貌似这题当年还是$XXF$官方数据,确实是水数据了...
//总的感觉是,我在在做阅读理解 #include<bits/stdc++.h> #define rs ((now<<1)|1) #define INF 0x3f3f3f3f #define ll long long #define MAXN 500005 #define ls (now<<1) using namespace std; int n,m,a[MAXN]; struct node { int val,cnt; //最小值,个数 ll sum; //区间和 int ask(int x) const { return (val==x)*cnt; //这一部分为了更新最小值个数 } void add(int x,int v) { if(v==val) val+=x,sum+=1ll*cnt*x; //这里是更新最小值,v是最小值 return ; } int operator < (const node &u) const { //更新一下最大值 return sum!=u.sum?sum<u.sum:cnt<u.cnt; } node operator + (const node &u) const { //更新区间最小值,区间加和 //区间最小值个数 static node out; out.val=min(val,u.val); out.sum=sum+u.sum; out.cnt=ask(out.val)+u.ask(out.val); return out; } }; struct Tree { int Maxn,Mini,val,tag; //次小,最小,阈值,lz node pre,nxt,sum,all; //前缀,后缀,全部,最大子段 void Init(int x) { Mini=x,Maxn=INF; sum=(node){x,1,(ll)x}; //初始化 if(x<=0) { pre=nxt=all=(node){INF,0,0ll}; //我们递归,其实第一个值就是判断递归使用的 //因为小于0,我们的值 val=1; //阈值 } else { pre=nxt=all=(node){x,1,(ll)x}; val=INF; } } void add(int x) { //这里是更新最小值,下传tag的过程 pre.add(x,Mini); nxt.add(x,Mini);tag+=x; sum.add(x,Mini); all.add(x,Mini);Mini+=x; } }tr[MAXN<<2]; void cmin(int &x,int y) { if(y-x>>31)x=y; } int clac(const node &x,const node &y,const node &z,int v,ll s=0,int d=0) { if((s=x.sum-y.sum-z.sum)>0&&(d=y.ask(v)+z.ask(v)-x.ask(v))) return min((ll)INF,s/d+1+v); return INF; } //Tree operator+(const Tree&x,const Tree&y){ // static Tree out; // out.val=min(x.val,y.val),out.all=max(max(x.all,y.all),x.nxt+y.pre); // out.sum=x.sum+y.sum,out.pre=max(x.pre,x.sum+y.pre),out.nxt=max(x.nxt+y.sum,y.nxt); // if(x.Mini==y.Mini)out.Mini=x.Mini,out.Maxn=min(x.Maxn,y.Maxn); // else out.Mini=min(x.Mini,y.Mini),out.Maxn=min(min(x.Maxn,y.Maxn),max(x.Mini,y.Mini)); // cmin(out.val,clac(x.pre,x.sum,y.pre,out.Mini)); // cmin(out.val,clac(y.nxt,x.nxt,y.sum,out.Mini)); // return out; //} Tree operator + (const Tree &x,const Tree &y) { static Tree out; // out.val=min(x.val,y.val),out.all=max(max(x.all,y.all),x.nxt+y.pre); // out.sum=x.sum+y.sum,out.pre=max(x.pre,x.sum+y.pre),out.nxt=max(x.nxt+y.sum,y.nxt); // if(x.Mini==y.Mini)out.Mini=x.Mini,out.Maxn=min(x.Maxn,y.Maxn); // else out.Mini=min(x.Mini,y.Mini),out.Maxn=min(min(x.Maxn,y.Maxn),max(x.Mini,y.Mini)); // Tree out; out.val=min(x.val,y.val);//更新阈值 out.all=max(max(x.all,y.all),x.nxt+y.pre); out.sum=(x.sum+y.sum); out.pre=max(x.pre,x.sum+y.pre); out.nxt=max(x.nxt+y.sum,y.nxt); if(x.Mini==y.Mini) { out.Mini=x.Mini; out.Maxn=min(x.Maxn,y.Maxn); } else { out.Mini=min(x.Mini,y.Mini); out.Maxn=min(min(x.Maxn,y.Maxn),max(x.Mini,y.Mini)); } cmin(out.val,clac(x.pre,x.sum,y.pre,out.Mini)); cmin(out.val,clac(y.nxt,x.nxt,y.sum,out.Mini)); return out; } void upd(int now) { tr[now]=tr[ls]+tr[rs]; //更新节点信息 } void pd(int now) { if(tr[now].tag) { if(tr[ls].Mini==tr[now].Mini-tr[now].tag) tr[ls].add(tr[now].tag); if(tr[rs].Mini==tr[now].Mini-tr[now].tag) tr[rs].add(tr[now].tag); //线段树基操 tr[now].tag=0; } } void build(int now,int l,int r) { if(l==r) { tr[now].Init(a[l]); //初始化 return ; } int mid=(l+r)>>1; build(ls,l,mid); build(rs,mid+1,r); upd(now); } void dfs(int now,int L,int R,int x) { //这个是递归到阈值的过程 // cout<<L<<" "<<R<<" "<<x<<endl; if(x<=tr[now].Mini) return ; if(L==R) return tr[now].Init(x); if(x<tr[now].val&&x<tr[now].Maxn) { return tr[now].add(x-tr[now].Mini); //如果阈值大于目前,就可以结束了 //所以说,递归的条件是,大于阈值,所以阈值越小限制越多 } int mid=(L+R)>>1; pd(now); dfs(ls,L,mid,x),dfs(rs,mid+1,R,x); upd(now); } Tree query(int now,int L,int R,int l,int r) { // cout<<tr[now].all.sum<<endl; if(L==l&&R==r) { return tr[now]; //简单询问 } int mid=(L+R)>>1; pd(now); if(l>mid) return query(rs,mid+1,R,l,r); if(r<=mid) return query(ls,L,mid,l,r); return query(ls,L,mid,l,mid)+query(rs,mid+1,R,mid+1,r); } void change(int now,int L,int R,int l,int r,int x) { if(l==L&&r==R) { // cout<<L<<" "<<R<<" "<<l<<" "<<r<<" "<<x<<endl; return dfs(now,l,r,x); } int mid=(L+R)>>1; pd(now); if(l>mid) return change(rs,mid+1,R,l,r,x),upd(now); if(r<=mid) return change(ls,L,mid,l,r,x),upd(now); change(ls,L,mid,l,mid,x); change(rs,mid+1,R,mid+1,r,x); upd(now); } int opt,l,r,x; int main() { // freopen("c.in","r",stdin); // freopen("c.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } build(1,1,n); //建树过程 for(int i=1;i<=m;i++) { scanf("%d",&opt); if(opt==0) { scanf("%d%d%d",&l,&r,&x); change(1,1,n,l,r,x); } else { scanf("%d%d",&l,&r); cout<<query(1,1,n,l,r).all.sum<<endl; } } } //这代码确实很印度啊...