[考试反思]0110省选模拟4: 苟活
不行了,做不动题了,写博客吧。。。
啊。。。全天效率低下。。。状态很差。。。莫名累。。。
而且还赶上数据结构专题测试(?)。能活着不滚到10名开外简直是莫大的荣幸2333
如何在什么也不会的情况下骗下T1?DC骗分录持续更新。(雾
骗分的前置知识:C++语言基础,STL,歪心思
看到T1,啊,二维几何,K-D啊。呃。。。直线距离而不是曼哈顿?好像很难写?算了算了估计也写不出来
$O(n^2)$爆扫还是会的。判断是否有包含关系。。。嗯。。。太暴力了
应该还是可以优化的。。?如果两个点横坐标的差大于半径了,那么直线距离就一定大于半径,不可能包含了。。。
于是按横坐标排一下序,这样也许能缩小一点枚举范围?
但是需要一个支持插入,删除的有序数据结构。。。STLset,但是比暴力还多一个$log$?
写写吧。于是你就有了比纯暴力的$60$分多$16$分。
但是
你觉得出题人会这么简单?他不想卡你?
他那么毒瘤肯定会卡你啊!他会想到你这个想法然后针对你构造数据啊!
那怎么办?
你跟我耍心机?那我只好反击了?
你觉得我会按照横坐标排序?不!我就要按着纵坐标排序!
你要卡我?那你还卡不卡那多数按照横坐标排序的人?
然后做好心理准备拿60分就可以走了,写个全场最短的代码扬长而去
然后你就AC了2333!
靠着这种乱七八糟毫无依据的方法多拿了24分。我也就会干这种事了。
揣测出题人意图?反向心理???
然而现在要揭示我有多智障了。
T1AC,T2T3全爆零,这是个啥啊????
首先看一下我T2的根号筛质因子
看起来人畜无害?对iv进行了质因数分解。
但是为什么mul%p==0,为什么是mul啊?
啊你问我?我不知道啊!我是智障吧。
我是怎么过样例的????还过了手模的一个大点的点???
然后就这么爆零了。
T3依旧弱智。。。我把题当成单向边的做了不知道多久
然后冲着我的网络流看了半天我板子怎么炸了
难得没炸一次啊啊啊又把分送了
我又是怎么过样例的啊!!!
如果这两个弱智分拿到了就180了。。。也是个rk2啊。。。
然而没有梦想rk8得了。。。
弱智在数据结构专题混个rk8也不容易,就这样吧。。。
T1:点点的圈圈
题目大意:给出若干可包含但不相交的圆,选中一些互不包含的圆最大化总价值。$n \le 10^5,w_i \le 1000,x_i,y_i,r_i \le 10^8$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 100005 4 struct cir{ 5 int x,y,r,w,o; 6 friend bool operator<(cir a,cir b){return a.y!=b.y?a.y<b.y:a.o<b.o;} 7 }C[S]; 8 bool cmp(cir a,cir b){return a.r<b.r;} 9 int n,f[S],del[S],dp[S],cc[S],q[S],qt; 10 long long x2(int a){return 1ll*a*a;} 11 set<cir>s; 12 int main(){//freopen("1.in","r",stdin); 13 scanf("%d",&n); 14 for(int i=1;i<=n;++i)scanf("%d%d%d%d",&C[i].x,&C[i].y,&C[i].r,&C[i].w); 15 sort(C+1,C+1+n,cmp); 16 for(int i=1;i<=n;++i){ 17 C[i].o=i; cir R=C[i],t; R.y-=C[i].r; 18 auto p=s.lower_bound(R);int tp=0; 19 while(p!=s.end()&&(*p).y<=C[i].y+C[i].r){ 20 t=*p;p++; 21 if(x2(R.x-t.x)+x2(C[i].y-t.y)<=x2(R.r))f[t.o]=i,del[++tp]=t.o; 22 } 23 for(int i=1;i<=tp;++i)s.erase(C[del[i]]); 24 s.insert(C[i]); 25 } 26 for(int i=1;i<=n;++i)cc[f[i]]++; 27 for(int i=1;i<=n;++i)if(!cc[i])q[++qt]=i; 28 for(int h=1;h<=qt;++h){ 29 dp[q[h]]=max(dp[q[h]],C[q[h]].w); 30 if(q[h])dp[f[q[h]]]+=dp[q[h]]; cc[f[q[h]]]--; 31 if(!cc[f[q[h]]])q[++qt]=f[q[h]]; 32 }cout<<dp[0]; 33 }
还是要运用那种「相含不相交就想父子关系」的思想建树。难点在建树。KD肯定是可以的但是不够优秀(虽然跑的更快)
正解是$O(nlogn)$的。运用了扫描线的思想,将圆的最左右两端坐标离散化后扫,现在只有两种事件。
1,插入一个圆。
如果你扫到某个圆的左端点,那么它就该出现了。
插入的时候可以顺便找一下包含它的最小圆。你可以找到纵坐标离它最近的那一个。
但是它可能是你的兄弟。。。怎么办?
于是把圆拆成上下两半加以区分。查纵坐标更大的点,如果查到下半圆那它就是你的兄弟,你和它一个爹。否则上半圆那就是你爹了。
打上一个标记,在这个圆到达右端点时删除。
2,删除一个圆。
也就是插入,删除,查前驱后继,STLset没问题了。
重载小于号。开一个全局变量表示当前扫描线所在的横坐标,计算半圆对应的纵坐标。
因为圆不相交,所以上下半圆之间的纵坐标虽然在变化,但是它们的相对大小关系是不变的,所以set不会出锅。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 200005 4 int X,n,f[S],dp[S],fir[S],l[S],to[S],r[S],rc,I=1,ec=1;vector<int>v[S]; 5 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 6 struct P{int x,y,r,w;friend bool operator<(P a,P b){return a.x-a.r<b.x-b.r;}}p[200005]; 7 void dfs(int x){for(int i=fir[x];i;i=l[i])dfs(to[i]),dp[x]+=dp[to[i]];dp[x]=max(dp[x],p[x].w);} 8 long long x2(int p){return 1ll*p*p;} 9 struct Q{ 10 int i,k; 11 double Y(int x){return p[i].y+k*sqrt(x2(p[i].r)-x2(p[i].x-x))+k*0.001;} 12 friend bool operator<(Q x,Q y){return x.Y(X)<y.Y(X);} 13 }R;set<Q>s; 14 int main(){ 15 cin>>n;for(int i=1;i<=n;++i)scanf("%d%d%d%d",&p[i].x,&p[i].y,&p[i].r,&p[i].w); 16 sort(p+1,p+1+n); 17 for(int i=1;i<=n;++i)r[++rc]=p[i].x-p[i].r,r[++rc]=p[i].x+p[i].r; 18 sort(r+1,r+1+rc);rc=unique(r+1,r+1+rc)-r-1; 19 for(int _=1;X=r[_],_<=rc;++_){ 20 for(int i=0;i<v[_].size();++i)s.erase((Q){v[_][i],-1}),s.erase((Q){v[_][i],1}); 21 while(I<=n&&p[I].x-p[I].r==X){ 22 auto it=s.lower_bound((Q){I,-1}); 23 if(it!=s.begin())it--,f[I]=(*it).k==1?f[(*it).i]:(*it).i; 24 v[lower_bound(r+1,r+1+rc,p[I].x+p[I].r)-r].push_back(I); 25 s.insert((Q){I,1});s.insert((Q){I,-1});I++; 26 } 27 }for(int i=1;i<=n;++i)link(f[i],i);dfs(0);cout<<dp[0]; 28 }
T2:点点的计算
题目大意:$T(i,1)=i,T(i,j)=\frac{1}{\frac{1}{T(i,j-1)}-\frac{1}{T(i-1,j-1)}}$。求$lcm(T(n,i))\ (1 \le i \le k)$。$70\%$的数据强制在线。
$T \le 2\times 10^5,n,k\le 10^5$
打表发现$T(i,j)=i\times C_{i-1}^{j-1}$。然而没有什么帮助。
大神们都看出了结论:$ans=lcm(n-k+1,...,n)$。
可以证明,先证明$lcm(T(i,j),T(i,j-1))=lcm(T(i-1,j-1),T(i,j))$。证明过程中要写一大堆分数懒得再写一遍$LATEX$了。
把$T(i,j)$拆开,发现式子只与$T(i-1,j-1)$与$T(i,j-1)$有关。换个变量表示要证明的就变成了$lcm(a,b)=lcm(a,\frac{ab}{(a-b)})$
化$lcm$为$gcd$,再把$g=gcd(a,b),a=Ag,b=Bg$代替已有变量能得到只需要证$Ag=(A-B)gcd(\frac{ABg}{A-B},Ag)$
把右端的$A-B$乘进$gcd$里面,问题在于证明$gcd(AgB,Ag(A-B))=Ag$即$gcd(B,A-B)=1$
而因为$A,B$是由$a,b$提公因子得到,所以它们已经互质。$A \perp B \rightarrow (A-B) \perp B$
所以最开始的那个式子得证。然后就把$j$这一列的问题推到了$j-1$列。同理一直推到第$1$列,而$T(i,1)=i$
所以得证。
问题变为维护$lcm(a...b)$。连续自然数的最小公倍数。
每个因子$p^k$只有在第一次出现的时候贡献答案,考虑每加入一个数的贡献。
对于$b=1$时显然。开线段树维护区间积。考虑当$b$增加时线段树的变化。
其实只有$b$的因子发生了变化,它们的第一次出现位置发生了改变。原来在$b-p^k$,现在在$b$。
于是在$b-p^k$去掉贡献,在$b$作出贡献就行了。
离线只要排序,一边处理询问一边更新线段树。
在线的话把线段树可持久化就行。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define mod 1000000007 4 #define S 10000005 5 int pow(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;} 6 int iv[S],c[S],d[S],w[S],rt[S],pc,lc[S],rc[S],la; 7 void build(int p,int cl=1,int cr=100000){ 8 w[p]=1;if(cl==cr)return; 9 lc[p]=p<<1;rc[p]=p<<1|1; 10 build(p<<1,cl,cl+cr>>1); 11 build(p<<1|1,(cl+cr>>1)+1,cr); 12 } 13 void modify(int cpy,int &p,int ps,int v,int cl=1,int cr=100000){ 14 w[p=++pc]=1ll*w[cpy]*v%mod;lc[p]=lc[cpy];rc[p]=rc[cpy]; 15 if(cl==cr)return; 16 if(ps>cl+cr>>1)modify(rc[cpy],rc[p],ps,v,(cl+cr>>1)+1,cr); 17 else modify(lc[cpy],lc[p],ps,v,cl,cl+cr>>1); 18 } 19 int ask(int l,int r,int p,int cl=1,int cr=100000){ 20 if(l<=cl&&cr<=r)return w[p]; 21 return 1ll*(l<=cl+cr>>1?ask(l,r,lc[p],cl,cl+cr>>1):1)*(r>cl+cr>>1?ask(l,r,rc[p],(cl+cr>>1)+1,cr):1)%mod; 22 } 23 int main(){//freopen("1.in","r",stdin); 24 int n,k,q,a,b,mo;cin>>q>>n>>k>>a>>b>>mo; 25 for(int i=1;i<q;++i)scanf("%d",&c[i]); 26 for(int i=1;i<q;++i)scanf("%d",&d[i]); 27 for(int i=1;i<100001;++i)iv[i]=pow(i,mod-2); 28 rt[1]=1;build(1);pc=400000; 29 for(int i=2;i<=100000;++i){ 30 int I=i,ls=i-1; 31 for(int j=2;j*j<=I;++j)if(I%j==0){ 32 int tms=0,al=1; 33 while(I%j==0)I/=j,tms++,al*=j; 34 if(al==i)al/=j; 35 while(al!=1)modify(rt[ls],rt[i],i-al,iv[j]),ls=i,al/=j; 36 } 37 if(I!=1&&i!=I)modify(rt[ls],rt[i],i-I,iv[I]),ls=i; 38 modify(rt[ls],rt[i],i,i); 39 } 40 for(int i=1;i<=q;++i)printf("%d\n",la=ask(n-k+1,n,rt[n])),n=(1ll*a*la+c[i])%mo+1,k=(1ll*b*la+d[i])%n+1; 41 }
T3:点点的最大流
题目大意:仙人掌(且每个点最多在一个环内),改边权,指定源汇查询最大流。$n \le 10^5,m,q \le 2 \times 10^5$
对于$30\%$的数据是一棵树。
最大流是最小割,树的话直接查路径最小边权。树剖就行。
仙人掌就是多了简单环,要么断缩点后图的边,要么断某个环上的两条边。
缩点+线段树+树剖。断环成链,线段树维护。
看得不是很明白。大概意思是点权是每个点和新树父点的顶端的最小割值查询最小值。
重链树剖维护,轻边线段树查询,复杂度两个log。
代码十分恶心?先撂了。
upd:
然而$G_keng$大神提供了好理解的多的方法。
考虑仙人掌上的最大流,只要是源到汇之间的所有环,环上的最小边一定会满流,不论方向之类的。。。
考虑树上的最大流,就是两点之间路径的边权最小值。
而既然我们已经知道仙人掌上所有环的最小边只要环上有一条边被经过就一定会产生贡献,所以我们特殊考虑它。
把它断掉,那么就不存在环了,剩下的就是一棵树了,直接路径上最小值即可。
而像我刚才所说的,因为只要经过某个环上的任意一条边,那么这个环的最小边也一定就满流了。
所以其实就相当于在断掉那条边之后,这个环上所有的其它边的权值都增加了这条边的权值,这样就是等价的了。
于是我们貌似可以跑一个$tarjan$边双(不用点双是因为这道题每个点最多属于一个环)然后在剩下的树上随便什么数据结构求最小值就好。
但是问题在于修改边权,并不能直接修改。
因为边权修改之后,它所在的环上的最小边可能会发生变化,如果最小边发生了变化,那么就需要去掉原来加的边权,然后把断的边加回来,新的最小边断开,在让新的最小边产生贡献。
保证图的形态是一棵树的前提下,连边,断边,链加,链$min$
这就很$LCT$了。
然而主函数里还是需要不少的分类讨论,挺麻烦的一道题。但是都挺好理解的。
$G_keng$大神的思路也不是很容易想到,真巨啊。。。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 600005 4 #define inf 2000000007 5 #define lc c[0][p] 6 #define rc c[1][p] 7 #define f1 first 8 #define s2 second 9 #define int long long 10 int c[2][S],f[S],lz[S],add[S],a[S],b[S],s[S],v[S],mn[S]; 11 bool nr(int p){return c[0][f[p]]==p||c[1][f[p]]==p;} 12 void rev(int p){swap(lc,rc);lz[p]^=1;} 13 void up(int p){mn[p]=min(v[p],min(mn[lc],mn[rc]));} 14 void down(int p){ 15 if(lz[p])rev(lc),rev(rc),lz[p]=0; 16 if(add[p])add[lc]+=add[p],add[rc]+=add[p],v[lc]+=add[p],v[rc]+=add[p],mn[lc]+=add[p],mn[rc]+=add[p],add[p]=0; 17 } 18 void spin(int p){ 19 int F=f[p],G=f[F],D=c[1][F]==p,B=c[!D][p]; 20 if(nr(F))c[c[1][G]==F][G]=p; c[!D][p]=F; c[D][F]=B; 21 if(B)f[B]=F; f[f[F]=p]=G; up(F); 22 } 23 void splay(int p){ 24 int y=p,tp=1,F;s[1]=p; 25 while(nr(y))s[++tp]=y=f[y]; while(tp)down(s[tp--]); 26 for(;F=f[p],nr(p);){ 27 if(nr(F))spin(c[1][f[F]]==F^c[1][F]==p?p:F); 28 spin(p); 29 }up(p); 30 } 31 void access(int r){for(int p=r,y=0;p;p=f[y=p])splay(p),rc=y,up(p);splay(r);} 32 void make(int p){access(p);rev(p);} 33 void link(int x,int y){make(x);f[x]=y;} 34 void split(int x,int y){make(x);access(y);} 35 void cut(int x,int y){split(x,y);c[0][y]=f[x]=0;up(y);} 36 int fir[S],l[S],to[S],ec=1,dfn[S],low[S],T,ins[S],tp,scc,bl[S]; set<pair<int,int>>E[S]; 37 void Link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 38 void con(int a,int b){Link(a,b);Link(b,a);} 39 void tarjan(int p,int fe){ 40 dfn[p]=low[p]=++T;s[++tp]=p;ins[p]=1; 41 for(int i=fir[p];i;i=l[i])if(i!=(fe^1)) 42 if(ins[to[i]])low[p]=min(low[p],dfn[to[i]]); 43 else if(!dfn[to[i]])tarjan(to[i],i),low[p]=min(low[p],low[to[i]]); 44 if(dfn[p]==low[p]){ 45 ++scc; 46 while(ins[p])E[scc].insert(make_pair(v[s[tp]],s[tp])),bl[s[tp]]=scc,ins[s[tp--]]=0; 47 } 48 } 49 void Add(int p,int w){v[p]+=w;add[p]+=w;} 50 main(){ 51 int n,m,r;cin>>n>>m; 52 for(int i=0;i<=n;++i)v[i]=mn[i]=inf; 53 for(int i=n+1;i<=n+m;++i)scanf("%lld%lld%lld",&a[i],&b[i],&v[i]),con(a[i],i),con(i,b[i]); 54 tarjan(1,0); 55 for(int i=1;i<=scc;++i){ 56 if(E[i].size()==1){if((r=(*E[i].begin()).s2)>n)link(r,a[r]),link(r,b[r]);} 57 else{ 58 auto it=++E[i].begin(); 59 while(it!=E[i].end()){if((r=(*it).s2)>n)link(r,a[r]),link(r,b[r]);it++;} 60 r=(*E[i].begin()).s2;split(a[r],b[r]);Add(b[r],(*E[i].begin()).f1); 61 } 62 } 63 int q,opt,x,y;cin>>q;while(q-->0){ 64 scanf("%lld%lld%lld",&opt,&x,&y); 65 if(opt){x+=n; 66 #define e E[bl[x]] 67 if(e.size()==1)split(x,x),v[x]=y; 68 else if((*e.begin()).s2==x){ 69 split(a[x],b[x]);Add(b[x],-(*e.begin()).f1); 70 splay(x);v[x]=y;splay(b[x]); 71 e.erase(e.begin());e.insert(make_pair(y,x)); 72 if((*e.begin()).s2==x)Add(b[x],y); 73 else r=(*e.begin()).s2,cut(r,a[r]),cut(r,b[r]),link(x,a[x]),link(x,b[x]),split(a[r],b[r]),Add(b[r],(*e.begin()).f1); 74 }else{ 75 r=(*e.begin()).s2;split(a[r],b[r]); Add(b[r],-v[r]); splay(x); 76 e.erase(make_pair(v[x],x));e.insert(make_pair(v[x]=y,x)); splay(b[r]); 77 if((*e.begin()).s2==r)Add(b[r],v[r]),splay(x); 78 else cut(x,a[x]),cut(x,b[x]),link(r,a[r]),link(r,b[r]),split(a[x],b[x]),Add(b[x],y); 79 } 80 }else split(x,y),printf("%lld\n",mn[y]); 81 } 82 }
.