2017-04-01周六(165)
▲22:28:43 BZOJ1497 最小割模型 注意边数*2!!!
2017-04-10周一(166)
学考考完我又是一条好汉!!十月再战!!
▲21:30:56 WC2007 剪刀石头布 费用流建模/正难则反 题目求最多有多少个"石头剪刀布"的情况.
对于(a,b,c),满足的情况是三个点的出度都是1. 即a->b,b->c,c->a
我们考虑不满足情况的a,b,c的连边方式: 比如 a->b,b->c,a->c或者 a->b,b->a,c->a,这类方案有很多,但是有一个共同点,(a,b,c)的度数都是0,1,2,但是顺序可以改变.这是最关键的地方.
那么我们就可以把这个组合抽象出来,假如点x的出度(赢的场次)为w,那么含x的特定不符合的组合有w*(w-1)/2种,那么总可行组合数=所有三个点的组合-∑w*(w-1)/2
这样就把问题转化成 求cost=Min{∑w*(w-1)/2}
已知∑w=n*(n-1)/2 ,可以通过这个条件想到最大流最小费用,建模每个st-en的流表示一场比赛,而费用就表示这场比赛对于cost的影响.
对于一个点x,当w[x]+1时,它对结果的影响会加上w[x].因此x到en的边不能用单一的费用,要连n-1条边,费用分别是0~n-2,w[x]对cost的影响为0+1+2+3+..+(w[x]-1) =w[x]*(w[x]-1)
此题还有一个bug!!就是要输出方案!!!
不能用贪心或者随意构造方法来得到方案,这样可能不合法.正确的做法是根据残余网络,确定方案选择了哪些路线,从而得到解.
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int M=50205; const int oo=1e9+6; int n,ec=0,tot=0,st,en,head[M],nxt[M],cap[M],cost[M],to[M],dis[M]; int Q[M],pre[M],cnt[M],los[M],res[105][105],ans; int A[M],num[M]; bool in[M]; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void sc(int a){ if(a)putchar('1'); else putchar('0'); // putchar(' '); } void ins(int a,int b,int f,int co){ to[ec]=b;cap[ec]=f;cost[ec]=co;nxt[ec]=head[a];head[a]=ec++; to[ec]=a;cap[ec]=0;cost[ec]=-co;nxt[ec]=head[b];head[b]=ec++; } bool SPFA(){ int x,y,i,l=0,r=0,f=oo; for(i=1;i<=tot;i++)in[i]=0,dis[i]=oo; dis[st]=0; Q[r++]=st; while(l<r){ x=Q[l++],in[x]=0; for(i=head[x];~i;i=nxt[i]){ int y=to[i]; if(cap[i]&&dis[y]>dis[x]+cost[i]){ dis[y]=dis[x]+cost[i]; if(!in[y])Q[r++]=y,in[y]=true; pre[y]=i; } } } if(dis[en]>=oo)return false; // cnt[to[pre[en]^1]]++;//表示每个人赢的次数 for(x=en;x!=st;x=to[pre[x]^1])f=min(f,cap[pre[x]]); for(x=en;x!=st;x=to[y^1]){ y=pre[x]; cap[y]-=f,cap[y^1]+=f; } // printf("%d %d ",f,dis[en]); ans-=dis[en]*f; return true; } void solve(){ int i,j,k,a,b; rd(n);tot=n+2,st=n+1,en=n+2; //点数 n+2+n*(n-1)/2 102+50*100 =5102 //边数 [n*(n-1)/2 + n*(n-1)/2*2 + n*(n-1) ] *2 // n^2 1+2+2 ->5 5w //st=n+1,en=n+2 contest numbered from n+3 //n*2+tot; ans=n*(n-1)*(n-2)/6;//所有的方案数 for(i=n*n+tot;~i;i--)head[i]=-1; for(i=1;i<=n;i++){ for(j=1;j<=n;j++){ // rd(a); rd(res[i][j]); if(i<=j)continue;//i>j a=res[i][j]; ++tot; num[tot]=i+j; ins(st,tot,1,0);//容量为1,费用为0 if(a==2){//还没比赛 ins(tot,i,1,0); ins(tot,j,1,0); } else if(a==1)ins(tot,i,1,0);//i赢了 else ins(tot,j,1,0);//j赢了 //*/ }//[0,n-2]一共n-1条边 for(j=n-2;~j;j--)ins(i,en,1,j); } while(SPFA()); for(i=n+3;i<=tot;i++){ for(j=head[i];~j;j=nxt[j]){ if(to[j]==st||cap[j])continue; int x=to[j],y=num[i]-to[j]; res[x][y]=1,res[y][x]=0; } } printf("%d ",ans); for(i=1;i<=n;i++){ for(j=1;j<=n;j++){ sc(res[i][j]); putchar(" "[j==n]); } } } int main(){ // freopen("da.in","r",stdin); solve(); return 0; }
2017-04-11周二(169)
▲16:33:48 BZOJ3996 线性代数 最大流最小割 两点之间建模 先把式子化简得到一个关于Ai的式子,然后考虑Ai的两种取值,以及Ai和Aj的关系进行建模,然后得到最小割即可.注意边数!!!
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> using namespace std;//edge = n+n+n^2 4*n+n^2 =252000 const int N=555,M=(N*N+2500)*2; const int oo=2e9; int ec=0,n,C[N],st,en,B[N][N],tot=0,head[N],to[M],nxt[M],cap[M],dis[N],Q[M]; bool BFS(){ int L=0,R=0,i,j,k; for(i=1;i<=en;i++){ dis[i]=-1; } dis[st]=0; Q[R++]=st; while(L<R){ int x=Q[L++],y; for(i=head[x];~i;i=nxt[i]){ y=to[i]; if(cap[i]&&dis[y]==-1){ dis[y]=dis[x]+1; Q[R++]=y; } } } return dis[en]>=0; } int dfs(int x,int f){ if(x==en)return f; int k,res=0,y,i; for(i=head[x];~i;i=nxt[i]){ y=to[i]; if(!cap[i]||dis[y]!=dis[x]+1)continue; k=dfs(y,min(cap[i],f-res)); if(k){ res+=k; cap[i]-=k,cap[i^1]+=k; if(res==f)return res; } } return res; } void ins(int a,int b,int c){ to[ec]=b;nxt[ec]=head[a];cap[ec]=c;head[a]=ec++; to[ec]=a;nxt[ec]=head[b];cap[ec]=0;head[b]=ec++; } inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void solve(){ int i,k,j,c,ans=0; rd(n); st=n+1,en=n+2; for(i=1;i<=en;i++)head[i]=-1; for(i=1;i<=n;i++){ for(j=1;j<=n;j++)rd(B[i][j]); } for(i=1;i<=n;i++){ for(j=1;j<i;j++){ k=B[i][j]+B[j][i]; C[i]+=k,C[j]+=k; ins(i,j,k); ins(j,i,k); ans+=(k<<1); } ans+=B[i][i]*2; }//最后答案/4 for(i=1;i<=n;i++){ rd(c);c<<=1; ins(i,en,c); ins(st,i,C[i]+B[i][i]*2); } // printf("%d ",ans); while(BFS())ans-=dfs(st,oo); // printf("%d ",ans); printf("%d ",ans>>1); } int main(){ // freopen("da.in","r",stdin); solve(); return 0; }
▲18:11:49 BZOJ3931 Dijkstra+最大流模板题
▲21:54:36 BZOJ1797 最大流+Tarjan 最小割的唯一性判定.判断一条边是否一定/可能在最小割中:先跑一遍最大流,对残余网络强连通缩点,对于边(a,b),假如不是满流,肯定不能在最下个上,若id[a]!=id[b],那么它可能在最小割上,如果id[a]=id[st],id[b]=id[en],一定在最小割中.
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> using namespace std;//先求出maxflow 然后再强连通缩点 const int N=4005,M=120005,oo=1e9; int head[N],ec=0,n,m,st,en,cap[M],to[M],nxt[M],Q[N],dis[N]; int stk[N],in[N],dfn[N],low[N],clo=0,scc=0,id[N],top=0; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void ins(int a,int b,int c){ to[ec]=b;nxt[ec]=head[a];cap[ec]=c;head[a]=ec++; to[ec]=a;nxt[ec]=head[b];cap[ec]=0;head[b]=ec++; } bool BFS(){ int L=0,R=0,i,j,k; for(i=1;i<=n;i++)dis[i]=-1; dis[st]=0; Q[R++]=st; while(L<R){ int x=Q[L++],y; for(i=head[x];~i;i=nxt[i]){ y=to[i]; if(cap[i]&&dis[y]==-1){ dis[y]=dis[x]+1; Q[R++]=y; } } } return dis[en]>=0; } int dfs(int x,int f){ if(x==en)return f; int k,res=0,y,i; for(i=head[x];~i;i=nxt[i]){ y=to[i]; if(!cap[i]||dis[y]!=dis[x]+1)continue; k=dfs(y,min(cap[i],f-res)); if(k){ res+=k; cap[i]-=k,cap[i^1]+=k; if(res==f)return res; } } return res; } void tarjan(int x){ dfn[x]=low[x]=++clo; stk[++top]=x; in[x]=1; int y; for(int i=head[x];~i;i=nxt[i]){ if(!cap[i])continue; y=to[i]; if(!dfn[y])tarjan(y),low[x]=min(low[x],low[y]); else if(in[y])low[x]=min(low[x],dfn[y]); } if(low[x]==dfn[x]){ id[x]=++scc; in[x]=0; while(stk[top]!=x){ y=stk[top]; id[y]=scc,in[y]=0; top--; } top--; } } void solve(){ int i,j,k,a,b,c; rd(n);rd(m);rd(st);rd(en); for(i=1;i<=n;i++)head[i]=-1; for(i=1;i<=m;i++){ rd(a),rd(b),rd(c); ins(a,b,c); } while(BFS())a=dfs(st,oo); for(i=1;i<=n;i++){ if(!dfn[i])tarjan(i); } for(i=0;i<ec;i+=2){ a=to[i^1],b=to[i]; if(cap[i]||id[a]==id[b]){ putchar('0'),putchar(' '),putchar('0'); } else { putchar('1');putchar(' '); if(id[a]==id[st]&&id[b]==id[en])putchar('1'); else putchar('0'); } putchar(' '); } } int main(){ // freopen("da.in","r",stdin); solve(); return 0; }
2017-04-12周三(173)
▲08:14:50 BZOJ2127 happiness 最小割 两点之间建模 dinic的优化
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=105,M=120505,oo=1e9; int n,m,id[N][N],tot=2,st=1,en=2; int A[N][N],B[N][N],upA[N][N],upB[N][N],leA[N][N],leB[N][N]; int head[N*N],hd[N*N],to[M],nxt[M],cap[M],Q[M],dis[N*N],ec=2; bool vis[N*N]; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void ins(int a,int b,int c){ to[ec]=b;nxt[ec]=head[a];cap[ec]=c;head[a]=ec++; to[ec]=a;nxt[ec]=head[b];cap[ec]=0;head[b]=ec++; } bool BFS(){ int L=0,R=0,i,j,k; for(i=1;i<=tot;i++)dis[i]=-1; dis[st]=0; Q[R++]=st; while(L<R){ int x=Q[L++],y; for(i=head[x];i;i=nxt[i]){ y=to[i]; if(cap[i]&&dis[y]==-1){ dis[y]=dis[x]+1; Q[R++]=y; } } } return dis[en]>=0; } int dfs(int x,int f){ // vis[x]=1; if(x==en)return f; int k,y,res=0; for(int i=head[x];i;i=nxt[i]){ y=to[i]; if(!cap[i]||dis[y]!=dis[x]+1)continue; k=dfs(y,min(cap[i],f-res)); if(k){ cap[i]-=k,cap[i^1]+=k; res+=k; if(res==f)return res; } } if(!res)dis[x]=-1; return res; } void RD(int s[N][N],int a,int b){ for(int i=1;i<=a;i++) for(int j=1;j<=b;j++)rd(s[i][j]); } void solve(){ int i,j,k,a,b,ans=0; rd(n);rd(m); RD(A,n,m),RD(B,n,m); RD(upA,n-1,m),RD(upB,n-1,m); RD(leA,n,m-1),RD(leB,n,m-1); for(i=1;i<=n;i++){ for(j=1;j<=m;j++){ id[i][j]=++tot; ans+=A[i][j]+B[i][j]; a=A[i][j]<<1,b=B[i][j]<<1; a+=upA[i-1][j]+upA[i][j],b+=upB[i-1][j]+upB[i][j]; a+=leA[i][j-1]+leA[i][j],b+=leB[i][j-1]+leB[i][j]; ins(st,tot,a); ins(tot,en,b); } } for(i=1;i<n;i++){//考虑上下好朋友 for(j=1;j<=m;j++){ ans+=upA[i][j]+upB[i][j]; ins(id[i][j],id[i+1][j],upA[i][j]+upB[i][j]); ins(id[i+1][j],id[i][j],upA[i][j]+upB[i][j]); } } for(i=1;i<=n;i++){//考虑上下好朋友 for(j=1;j<m;j++){ ans+=leA[i][j]+leB[i][j]; ins(id[i][j],id[i][j+1],leA[i][j]+leB[i][j]); ins(id[i][j+1],id[i][j],leA[i][j]+leB[i][j]); } } ans<<=1; while(BFS())ans-=dfs(st,oo); ans>>=1; printf("%d ",ans); } int main(){ // freopen("da.in","r",stdin); solve(); return 0; }
▲10:10:47 BZOJ3894 文理分科 最小割
▲14:49:58 BZOJ1927 星际竞速 费用流 要求每个点恰好经过一次->这个条件可以看成 到达每个点一次,从每个点出发一次,这个条件可以通过拆点连边流量为1来解决.现在问最短时间,我们可以把最终路线的每一段都拆开考虑->要么是跳跃,要么是一个点走单向边到另一个点.注意第一种方式的起点根本不重要,所以从st->x'连边,费用为定位时间,走这条边说明用了跳跃;从u->v'连边,表示走图中的边,费用为边权值,再让st和u连边费用为0.最后让x'与en连边,只有到达x'才说明到达过这个点.跑最小费用最大流即可.
▲19:35:06 BZOJ3876 支线剧情 有下界的费用流
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> using namespace std; const int oo=1e9,N=305,M=25005; int ans=0,n,m,st,en,pre[N],to[M],head[N],nxt[M],ec=2,cap[M],cost[M],dis[M],Q[M],in[M],deg[N]; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void ins(int a,int b,int c,int d){ to[ec]=b,nxt[ec]=head[a],cap[ec]=c,cost[ec]=d;head[a]=ec++; // if(cap[ec-1]<0)printf("hey %d ",cap[ec-1]); to[ec]=a,nxt[ec]=head[b],cap[ec]=0,cost[ec]=-d,head[b]=ec++; } bool SPFA(){ int f=oo,i,x,y,L=0,R=0; for(i=1;i<=en;i++)dis[i]=oo; dis[st]=0,Q[R++]=st; while(L<R){ x=Q[L++];in[x]=0; for(i=head[x];i;i=nxt[i]){ y=to[i]; // printf("%d %d ",x,y); if(cap[i]&&dis[y]>dis[x]+cost[i]){ dis[y]=dis[x]+cost[i]; pre[y]=i; if(!in[y])in[y]=1,Q[R++]=y; } } } if(dis[en]>=oo)return false; // for(x=en;x!=st;x=to[pre[x]^1])f=min(f,cap[pre[x]]); for(x=en;x!=st;x=to[pre[x]^1]){ cap[pre[x]]--,cap[pre[x]^1]++; // printf("%d ",x); } ans+=dis[en]; // printf(" val %d ",dis[en]); // puts("-----------"); return true; } void solve(){ int i,j,k,a,b,c; rd(n); st=n+1,en=n+2; for(i=1;i<=n;i++){ rd(a); if(a)ins(i,en,a,0);//a是出度 ins(i,1,oo,0); while(a--){ rd(b),rd(c); ins(i,b,oo,c); ins(st,b,1,c); } } while(SPFA()); printf("%d ",ans); } int main(){ // freopen("da.in","r",stdin); solve(); return 0; }
2017-04-13周四(176)
▲10:30:37 BZOJ2756 奇怪的游戏 终态分析->分类讨论/二分/最大流
①确定了最后统一的数字,就可以确定操作的次数.
②对于n*m为偶数的情况,此时解满足二分的性质,即假设x为合法的统一数字,则任何y>=x都是可行的,所以可以通过二分枚举得到最小满足的x.判定一个解是否可行->进行网络流建模
由于网格图是二分图,所以模型很简单,白色点连向st,流量为需要修改的大小,黑色的连向en,同理.黑白点相邻的连接即可.
③对于n,m为奇数的情况:可以通过一个式子转化,x*cnt黑-sum黑色点=x*cnt白-sum白色点 ->由于nm为奇数,cnt黑!=cnt白,因此x可解,所以直接判断x即可.
启示->当某一个情况不好解决时,考虑是否能够利用信息直接解出来,把问题变为判定型.
▲16:25:41 SPOJ839 最大流 这道题好神!!
①题目是异或值,每一位之间的值是互不影响的,所以每一位分开考虑,分别求最小值.那现在只有0和1的区别了.
②最小割建模,建模-> 一条边(u,v),流量为1;假如点x权值已经确定,当前位是1,就和en连接 ,流量为oo,否则和st连接,流量oo.然后求最小割即可.
③对于第二问 有一个巧妙的建模方式-> 为了让点权和最小,就要让点尽可能在st集合,将点和st连一条流量为1的边,并且把原图的边的流量改为10000,这样可以保证在满足边权和最小的情况下,点权和最小.得到的最小割val,val/10000就是边权和,val%10000就是点数.
但是如果要求出每个点具体的权值,可以采用另一种贪心的方式:从en开始,遍历到的(没有经过割边的)每个点设为1.也就是找到离汇点最近的割,这样能让更少的点进入en集.
▲20:50:00 长郡中学省选模拟T2 DP+分类讨论+前缀/后缀最值
从最基本的DP入手,发现表达式是这样的 f[i]=Max{f[j]+(t[i]-t[j]-1)/step}
对于(a-b)/c 变量分离 a=a1*c+a2,b=b1*c+b2
(a-b)/c= (a1-b1)*c+a2-b2
根据a2和b2的大小关系得到两个值,所以每个点根据t[i]mod step分类,离散出大小关系 用树状数组维护前缀和后缀的最值即可.
注意有一个小bug!!这里的起点,也就是t[0]是st-1不是0!!!!!
2017-04-14 (177)
▲15:02:29 长郡中学省选模拟T1 最小割 把每个点的权值从w改为1000-w,这样就把问题转化为求权值和最小.考虑建模,有两个约束条件:
①一个炮台只能攻击一个目标
②两个炮台路线不相交.
而且题目中又给出了一个条件:保证一个炮台不会被另一个攻击,那么若两个炮台路线相交,只有可能是一个横着的和一个竖着的相交.
假如有一个向左的炮台(i,j),让它与st相连,再和(i,j-1),(i,j-1)再和(i,j-2)....形成一条链,(i,1)再和en相连.在没有其他炮台的影响下,这条路径的最小割就是答案.对于两个炮台可能相交的情况:在两个炮台对应的链的两个交点之间连边,这样保证不能同时选中某个部分的边.
2017-04-15 (178)
▲11:15:16 长郡中学省选模拟T3 莫比乌斯反演/积性函数求和/推推推
啊啊啊好神奇啊我居然做到了~~~(≧▽≦)/
本来只是想写个60分,结果想出了优化hhh
首先可以求出所有数对(i,j)的lcm之和设为X,这是一个经典问题.
X=∑d*F(A/d,B/d) 枚举数对的gcd为d,问题转化成求 i<=A/d,j<=B/d的互素数对的乘积和设为Y.这个也是一个经典问题.
Y=∑i^2*μ(i)*S(A/(d*i))*S(B/(d*i))
这个问题可以在O(n)时间内解决,后来一想,假如i,j的gcd=d含平方因子,那就不算入答案里即可.只要修改一下d的前缀和就可以在O(n)时间内求解原问题的答案了.
原题有优化多case的方法.
不妨枚举p=d*i.
问题转化成ans=∑S(A/p)*S(B/p)*f(p)*p
f[p]=∑μ(q)*q*μ(p/q)^2
假如能够求出f[p]就能够在O(sqrtn)内解决一个case.
假如p的某个素因子次数>=3,f[p]=0,这是显然的.
假如把p写成a^2*b的形式,再枚举b的组成,是可以卡过去的.
更优的解法:由于f[p]使一个积性函数,可以直接线性筛出f[p].
假如p的所有素因子次数都是1,那么f[p]=f[i]*f[pri[j]].否则分类考虑:①假如i已经包含两个pri[j]了或者f[i]=0,那么f[i*pri[j]]显然为0 .
②否则i中有一个pri[j],现在又来一个,相当于给a多了一个素因子,给b少了一个,f[i*pri[j]]=-f[i/pri[j]]*pri[j]
这样可以O(n)求出f,从而解决多case问题.
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> using namespace std; const int M=4e6+5,P=(1<<30),ful=P-1; int A,B,mu[M],sum[M],pri[300000],tot=0;//[0,7] 可用 bool mark[M]; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void print(int x){ if(!x)return ; print(x/10); putchar((x%10)^48); } void sc(int x){ if(x<0){x=-x;putchar('-');} print(x); if(!x)putchar('0'); putchar(' '); } void Add(int &x,int y){ x+=y; if(x<0)x+=P; else x&=ful; } void init(){//求出 mu,r,phi->sum int i,j,k; mu[1]=sum[1]=1; for(i=2;i<M;i++){ if(!mark[i]){ pri[++tot]=i; mu[i]=-1; sum[i]=1-i+P; } for(j=1;j<=tot&&pri[j]*i<M;j++){ mark[i*pri[j]]=1; if(i%pri[j]==0){ mu[i*pri[j]]=0; if(sum[i]==0||i/pri[j]%pri[j]==0)sum[i*pri[j]]=0; else sum[i*pri[j]]=1ll*(-sum[i/pri[j]]+P)*pri[j]&ful; break; } mu[i*pri[j]]=-mu[i]; sum[i*pri[j]]=1ll*sum[i]*sum[pri[j]]&ful; } } for(i=2;i<M;i++){ sum[i]=1ll*sum[i]*i&ful; sum[i]=(sum[i-1]+sum[i])&ful; } } int f(int a){ return (1ll*a*(a+1)>>1)&ful; } void solve(){ int ans=0,i,a,b,en; rd(A),rd(B); if(A>B)swap(A,B);//默认A较小 for(i=1;i<=A;i=en+1){ a=A/i,b=B/i; en=min(A/a,B/b); ans=(ans+((1ll*f(a)*f(b)&ful)*(sum[en]-sum[i-1]+P)&ful))&ful; } sc(ans); } int main(){ // freopen("lcm.in","r",stdin); // freopen("lcm.out","w",stdout); // printf("%d ",P); init(); int cas; rd(cas); while(cas--)solve(); return 0; }
2017-04-16(179)
▲14:17:14 BZOJ1449 最小费用流 对于比赛输赢的建模,如果输赢都考虑非常难搞,所以可以考虑先把答案都设为输,那么现在每场比赛只用考虑赢的人就可以啦.这样就方便很多.
▲14:38:05 模拟赛(by phillipsweng)T1 启发式合并+倍增/并查集+LCT
比赛时写的是第一种做法,启合+倍增.但是因为有一个小地方写错,狗带了...
因为这种做法是O(nlogn^2)的,所以要卡一波常数,因此在更新倍增数组的时候这样写了
for(i=1;i<S&&fa[x][i-1];i++)fa[x][i]=fa[fa[x][i-1]][i-1]
因为有可能原来的dis更长,导致fa[x][i-1]=0 但是fa[x][i]还有数字,但是更新就停止了,后面的没有清空,导致LCA求错...然后狗带.
后来发现其实这个优化没有什么卵用,没有优化的反而快点..
两种写法的具体思路是一样的:维护每个联通块的直径端点,并且能够迅速求出两点之间距离.
对于可以离线的情况:可以先把最后的树建立出来,这样就可以方便求出两点之间的距离,然后再启发式合并维护每个联通块的直径即可.
并查集+LCT的做法:用LCT维护加边操作和两点距离即可.一开始脑子短路了,考虑怎么记录每个联通块,因为LCT中联通块的根经常变,无法有一个确定的数字,所以用到并查集!!并查集只维护点的联通情况,保证一个联通块内的点都连向同一个点rt即可,用rt记录联通块的直径端点.剩下的就是LCT连边和求两点距离的功能了.注意:连边(x,y)的时候是先把x make_rt,再把x的fa设置为y,再access x.
树上加边,删边->LCT大法!!
#include<cstdio> #include<cstring> #include<iostream> using namespace std; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void print(int x){ if(!x)return ; print(x/10); putchar((x%10)^48); } void sc(int x){ if(x<0){x=-x;putchar('-');} print(x); if(!x)putchar('0'); putchar(' '); } const int M=3e5+15; int n,m,fa[M],tmp[M]; int type,dx[M],dy[M],sz[M],D[M],ch[M][2],rev[M],par[M]; int get(int a){ if(par[a]!=a)return par[a]=get(par[a]); return a; } void up(int x){ sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1; } void down(int x){ if(rev[x]){ swap(ch[x][0],ch[x][1]); rev[ch[x][0]]^=1,rev[ch[x][1]]^=1; rev[x]=0; } } bool chk(int x){ return ch[fa[x]][0]==x||ch[fa[x]][1]==x; } void rotate(int x){ int y=fa[x],z=fa[fa[x]],son; bool a=ch[y][1]==x,b=ch[z][1]==y; son=ch[x][a^1]; if(chk(y))ch[z][b]=x;fa[x]=z; ch[y][a]=son,fa[son]=y; ch[x][a^1]=y,fa[y]=x; up(y); } void splay(int x){ int y=x,z,tot=0; while(chk(y))tmp[++tot]=y,y=fa[y]; tmp[++tot]=y; while(tot)down(tmp[tot--]); while(chk(x)){ y=fa[x],z=fa[fa[x]]; if(chk(y)){ if(ch[y][0]==x^ch[z][0]==y)rotate(x); else rotate(y); } rotate(x); } up(x); } void access(int x){ for(int t=0;x;t=x,x=fa[x]){ splay(x),ch[x][1]=t,up(x); } } void make_rt(int a){ access(a),splay(a);rev[a]^=1; } void link(int a,int b){ make_rt(b);fa[b]=a;access(b); } int Dis(int a,int b){ if(a==b)return 0; make_rt(a);access(b);splay(b); return sz[b]-1; } void upd_D(int c,int v,int a,int b){ if(v>D[c])D[c]=v,dx[c]=a,dy[c]=b; } void upd(int a,int b){// 加边(a,b) int y,x,c,d; c=get(a),d=get(b); link(a,b);//把id设置为c x=dx[c],y=dy[c]; upd_D(c,D[d],dx[d],dy[d]); upd_D(c,Dis(x,dx[d]),x,dx[d]); upd_D(c,Dis(x,dy[d]),x,dy[d]); upd_D(c,Dis(y,dx[d]),y,dx[d]); upd_D(c,Dis(y,dy[d]),y,dy[d]); par[d]=c; } int qry(int a){ int b=get(a); return max(Dis(a,dx[b]),Dis(a,dy[b])); } void solve(){ int i,j,k,op,ans=0,a,b; for(i=1;i<=n;i++){ fa[i]=0,sz[i]=1; dx[i]=i,dy[i]=i; par[i]=i; } while(m--){ rd(op); if(op==1){ rd(a),rd(b); if(type)a^=ans,b^=ans; upd(a,b); } else{ rd(a); if(type)a^=ans; ans=qry(a); sc(ans); } } } int main(){ // freopen("hike.in","r",stdin); // freopen("hike.out","w",stdout); rd(type); rd(n);rd(m); solve(); return 0; }
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<ctime> #include<cstdlib> #include<cmath> #include<string> #include<vector> #include<map> #include<queue> #include<bitset> #define ll long long #define debug(x) cout<<#x<<" "<<x<<endl; #define db(x,y)cout<<#x<<" "<<#y<<" "<<x<<" "<<y<<endl; using namespace std; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void print(int x){ if(!x)return ; print(x/10); putchar((x%10)^48); } void sc(int x){ if(x<0){x=-x;putchar('-');} print(x); if(!x)putchar('0'); putchar(' '); } inline void Max(int &x,int y){if(x<y)x=y;} inline void Min(int &x,int y){if(x>y)x=y;} const int M=6e5+15,S=20;//30*20w=600w int n,m,to[M],nxt[M],ec=2,head[M],fa[M/2][20],mp[1<<S]; int type,id[M],dis[M],dx[M],dy[M],sz[M],D[M],lg_[M]; void ins(int a,int b){ to[ec]=b;nxt[ec]=head[a];head[a]=ec++; to[ec]=a;nxt[ec]=head[b];head[b]=ec++; } int flag=0; namespace P2{ int cnt[M]; void dfs(int x,int par,int d){ fa[x][0]=par; for(int i=1,j=lg_[max(dis[x],d)]+1;i<=j;i++)fa[x][i]=fa[fa[x][i-1]][i-1]; dis[x]=d; id[x]=id[par]; for(int i=head[x];i;i=nxt[i]){ if(to[i]!=par)dfs(to[i],x,d+1); } } void Up(int &x,int step){ while(step){ int k=step&-step; x=fa[x][mp[k]]; step-=k; } } int LCA(int a,int b){ if(dis[a]<dis[b])swap(a,b); Up(a,dis[a]-dis[b]); if(a==b)return a; int i,st=lg_[dis[a]]+1; for(i=st;i>=0;i--){ if(fa[a][i]!=fa[b][i])a=fa[a][i],b=fa[b][i]; } return fa[a][0]; } int Dis(int a,int b){ return dis[a]+dis[b]-2*dis[LCA(a,b)]; } void upd_D(int &x,int &y,int c,int v,int a,int b){ if(v>D[c])D[c]=v,x=a,y=b; } void upd(int a,int b){// 加边(a,b) 默认a较大 把b加到a里去 int i,y,k,x,c,d; if(sz[id[a]]<sz[id[b]])swap(a,b); c=id[a],d=id[b]; sz[c]+=sz[d]; dfs(b,a,dis[a]+1);//得到fa,dis x=dx[c],y=dy[c]; upd_D(x,y,c,D[d],dx[d],dy[d]); upd_D(x,y,c,Dis(dx[c],dx[d]),dx[c],dx[d]); upd_D(x,y,c,Dis(dx[c],dy[d]),dx[c],dy[d]); upd_D(x,y,c,Dis(dy[c],dx[d]),dy[c],dx[d]); upd_D(x,y,c,Dis(dy[c],dy[d]),dy[c],dy[d]); dx[c]=x,dy[c]=y; ins(a,b); } int qry(int a){ return max(Dis(a,dx[id[a]]),Dis(a,dy[id[a]])); } void solve(){ int i,j,k,op,ans=0,a,b; for(i=1,j=1;i<=n;i++){ lg_[i]=j; if((i&(i-1))==0)j++; } for(i=0;i<S;i++)mp[1<<i]=i; for(i=1;i<=n;i++){ id[i]=i; sz[i]=1; dx[i]=dy[i]=i; } while(m--){ rd(op); if(op==1){ rd(a),rd(b); if(type)a^=ans,b^=ans; upd(a,b); } else{ rd(a); if(type)a^=ans; ans=qry(a); sc(ans); } } } } int main(){ // freopen("hike.in","r",stdin); // freopen("hike.out","w",stdout); rd(type); rd(n);rd(m); P2::solve(); return 0; }
2017-04-17周一(187)
▲11:22:08 模拟赛T2 斜率优化/DP
暴力做法: 经典01背包+滚动数组优化 复杂度n*K
C=V 时候,相当于求是否能从物品中选出一些价值和为k. 我用bitset来乱搞hhh
正解:切入点在于每个物品的价格非常小,最多只要300!!所以我们把价格相同的东西,放在一起,假设最终选取了a个,那肯定是选价值最大的a个,因此可以处理出前缀和,v[s][a]表示价格为s的东西前a大的前缀和.v是一个斜率单调不增的函数,这对于后面的影响很重要!!
那么原问题可以这样求解:f[s][i]表示考虑价格为前s的物品,花了i价格,得到的最大价值.
f[s][i]=Max{f[s-1][i-k*s]+v[s][k]} 复杂度是O(s*K*n)
可以发现对于s,i的转移,之和与imods同余的点有关,因此我们对于一个s,将所有i根据mod s的 余数分类,每一类单独解决.
假设当前要解决的i/s=y,现在要找到对应的x,使得 f[s-1][x*s+mod]+v[s][y-x]最大,考虑x的变化.
假如x2>x1,且x2对应的值比x1更优时,y应该满足什么条件,由于v函数的上凸性质,我们可以通过二分,得到y的范围,是形如[y',oo]的结构.那么我们就用队列维护相邻两个x的y'值.假设队列最后的元素为a,b,现在要加一个c,如果bc的y比ab的小于等于,那么b点就可以删掉..一次类推.那么队列就是一个单调递增的东西.对于当前的y,把队首不合法(包括个数太大或者不是最优)的删掉,队首的当前元素就是最优的x了.注意二分的时候要判断y的初始范围.s的个数是有限制的啊!!
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #define ll long long using namespace std; const int N=(int)1e6+5; const int M=50005; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void pt(ll x){ if(!x)return; pt(x/10); putchar((x%10)^48); } void sc(ll x){ if(x)pt(x); else putchar('0'); // putchar(' '); } int sum=0,n,m,Q[M],h[M],p,s,tot; bool cur=0; ll v[N],f[2][M+500];//v[i] 表示选前i个的价值和 f[i]表示当前花了i 得到的最多价值 vector<int>A[305]; int pos(int x1,int x2,int mod){//求临界点 x2>x1 int a=p,L=0,R=min(tot,p-x2),t=x2-x1; if(t>tot)return p+1; double k=(double)(1.0*(f[cur^1][x2*s+mod]-f[cur^1][x1*s+mod])/(x2-x1)); ll g=(ll)(k*t); while(L<=R){ int mid=L+R>>1; if(v[mid+t]-v[mid]<=g){ a=mid,R=mid-1; }else L=mid+1; } return a+x2; } void work(){//s为单价 tot表示个数 int L,R,i,j,k,x,y;//滚动数组 cur^=1; sum+=s*tot; p=min(sum,m)/s;//能够买的上界 for(j=0;j<s;j++){//枚举mod s的余数 L=R=1;Q[R]=0;//x=0//Q[i]记录的是x的值 h再记录它和上一个的临界点y' f[cur][j]=f[cur^1][j]; for(y=1;y<=p;y++){//f[y*s+j]应该是队列的首元素 y-Q[L]是这次用的个数 while(L<=R&&(h[y]=pos(Q[R],y,j))<=h[Q[R]])R--;//删掉队尾非最优的 if(L<=R)h[y]=pos(Q[R],y,j); else h[y]=0; Q[++R]=y;//插入 while(L<R&&(y-Q[L]>tot||h[Q[L+1]]<=y))L++;//删掉队首非最优的 //y=2 y-Q[L]=1 -> Q[L]=1 f[cur][y*s+j]=f[cur^1][Q[L]*s+j]+v[y-Q[L]]; } } } bool cmp(int a,int b){return a>b;} void solve(){ int i,j,k,c,val; rd(n);rd(m); for(i=1;i<=n;i++){ rd(c),rd(val); A[c].push_back(val); } for(s=1;s<=300;s++){ tot=A[s].size(); if(!tot)continue; sort(A[s].begin(),A[s].end(),cmp); for(j=0;j<tot;j++)v[j+1]=v[j]+A[s][j]; work(); } for(i=1;i<=m;i++){ f[cur][i]=max(f[cur][i],f[cur][i-1]); sc(f[cur][i]); putchar(" "[i==m]); } } int main(){ // freopen("jewelry.in","r",stdin); // freopen("jewelry.out","w",stdout); solve(); return 0; }
▲16:12:55 BZOJ1597 斜率优化 一开始卡了半天我去,对于可包含的情况不用考虑了 那么现在的序列就是一个根据y升序的序列,x必定是降序的,这样就很显然了.在想题目的时候,要把冗余去掉,把条件越精简越好.
#include<cstdio> #include<iostream> #include<algorithm> #define ll long long using namespace std; const int M=5e4+5; struct node{ int x,y; bool operator<(const node &tmp)const{ if(y!=tmp.y)return y<tmp.y; return x<tmp.x; } }A[M]; int m,n=0,Q[M],h[M]; ll f[M]; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } int pos(int x1,int x2){//k<v[y] 的最小的y 假如k=3.2 int res=n+1,L=x2+1,R=n; ll k=(f[x2]-f[x1]+A[x1+1].x-A[x2+1].x-1)/(A[x1+1].x-A[x2+1].x); while(L<=R){ int mid=L+R>>1; if(A[mid].y>=k){ res=mid; R=mid-1; }else L=mid+1; } return res; } void solve(){ int i,j,k; rd(m); for(i=1;i<=m;i++)rd(A[i].x),rd(A[i].y); sort(A+1,A+1+m); for(i=1;i<=m;i++){ while(n&&A[n].x<=A[i].x)n--; A[++n]=A[i]; } int L=1,R=1; Q[1]=0; for(i=1;i<=n;i++){ while(L<R&&h[Q[L+1]]<=i)L++; f[i]=f[Q[L]]+1ll*A[Q[L]+1].x*A[i].y; while(L<=R&&(h[i]=pos(Q[R],i))<=h[Q[R]])R--; Q[++R]=i; } cout<<f[n]<<endl; } int main(){ // freopen("da.in","r",stdin); solve(); return 0; }
▲16:42:52 BZOJ1911 APIO2010 特别行动队 斜率优化 DP推式子.
▲19:01:59 BZOJ1096 ZJOI2007 仓库建设 斜率优化
▲19:22:49 BZOJ3156 防御准备 斜率优化 裸题
▲20:43:52 BZOJ1010 斜率优化裸题 注意注意注意 变量名不要重复啦!!!尤其是二分的L,R 和全局变量是否重复!!!
▲21:17:55 BZOJ 3437 斜率优化 裸题...
▲22:22:42 BZOJ 3675 APIO2014 斜率优化+终态枚举/推结论
2017-04-18 周二 (189)
▲11:08:42 HDU5956 树上DP+斜率优化. 明显的斜率优化,只是把dp从序列上搬到树上去了,所以需要还原现场,记录队列的左右指针位置以及删除的信息即可.
注意斜率优化有一个优化: 在求决策点时,假如A[mid]的单调的,就不用二分啦!!直接记录这个值就可以啦.
▲16:16:39 BZOJ1492 NOI2007 CDQ分治+斜率优化.
根据题目给出的每次都可以"全部买,全部卖掉"这个条件,可以把操作过程简化:
假设f[i]为i天操作完毕之后得到的最多人民币数量.那么第i天有两种可能,什么都不干,得到f[i-1],也就是上一天的人民币数量;或者用金券升值,假设在第k天买金券 就有f[i]=第k天最多的人民币来换得当天的金券 ,金券保留到第i天,然后卖掉 收获人民币.
f[i]=Max{f[i-1],(A[i]*R[k]+B[i])*f[k]/(A[k]*R[k]+B[k])},暴力就是n^2的.
对于这个式子并不能直接用斜率优化来搞.
按照一般的优化思路,有:
设x[i]=f[i]/(A[i]*R[i]+B[i])
x2>x1 且x2优于x1 时的i,满足: A[i]*(R[x2]*x[x2]-R[x1]*x[x1])+B[i]*(x[x2]-x[x1])>=0
对于有两个未知数的情况,可以通过移项相除的方式,把未知数变为一个-> A[i]*(R[x2]*x[x2]-R[x1]*x[x1])>=B[i]*(x[x1]-x[x2])
如果要相除,要确定(x[x1]-x[x2])的符号,假如是负的,那么就可以转化了.但是现在的x并不是有序的.那么我们把它构造成有序的.
可以利用CDQ分治,维护下标,那么每次就可以将左右部分 分别按照x和B/A排序,这样就可以完成优化了.
#include<cstdio> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> #define db double using namespace std; const int M=1e5+5; const db eps=1e-8,oo=1e18; int Q[M],n,s[M]; db f[M],x[M],A[M],B[M],Rate[M],h[M],ab[M]; bool cmp(int a,int b){ return ab[a]<ab[b]; } bool cmp2(int a,int b){ return x[a]<x[b]; } db calc(int a,int b){ if(fabs(x[b]-x[a])<eps)return oo; return (Rate[b]*x[b]-Rate[a]*x[a])/(x[a]-x[b]); } void work(int L,int R){ if(L==R){ f[L]=max(f[L],f[L-1]); x[L]=f[L]/(A[L]*Rate[L]+B[L]); return; }//已经更新完了 int i,j,t=0,l=1,r=0,mid=L+R>>1;//先处理左边 work(L,mid); h[L]=-oo; for(i=L;i<=mid;i++)s[t++]=i; sort(s,s+t,cmp2); for(i=0;i<t;i++){ while(l<=r&&(h[s[i]]=calc(Q[r],s[i]))<=h[Q[r]])r--; Q[++r]=s[i]; } for(i=mid+1,t=0;i<=R;i++)s[t++]=i; sort(s,s+t,cmp); for(i=0;i<t;i++){ while(l<r&&h[Q[l+1]]<=ab[s[i]])l++; f[s[i]]=max(f[s[i]],(A[s[i]]*Rate[Q[l]]+B[s[i]])*x[Q[l]]); } work(mid+1,R); } int main(){ // freopen("da.in","r",stdin); int i,j,k; scanf("%d %d",&n,&k); f[0]=(db)k;//第一天的初始人民币 for(i=1;i<=n;i++){ scanf("%lf %lf %lf",&A[i],&B[i],&Rate[i]); ab[i]=B[i]/A[i]; } work(1,n); printf("%.3lf ",f[n]); return 0; }
CDQ分治可以通过分治下标或者别的信息,来维护dp或者操作的转移->每个点会被所有它前面的点都更新到.同时,又可以进行排序来维护其他维度.
2017-04-19周三(191)
▲14:19:48 NOI2014 购票 点分治/CDQ分治/斜率优化.
首先写一个简单的dp,可以得到
f[x]=Min{f[y]-dis[y]*p[x]}+dis[x]*p[x]+q[x] 且dis[x]-dis[y]<=lim[x],y是x的祖先.
假如没有lim[x]的限制,就可以通过斜率优化,对于当前的p[x]二分出最优答案.
假如有lim[x]的限制,就可以无法满足直接用斜率优化,这样可能漏掉最优解.因此需要想方法把lim这个条件去掉.
排序是一种方法,待更新的点和可用来更新的点按照能够去的最远和dis分别排序,一边求值一边更新,这样就可以保证当前凸包里的东西都是合法的.
那么假如是在序列上,我们可以直接用cdq分治来解决这个问题了.用左儿子来更新右儿子,这个就是链的做法.
对于一棵树,也用类似的方法,对于每一条链都类似于一个序列,但是又不能每条链分别考虑,而每条链都有多多少少的重叠部分,所以可以用点分治.在这里包含根的子树就相当于下标更小,需要先完成.对于一个以x为根的子树,找到重心cg,对于重心连向x那条边的那个子树需要最先处理,于是先处理以x为根的剩下部分...以此类推,保证cg到x的这部分都处理完了,那么剩下的部分就是cg的子树们了,用cg到x之间的点来更新cg的子树即可.类似于序列上的做法.
好神啊!!!对于某一维有条件限制的情况可以通过分治来排序降维!!!CDQ!!!
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #define ll long long using namespace std; const int M=2e5+5; const ll oo=1e18; int vis[M],Q[M],ty,n,ec=2,to[M],nxt[M],sz[M],head[M],fa[M],p[M],A[M]; ll dis[M],h[M],lim[M],w[M],q[M],f[M]; void Min(ll &a,ll b){if(a>b)a=b;} inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } inline void Rd(ll &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void print(ll x){ if(!x)return ; print(x/10); putchar((x%10)^48); } void sc(ll x){ if(x<0){x=-x;putchar('-');} print(x); if(!x)putchar('0'); putchar(' '); } void ins(int a,int b,ll c){ to[ec]=b;nxt[ec]=head[a];w[ec]=c;head[a]=ec++; } void dfs(int x){ for(int i=head[x];i;i=nxt[i]){ dis[to[i]]=dis[x]+w[i]; dfs(to[i]); } } void find_cg(int x,int tot,int &cg){//找到当前联通块的重心 -> 没有子树sz>tot/2 sz[x]=1; int f=1; for(int i=head[x];i;i=nxt[i]){ if(!vis[i]){ find_cg(to[i],tot,cg); sz[x]+=sz[to[i]]; if(sz[to[i]]>(tot>>1))f=0; } } if(tot-sz[x]>(tot>>1))f=0; if(f)cg=x; } void rdfs(int x,int &m){ A[++m]=x; for(int i=head[x];i;i=nxt[i]){ if(!vis[i])rdfs(to[i],m); } } bool cmp(int a,int b){//按照能够上去的最远点 从低到高排序 return dis[a]-lim[a]>dis[b]-lim[b]; } ll calc(int a,int b){//b比a优 if(f[a]>=f[b])return (f[a]-f[b])/(dis[a]-dis[b]); return -(f[b]-f[a]+dis[a]-dis[b]-1)/(dis[a]-dis[b]); } int find(int l,int r,int a){ int res=-1; while(l<=r){ int mid=l+r>>1; if(a<=h[Q[mid]]){ res=Q[mid]; l=mid+1; }else r=mid-1; } return res; } void solve(int rt,int tot){ if(tot==1)return; int l=1,r=0,cg=-1,i,j,k,m=0; find_cg(rt,tot,cg);//找到了cg for(i=head[cg];i;i=nxt[i])vis[i]=1;//分离子树 solve(rt,tot-sz[cg]+1);//还包括cg这个点的,这样就把cg到根以上的所有点都solve好了 for(i=head[cg];i;i=nxt[i])rdfs(to[i],m); sort(A+1,A+1+m,cmp); h[cg]=oo; for(i=1,j=cg;i<=m;i++){ for(;j!=fa[rt]&&dis[j]>=dis[A[i]]-lim[A[i]];j=fa[j]){//可以搞 while(l<=r&&(h[j]=calc(Q[r],j))>=h[Q[r]])r--;//找到最小的>=p的 Q[++r]=j; } k=find(l,r,p[A[i]]);//找到最大的<=p的 Q[i] if(~k)Min(f[A[i]],f[k]+(dis[A[i]]-dis[k])*p[A[i]]+q[A[i]]); } for(i=head[cg];i;i=nxt[i])solve(to[i],sz[to[i]]); } int main(){ // freopen("da.in","r",stdin); // freopen("my.out","w",stdout); int i,j,k,a,b; ll c; rd(n);rd(ty); for(i=2;i<=n;i++){ rd(fa[i]),Rd(c),rd(p[i]),Rd(q[i]),Rd(lim[i]); ins(fa[i],i,c); f[i]=oo; } dfs(1);//得到dis solve(1,n);// rt/sz for(i=2;i<=n;i++)sc(f[i]); return 0; }
▲ 22:35:05 HNOI2017 D1T1 找结论
首先确定平衡二叉树有一个性质:每个点的子树对应的值是一个区间.
①模拟单旋最值得操作,进行单旋提根之后,假设最值为x,除了x的儿子,其他点的深度都会增加+1,x的深度变为1.
②模拟单旋删除最值操作,最值为x,x的子树的点的深度会-1.
④插入操作: 加入一个点值为x,一定插在当前树中x的前驱或者x的后继的下面,代价是fa的深度+1.
所以现在我们需要维护每个点的深度,再求前驱后继即可.
用set求前驱后继,用线段树维护深度,进行单点赋值,区间更新.
2017-04-20(194)
▲10:39:59 HNOI2017 D1T3 枚举 暴力想法是枚举旋转的位置,然后求最优的c.首先为了简化旋转匹配,把一个串倍长.
假如A与B的第j个开始匹配,代价就是∑(A[i]-B[j+i]-c)^2
分解之后发现是: ∑(Ai-B[j+i])^2+nc^2-2*∑(A[i]-B[j+i])c
求出c的一次项系数,那么关于c的是一个二次函数,可以O(1)求出最值.复杂度是O(n^2).
再把前面的展开 ∑Ai^2 +∑B[j+i]^2-2*∑Ai*B[i+j]
前面两项和c的系数都可以用前缀和O(1),但是第三项就很恶心了....考虑如何快速求出第三项,第三项是一个和.是两个数列对应项相乘的和.有点像卷积,但是又不是,但是我们可以通过反转B,写成卷积形式,然后用ntt优化~~注意!!因为这里的A,B值有点大,但又不是特别大,所以尽量不要用fft,会丢失精度.
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #define ll long long #define db double using namespace std; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } inline void Max(int &x,int y){if(x<y)x=y;} inline void Min(int &x,int y){if(x>y)x=y;} const int M=200005; const int oo=1e9+5; const int g=3,P=479*(1<<21)+1; int ans=oo,A[M],B[M],n,m; int qry(int b){ b=abs(b); double p=1.0*b/(2*n); int c=(int)(p+0.5); return n*c*c-b*c; } db pi=acos(-1); namespace P3{ int pw(int a,int b){ int res=1; while(b){ if(b&1)res=1ll*res*a%P; a=1ll*a*a%P; b>>=1; }return res; } void Add(int &x,int y){ x+=y; if(x<0)x+=P; else if(x>=P)x-=P; } int a[M],b[M]; int R[M],C[M],D[M],len,tot,m;//cd 记录平方和,x,y记录和 void fft(int a[],int f){ int i,j,k,len; for(i=0;i<tot;i++)if(i<R[i])swap(a[R[i]],a[i]); for(i=1;i<tot;i<<=1){ len=i<<1; int w,wn=pw(g,(P-1)/len); if(f)wn=pw(wn,P-2); for(j=0;j<tot;j+=len){ for(w=1,k=0;k<i;k++,w=1ll*w*wn%P){ int t=a[j+k],y=1ll*w*a[j+k+i]%P; Add(a[j+k]=t,y); Add(a[j+k+i]=t,-y); } } } } void work(){ int i,j,k,c=0,d=0; for(i=1;i<=m;i++){ D[i]=D[i-1]+B[i]*B[i]; C[i]=C[i-1]+B[i]; if(i<=n)d+=A[i]*A[i],c+=A[i]; } a[0]=b[0]=0; for(i=1;i<=n;i++)a[i]=A[i]; for(i=n+1;i<tot;i++)a[i]=0; for(i=1;i<m;i++)b[i]=B[m-i]; for(i=m;i<tot;i++)b[i]=0; fft(a,0); fft(b,0); for(i=0;i<tot;i++)a[i]=1ll*a[i]*b[i]%P; fft(a,1); int inv=pw(tot,P-2); for(i=0;i<n;i++){ int sum=0,val=1ll*a[m-i]*inv%P; sum=d+D[n+i]-D[i]+qry(2*(c-C[n+i]+C[i]))-2*val; ans=min(ans,sum); } } void solve(){ m=n*2; int i; for(tot=1;tot<=m;tot<<=1,len++); for(i=0;i<tot;i++)R[i]=(R[i>>1]>>1)|((i&1)<<len-1); for(i=1;i<=n;i++)rd(A[i]),A[i+n]=A[i]; for(i=1;i<=n;i++)rd(B[i]),B[i+n]=B[i]; work(); printf("%d ",ans); } } int main(){ // freopen("gift.in","r",stdin); // freopen("gift.out","w",stdout); rd(n);rd(m); P3::solve(); return 0; }
▲16:42:22 HNOI2017 D1T2 猜结论!!终态枚举!! 对于第一种情况 ,i,j分别是[i,j]的最大值,这个不好枚举,但是可以把这个条件转化一下:[i+1,j-1]中的最大数字<i,j那么我们是不是可以枚举[i+1,j-1]中的最大数字呢??假如枚举为i,那么对应的l,r就是左边右边第一个比ai大的数字!!
那么这个问题就可以解决了.
考虑第二种.对于第二种情况,假设L是最大值,那么次大值为L+k,那么保证[L+k+1,R]不包含比L+k大的数字,这个和上面的结构有点像?
对于i,假如以l[i]作为左端点,那么次大值为i时,保证右端点在[i+1,r[i]-1]就可以保证满足p2条件.按照这样的方式能够枚举到所有的p2,因为对于任意一对最大和次大都被枚举到了.
现在知道解的构成,就考虑如何求出解.首先只考虑最大值在左端点的情况(因为同理可以求出最大值在右端点的答案):
对于p2:每个右端点对应一个区间,可以将询问按照L从大到小,并且同时从大到小进行[L,a,b]的更新(表示,以L为左端点,右端点在[a,b]可行).这样保证现在更新到的都是满足这个询问的条件的.然后问题就变成了区间更新,区间求和了,用线段树可以简单求解.
对于p1,可以看做每次的a,b都相等.但是由于p1,p2可能不同,所以在线段树上存储个数不便,不如直接存权值,就是个数*p1或p2后的值,这样只要进行加减即可.
同理右端点也可以这么求,然后问题就解决了~
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #define ll long long using namespace std; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void print(ll x){ if(!x)return ; print(x/10); putchar((x%10)^48); } void sc(ll x){ if(x<0){x=-x;putchar('-');} print(x); if(!x)putchar('0'); putchar(' '); } const int M=2e5+5; int p1,p2,n,m,l[M],r[M],stk[M],A[M],tot,u[M],v[M]; ll res[M]; struct node{ int id,a,b,c; }q[M<<2]; struct Seg{ int add[M<<2]; ll t[M<<2]; void down(int L,int R,int mid,int p){ if(!add[p])return; t[p<<1]+=1ll*add[p]*(mid-L+1); t[p<<1|1]+=1ll*add[p]*(R-mid); add[p<<1]+=add[p]; add[p<<1|1]+=add[p]; add[p]=0; } void up(int p){ t[p]=t[p<<1]+t[p<<1|1]; } void upd(int L,int R,int l,int r,int a,int p){ if(L==l&&R==r){ t[p]+=1ll*a*(R-L+1); add[p]+=a; return; } int mid=L+R>>1; down(L,R,mid,p); if(r<=mid)upd(L,mid,l,r,a,p<<1); else if(l>mid)upd(mid+1,R,l,r,a,p<<1|1); else upd(L,mid,l,mid,a,p<<1),upd(mid+1,R,mid+1,r,a,p<<1|1); up(p); } ll qry(int L,int R,int l,int r,int p){ if(L==l&&R==r)return t[p]; int mid=L+R>>1; down(L,R,mid,p); if(r<=mid)return qry(L,mid,l,r,p<<1); else if(l>mid)return qry(mid+1,R,l,r,p<<1|1); else return qry(L,mid,l,mid,p<<1)+qry(mid+1,R,mid+1,r,p<<1|1); } void init(int L,int R,int p){ add[p]=t[p]=0; if(L==R)return; int mid=L+R>>1; init(L,mid,p<<1); init(mid+1,R,p<<1|1); } }T; bool cmpL(node x,node y){ if(x.a!=y.a)return x.a>y.a; return x.id<y.id;//更新在询问之前 } bool cmpR(node x,node y){ if(x.b!=y.b)return x.b<y.b;// return x.id<y.id;//更新在询问之前 } void solve(){ int i,j,k,top=0; rd(n);rd(m);rd(p1);rd(p2); for(i=1;i<=n;i++)rd(A[i]),r[i]=n+1; //找到左边第一个比我大的点l[i] for(i=1;i<=n;i++){ while(top&&A[stk[top]]<A[i]){ r[stk[top]]=i; top--; } l[i]=stk[top]; stk[++top]=i; } for(i=1;i<=m;i++){ q[i].id=i,rd(q[i].a),rd(q[i].b); u[i]=q[i].a,v[i]=q[i].b; } tot=m; for(i=1;i<=n;i++){//[li,x] x<-[i+1,r[i]-1] if(l[i]&&i+1<=r[i]-1)q[++tot]=(node){0,l[i],i+1,r[i]-1}; } sort(q+1,q+1+tot,cmpL); for(i=1;i<=tot;i++){ if(!q[i].id)T.upd(1,n,q[i].b,q[i].c,p2,1); else res[q[i].id]+=T.qry(1,n,q[i].a,q[i].b,1);//区间求和 } T.init(1,n,1); for(i=1,tot=m;i<=m;i++)q[i]=(node){i,u[i],v[i]}; for(i=1;i<=n;i++){ if(r[i]<=n&&l[i]+1<=i-1)q[++tot]=(node){0,l[i]+1,r[i],i-1}; if(l[i]&&r[i]<=n)q[++tot]=(node){-1,l[i],r[i],l[i]}; } sort(q+1,q+1+tot,cmpR); for(i=1;i<=tot;i++){ if(q[i].id==-1)T.upd(1,n,q[i].a,q[i].a,p1,1); else if(!q[i].id)T.upd(1,n,q[i].a,q[i].c,p2,1); else res[q[i].id]+=T.qry(1,n,q[i].a,q[i].b,1); } for(i=1;i<=m;i++){ res[i]+=p1*(v[i]-u[i]); sc(res[i]); } } int main(){ // freopen("sf.in","r",stdin); // freopen("sf.out","w",stdout); solve(); return 0; }
▲22:25:04 模拟赛T2 KMP大法好~~遇到一个模式串和匹配串的问题可以通过KMP解决啊~~就是在判断匹配的时候根据条件变通一下~
2017-04-23周日(196)
▲14:22:06 模拟赛T1 概率DP 把每个东西的贡献分开考虑->m=1 当m=2时,把x^2看成一种win了x场的方案中任选两个有序可重复的ai的方案数!
那么我们考虑任意一个二元组对答案的贡献:考虑任意一个二元组被选中的概率,对于相同元素的二元组,被选中的概率就是赢的概率,可计算;否则(a,b)就是a赢了并且b赢了的概率.但要去掉两者冲突的情况,然后推出式子用前缀和优化一下就好了.
对于m很大但是n较小的情况 考虑用Dp来求出赢得x场的方案数.
我们现在要确保有x场赢得比赛,假如设f[i][j]为前i个人赢了j场的方案数,因为不确定对方还剩下那些人所以不好求,既然这不好求,那么我们就忽略这个部分!只要确定有x人赢了就好,其他人不管了.
我们从小到大dp,这样保证可以确定出第i个人赢的选择.从而求出dp[i][j],前i个人,赢了j场,其他人不管的方案数.
但是这个并不是答案,要算出最终的答案g[i]表示赢i场的方案数还要继续算. 对于f[n][i],考虑其他输掉的n-i个人的情况,它们一共有(n-i)!种选择,但是某些选择会让这n-i中有些人也赢,我们还要去掉这些方案.只要枚举一共多了多少人赢假设有j(j>i)个人赢了 -g[j]*C[j][i]就是方案数.
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<ctime> #include<cstdlib> #include<cmath> #include<string> #include<vector> #include<map> #include<queue> #include<bitset> #define ll long long #define debug(x) cout<<#x<<" "<<x<<endl; #define db double using namespace std; inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } void print(int x){ if(!x)return ; print(x/10); putchar((x%10)^48); } void sc(int x){ if(x<0){x=-x;putchar('-');} print(x); if(!x)putchar('0'); putchar(' '); } inline void Max(int &x,int y){if(x<y)x=y;} inline void Min(int &x,int y){if(x>y)x=y;} const int P=1e9+7; const int M=1e6+5; int n,m,A[M],B[M]; int pw(int a,int b){ int res=1; while(b){ if(b&1)res=1ll*a*res%P; a=1ll*a*a%P; b>>=1; } return res; } void Add(int &x,int y){ x+=y; if(x>=P)x-=P; } namespace P1{ void solve(){ int i,j,k,inv=pw(n,P-2),ans=0; for(i=0;i<n;i++)rd(A[i]); for(i=0;i<n;i++)rd(B[i]); for(i=0,j=0;i<n;i++){ while(j<n&&B[j]<=A[i])j++; Add(ans,j); } ans=1ll*ans*inv%P; printf("%d ",ans); } } namespace P2{// m=2 void solve(){ int i,j,k,ans=0,inv=pw(n,P-2),sum=0;// 1/n*(n-1) * (w[i]-1)*∑w[j] for(i=1;i<=n;i++)rd(A[i]); for(i=0;i<n;i++)rd(B[i]); for(i=1,j=0;i<=n;i++){ while(j<n&&A[i]>=B[j])j++; if(j)Add(ans,1ll*(j-1)*sum%P); Add(sum,j); } ans=1ll*ans*inv%P; ans=1ll*ans*pw(n-1,P-2)%P; ans=ans*2%P; Add(ans,1ll*sum*inv%P); printf("%d ",ans); } } namespace P3{ const int M=2005; int C[M][M];//400w int f[M][M],g[M],fac[M]; void solve(){ int i,j,k,ans=0; for(i=1;i<=n;i++)rd(A[i]); for(i=0;i<n;i++)rd(B[i]); //f[i][j]=f[i-1][j]+f[i-1][j-1]*p C[0][0]=fac[0]=1; f[0][0]=1; for(k=0,i=1;i<=n;i++){ while(k<n&&A[i]>=B[k])k++; for(j=0;j<=i;j++){ f[i][j]=f[i-1][j]; if(k-j+1>0)Add(f[i][j],1ll*f[i-1][j-1]*(k-j+1)%P); } C[i][0]=1; for(j=1;j<=i;j++)Add(C[i][j]=C[i-1][j],C[i-1][j-1]); fac[i]=1ll*fac[i-1]*i%P; } for(i=n;i>=1;i--){//f[n][i]*(n-i)!-去掉不小心赢的=∑g[j]*C[j][i] g[i]=1ll*f[n][i]*fac[n-i]%P; for(j=i+1;j<=n;j++)Add(g[i],-1ll*g[j]*C[j][i]%P+P); Add(ans,1ll*g[i]*pw(i,m)%P); } ans=1ll*ans*pw(fac[n],P-2)%P; printf("%d ",ans); } } int main(){ // freopen("duel.in","r",stdin); // freopen("duel.out","w",stdout); rd(n);rd(m); if(m==1)P1::solve(); else if(m==2)P2::solve(); else P3::solve(); return 0; }
▲19:01:21 模拟赛T3 扫描线 若存在相切的情况,一定会保证是在同一列中相邻的两个圆的情况,所以根据圆心从上到下排序,然后根据圆从左到右出现进行扫描线排序.然后维护上下方向的前驱后继.但是有一个bug就是在删除一个点的时候会修改一对前驱后继,因此需要判断这一对是否已经计算在答案中了.用map来维护即可.
#include<cstdio> #include<cstring> #include<map> #include<algorithm> #include<iostream> #include<set> #define ll long long #define mkp(a,b) make_pair(a,b) using namespace std; const int M=5e5+5; inline void rd(int &res){ res=0;char c;int k=1; while(c=getchar(),c<48&&c!='-'); if(c=='-')c=getchar(),k=-1; do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); res*=k; } struct CIRCLE{ int x,y,r; bool operator<(const CIRCLE &tmp)const{ if(y!=tmp.y)return y<tmp.y; return x<tmp.x; } }a[M]; struct node{ int x,id;//id<0 表示删除 bool operator<(const node &tmp)const{ if(x!=tmp.x)return x<tmp.x; return id<tmp.id; } }b[M<<1];//每个圆有两个动作 typedef pair<int,int> pii; map<pii,bool>mp; set<int>s; set<int>::iterator it; int n,ans=0;// ll sq(int a){return (ll)a*a;} void chk(int p,int q){ pii f=mkp(p,q); if(mp.find(f)!=mp.end()){return;} bool t=(sq(a[p].x-a[q].x)+sq(a[p].y-a[q].y)==sq(a[p].r+a[q].r)); ans+=t; mp[f]=t; } void solve(){ int tot=0,i,j,k; rd(n); for(i=1;i<=n;i++){ rd(a[i].x),rd(a[i].y),rd(a[i].r); mp[mkp(a[i].x+a[i].r,a[i].y)]=true; } sort(a+1,a+1+n); for(i=1;i<=n;i++){//先要考虑两个圆竖直相切的情况> if(mp.find(mkp(a[i].x-a[i].r,a[i].y))!=mp.end())ans++; b[++tot]=(node){a[i].x-a[i].r,i}; b[++tot]=(node){a[i].x+a[i].r,-i}; } mp.clear(); sort(b+1,b+1+tot); int pre,nxt; for(i=1;i<=tot;i++){ int id=b[i].id,pre=-1,nxt=-1; if(id>0){//找到前驱后继 it=s.upper_bound(id);// if(it!=s.end()){ nxt=*it; } if(it!=s.begin())pre=*(--it); if(~pre)chk(pre,id); if(~nxt)chk(id,nxt); s.insert(id); } else{//删除操作 it=s.find(-id);it++; if(it!=s.end())nxt=*it; it--; if(it!=s.begin())pre=*(--it); if(~pre&&~nxt)chk(pre,nxt); s.erase(s.find(-id)); // printf("%d %d %d %d ",id,pre,nxt,ans); } } printf("%d ",ans); } int main(){ // freopen("bubble.in","r",stdin); solve(); return 0; }