[考试反思]0115省选模拟8:等待
不好。但凑和。
再也不相信对拍了(然而肯定还是要写的)
T1又是一个根号算法(当然可以更优),没想到,写了一个剪枝暴力,本来从复杂度上来说应该有80分,但是因为没有发现
「加0不算对一个数进行修改」
一个细节没注意,挂了45分。
还写对拍了,但是暴力也没有注意这个细节,于是二者愉快的拍上了。。。
T3的话写了个迷之网络流,感觉不太对于是愉快的写了一个状压。
话说这辈子第一次靠自己不抄代码把$tarjan$写对,开心。(我为啥没联赛退役?因为它没考啊)
又愉快的拍上了二十万组。于是自信的交了网络流。
结果网络流20分,状压50分。。。
就这些弱智玩意送了小一百分。。。能混到这个rank就靠一个T2
然而T2是我三道题里花时间最少的,也没写对拍,反而得分最多。。。
运气好,爱用STL,在别人开数组的地方开了一个$unoredred \ map$。数据微锅的情况下没有$RE$。靠非实力因素多拿了$13$分。
所以其实rank应该再掉两个。。。
考得还是挺烂的,但是这次除了没认真注意细节以外貌似是尽力了。。。
所以我也做不出什么评价。只能继续加油吧。。。
T1:序列
题意:维护序列支持区间加,区间取$max$,询问单点值及从开始到现在为止的变化次数。$n,m\le 100000$
内存限制$32MB$。时间限制$1000ms$
线段树,假装小清新,维护最大值与最小值并注意+0不算改变后可以拿到80分。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 400002 4 #define ll long long 5 #define lc p<<1 6 #define rc p<<1|1 7 #define md (cl+cr>>1) 8 ll lz[S],mn[S],mx[S];int n,a[S],m,tms[S];char s[9]; 9 void up(int p){mn[p]=min(mn[lc],mn[rc]);mx[p]=max(mx[lc],mx[rc]);} 10 void build(int p=1,int cl=1,int cr=n){ 11 if(cl==cr){mn[p]=mx[p]=a[cl];return;} 12 build(lc,cl,md);build(rc,md+1,cr);up(p); 13 } 14 void down(int p){ 15 if(tms[p])tms[lc]+=tms[p],tms[rc]+=tms[p],tms[p]=0; 16 if(mn[p]==mx[p])mn[lc]=mn[rc]=mx[lc]=mx[rc]=mn[p],lz[p]=0; 17 else if(lz[p])mn[lc]+=lz[p],mn[rc]+=lz[p],mx[lc]+=lz[p],mx[rc]+=lz[p],lz[lc]+=lz[p],lz[rc]+=lz[p],lz[p]=0; 18 } 19 void Max(int l,int r,int w,int p=1,int cl=1,int cr=n){ 20 if(w<=mn[p])return; 21 if(l<=cl&&cr<=r&&w>mx[p]){tms[p]++;lz[p]=0;mn[p]=mx[p]=w;return;} 22 down(p); 23 if(l<=md)Max(l,r,w,lc,cl,md); 24 if(r>md)Max(l,r,w,rc,md+1,cr); 25 up(p); 26 } 27 void add(int l,int r,int v,int p=1,int cl=1,int cr=n){ 28 if(v==0)return; 29 if(l<=cl&&cr<=r){ 30 if(mn[p]==mx[p])mn[p]+=v,mx[p]+=v,lz[p]=0; 31 else lz[p]+=v,mn[p]+=v,mx[p]+=v; 32 tms[p]++;return; 33 } 34 down(p); 35 if(l<=md)add(l,r,v,lc,cl,md); 36 if(r>md)add(l,r,v,rc,md+1,cr); 37 up(p); 38 } 39 int ask(int x,int p=1,int cl=1,int cr=n){ 40 if(cl==cr)return p; 41 down(p); 42 return x<=md?ask(x,lc,cl,md):ask(x,rc,md+1,cr); 43 } 44 int main(){//freopen("1.in","r",stdin);freopen("1.out","w",stdout); 45 scanf("%d",&n); 46 for(int i=1;i<=n;++i)scanf("%d",&a[i]); 47 build(); 48 scanf("%d",&m);while(m-->0){ 49 scanf("%s",s);int a,b,c; 50 if(s[0]=='A')scanf("%d%d%d",&a,&b,&c),add(a,b,c); 51 if(s[0]=='M')scanf("%d%d%d",&a,&b,&c),Max(a,b,c); 52 if(s[0]=='Q')scanf("%d",&a),a=ask(a),printf("%lld %d\n",mn[a],tms[a]); 53 } 54 }
正解很多,能看懂的就一个分块。块内部排序,维护加法标记和「已经进行过的取max中的最大值」两个标记。
有序之后,变化次数的累加可以差分,而且如果整个块都被操作的话单调性不变。
然后T的还不如线段树。把块长开成36之后它AC了???36是个什么块啊?????
理论复杂度$O(n\sqrt{n} log \ n)$。然而我常数写太丑了。。。卡过去的
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 400002 4 #define ll long long 5 ll lz[S],mx[S];int bc,n,m,tms[S],cl[S],cr[S],bl[S],B,lzt[S],pt[S];char s[9]; 6 struct data{long long v;int p;}da[S]; 7 bool cv(data x,data y){return x.v<y.v;} 8 bool cp(data x,data y){return x.p<y.p;} 9 void down(int x){ 10 for(int i=cr[x]-1;i>=cl[x];--i)lzt[i]+=lzt[i+1]; 11 for(int i=cl[x];i<=cr[x];++i)tms[da[i].p]+=lzt[i],lzt[i]=0; 12 for(int i=cl[x];i<=cr[x];++i)da[i].v+=lz[x]; 13 for(int i=cl[x];i<=cr[x];++i)if(da[i].v<mx[x])da[i].v=mx[x]; 14 lz[x]=0;mx[x]=-123456789012345;pt[x]=cl[x]; 15 sort(da+cl[x],da+cr[x]+1,cp); 16 } 17 void up(int x){sort(da+cl[x],da+cr[x]+1,cv);} 18 int main(){//freopen("1.in","r",stdin);freopen("1.out","w",stdout); 19 scanf("%d",&n); 20 for(int i=1;i<=n;++i)scanf("%lld",&da[i].v),da[i].p=i; 21 B=36;bc=n/B; 22 for(int i=1;i<=bc;++i)cl[i]=i*B-B+1,cr[i]=i*B; 23 if(cr[bc]!=n)cr[++bc]=n,cl[bc]=cr[bc-1]+1; 24 for(int i=1;i<=bc;++i)for(int j=cl[i];j<=cr[i];++j)bl[j]=i; 25 for(int i=1;i<=bc;++i)up(i),mx[i]=-123456789012345,pt[i]=cl[i]; 26 scanf("%d",&m);while(m-->0){ 27 scanf("%s",s);int a,b,c; 28 if(s[0]=='A'){ 29 scanf("%d%d%d",&a,&b,&c); 30 if(c==0)continue; 31 if(bl[a]==bl[b]){down(bl[a]);for(int i=a;i<=b;++i)da[i].v+=c,tms[i]++;up(bl[a]);} 32 else{ 33 down(bl[a]);for(int i=a;i<=cr[bl[a]];++i)da[i].v+=c,tms[i]++;up(bl[a]); 34 down(bl[b]);for(int i=cl[bl[b]];i<=b;++i)da[i].v+=c,tms[i]++;up(bl[b]); 35 for(int i=bl[a]+1;i<bl[b];++i)lz[i]+=c,mx[i]+=c,lzt[cr[i]]++; 36 } 37 } 38 if(s[0]=='M'){ 39 scanf("%d%d%d",&a,&b,&c); 40 if(bl[a]==bl[b]){down(bl[a]);for(int i=a;i<=b;++i)if(da[i].v<c)da[i].v=c,tms[i]++;up(bl[a]);} 41 else{ 42 down(bl[a]);for(int i=a;i<=cr[bl[a]];++i)if(da[i].v<c)da[i].v=c,tms[i]++;up(bl[a]); 43 down(bl[b]);for(int i=cl[bl[b]];i<=b;++i)if(da[i].v<c)da[i].v=c,tms[i]++;up(bl[b]); 44 for(int i=bl[a]+1;i<bl[b];++i){ 45 if(c<=mx[i])continue; 46 while(pt[i]<cr[i]&&lz[i]+da[pt[i]+1].v<c)pt[i]++; 47 if(lz[i]+da[pt[i]].v>=c)continue; 48 lzt[pt[i]]++;mx[i]=c; 49 } 50 } 51 } 52 if(s[0]=='Q')scanf("%d",&a),down(bl[a]),printf("%lld %d\n",da[a].v,tms[a]),up(bl[a]); 53 } 54 }
T2:旅行计划
题意:无向图多次询问两点之间在模意义下的最短路(询问间模数可能不同)$n,m,q \le 10^5$
子任务:$k$为奇数/$k=2,w=1$
其实这两档部分分给的挺好的,但是想到正解仍然不容易。
k为奇数时你可以在同一条边来回走直到刷出你想要的数,所以只要联通答案就是$0$
对于后者,奇偶染色判奇环,如果联通块内存在奇环一定可以刷出想要的余数,否则就看两个点颜色是否相同。
对于其它子任务,因为要取模和环的问题所以考场上我想到了要gcd,然后就想不下去了。
事实上我们预处理把每个联通块里的边都除以边权的gcd再奇偶染色。
询问时,如果模数中2的指数少于边的gcd中的2的指数,那么余数一定是0。
否则如果k是奇数也可以刷出想要的数。
否则如果有奇环也一定能刷出来。
再否则如果染色相同那么也就不用刷了最短路模完就是0了。
再否则,我们也可以通过刷边的方法,将答案最小化为$gcd(k,gcd(edge))$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 200005 4 int n,m,q,fir[S],l[S],to[S],v[S],bl[S],co[S],y[S],tim,g,ans,k,ec,eg[S]; 5 void link(int a,int b,int c){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=c;} 6 void paint(int p,int c){ 7 bl[p]=tim;co[p]=c; 8 for(int i=fir[p];i;i=l[i])if(!co[to[i]])paint(to[i],(co[p]+v[i]&1)+2); 9 else if((co[p]+v[i]&1)+2!=co[to[i]])y[tim]=1; 10 } 11 int gcd(int a,int b){return b?gcd(b,a%b):a;} 12 int main(){ 13 scanf("%d%d%d",&n,&m,&q); 14 for(int i=1,a,b,c;i<=m;++i)scanf("%d%d%d",&a,&b,&c),link(a,b,c),link(b,a,c); 15 for(int i=1;i<=n;++i)if(!co[i])tim++,paint(i,2); 16 for(int i=1;i<=n;++i)for(int j=fir[i];j;j=l[j])eg[bl[i]]=gcd(eg[bl[i]],v[j]); 17 for(int i=1;i<=tim;++i)y[i]=0; 18 for(int i=1;i<=n;++i)for(int j=fir[i];j;j=l[j])v[j]/=eg[bl[i]]; 19 for(int i=1;i<=n;++i)co[i]=0;tim=0; 20 for(int i=1;i<=n;++i)if(!co[i])tim++,paint(i,2); 21 while(q-->0){ 22 int x;scanf("%d%d%d",&x,&g,&k); 23 if(bl[x]!=bl[g])puts("NIE"); 24 else printf("%d\n",eg[bl[x]]/gcd(k,eg[bl[x]])%2==0||k&1||y[bl[x]]||co[x]==co[g]?0:gcd(k,eg[bl[x]])); 25 } 26 }
T3:Hack
题意:有向联通图,选定边集使所有1到n的路径均经过边集中的边合计恰好一次,最小化边权和。$n\le100,m \le 2500$
模型很像最小割。但是最小割并没有保证恰好经过一次。
经过多于一次的方案就是走某些路径由最小割后$T$点集走回了$S$点集。
于是我们把反向边开成$inf$就好了。这样就保证了即使它可以往回走的路径也被割断。
然而没必要$tarjan$缩点。直接按照含义跑,最后最小割是0表示不联通,无解。最小割$inf$表示都至少经过两次,也无解。
判掉之后你的网络流图上反向边$inf$成环所以你一定不割环上边所以是对的。$miku$大神实测可以$AC$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 1234567 4 int fir[S],l[S],to[S],ec,v[S],dfn[S],low[S],tim,in[S],s[S],tp,bl[S],scc,n,m; 5 void link(int a,int b,int w){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=w;} 6 void tarjan(int p){ 7 dfn[p]=low[p]=++tim;s[++tp]=p;in[p]=1; 8 for(int i=fir[p];i;i=l[i])if(!dfn[to[i]])tarjan(to[i]),low[p]=min(low[p],low[to[i]]); 9 else if(in[to[i]])low[p]=min(low[p],dfn[to[i]]); 10 if(dfn[p]==low[p]){++scc;while(in[p])in[s[tp]]=0,bl[s[tp]]=scc,tp--;} 11 } 12 int Fir[S],L[S],To[S],q[S],Ec=1,al[S],d[S];long long ans,V[S]; 13 void Link(int a,int b,int w){ 14 L[++Ec]=Fir[a];Fir[a]=Ec;To[Ec]=b;V[Ec]=w; 15 L[++Ec]=Fir[b];Fir[b]=Ec;To[Ec]=a;V[Ec]=123456789012345; 16 } 17 bool SPFA(){ 18 for(int i=1;i<=scc;++i)d[i]=123456789;d[bl[1]]=0;q[1]=bl[1]; 19 for(int h=1,t=1;h<=t;++h)for(int i=Fir[q[h]];i;i=L[i])if(V[i]&&d[q[h]]+1<d[To[i]]) 20 d[q[++t]=To[i]]=d[q[h]]+1; 21 return d[bl[n]]!=123456789; 22 } 23 long long dinic(int p,long long flow){//cout<<p<<' '<<flow<<endl; 24 long long r=flow; 25 if(p==bl[n])return flow; 26 for(int i=Fir[p];i&&r;i=L[i])if(d[To[i]]==d[p]+1&&V[i]){ 27 long long x=dinic(To[i],min(r,V[i])); 28 if(!x)d[To[i]]=-1; 29 V[i]-=x;V[i^1]+=x;r-=x; 30 }return flow-r; 31 } 32 int main(){//freopen("3.in","r",stdin);freopen("3.out","w",stdout); 33 scanf("%d%d",&n,&m); 34 for(int i=1,a,b,w;i<=m;++i)scanf("%d%d%d",&a,&b,&w),link(a+1,b+1,w); 35 tarjan(1); 36 if(bl[1]==bl[n]||!bl[n])return puts("-1"),0; 37 for(int i=1;i<=n;++i)for(int j=fir[i];j;j=l[j])if(bl[i]&&bl[to[j]]&&bl[to[j]]!=bl[i])Link(bl[i],bl[to[j]],v[j]); 38 while(SPFA())ans+=dinic(bl[1],123456789012345); 39 printf("%lld\n",ans); 40 }
.