2017-03-01周三(121)
▲07:57:49 CTSC2010 D1T3 性能优化 NTT变形/快速幂
▲14:06:15 QTREE4 边分治/堆/构造虚点 对于求解"路径",可以考虑边分治,这是很好的选择.每次找到一条边,保证边分成的两棵子树sz的较大值最小.然后求解一定经过这条边的答案.
边分治对于菊花图会退化.所以要加上新点,使原树变成二叉树.这样就不会退化了.对于每条边,用两个堆来维护端点下面子树路径的最值.(边分治的代码没有传说中那么恶心啊~(你确定???))
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 #include<queue> 6 #include<set> 7 using namespace std; 8 const int M=2e5+5; 9 const int S=20; 10 const int oo=1e9+5; 11 inline void rd(int &res){ 12 res=0;char c; 13 int k=1; 14 while(c=getchar(),c<48&&c!='-'); 15 if(c=='-'){k=-1;c=getchar();} 16 do res=(res<<1)+(res<<3)+(c^48); 17 while(c=getchar(),c>=48); 18 res*=k; 19 } 20 void pt(int x){ 21 if(!x)return ; 22 pt(x/10); 23 putchar((x%10)^48); 24 } 25 void sc(int x){ 26 if(x)pt(x); 27 else putchar('0'); 28 putchar(' '); 29 } 30 struct node{ 31 int id,v; 32 bool operator<(const node &tmp)const{ 33 return v<tmp.v;//大顶堆 34 } 35 }; 36 int sum,ec=1,tot=0,fa[M],n,m,v[M],vis[M],sz[M],f[M][2],len[M]; 37 priority_queue<node>Q[M][2];//记录的是边的序号 38 multiset<int>s; 39 struct EDGE{ 40 int a,b,c; 41 }ed[M]; 42 struct G{ 43 int head[M],to[M*S],nxt[M*S],dis[M*S],ec;//1200w to<0 说明是0 否则是1 44 G(){ec=1;} 45 void ins(int a,int b,int c){ 46 to[ec]=b;dis[ec]=c;nxt[ec]=head[a];head[a]=ec++; 47 } 48 void add(int a){//把a算入答案了 49 int i,y,c=1; 50 for(i=head[a];i;i=nxt[i]){ 51 y=to[i],c=1; 52 if(y<0){ 53 y=-y;c=0; 54 } 55 Q[y][c].push((node){a,dis[i]}); 56 while(!Q[y][c].empty()&&v[Q[y][c].top().id])Q[y][c].pop(); 57 if(Q[y][c].top().v!=f[y][c]){ 58 s.erase(s.find(f[y][c]+f[y][c^1]+len[y])); 59 f[y][c]=dis[i]; 60 s.insert(f[y][c]+f[y][c^1]+len[y]); 61 } 62 } 63 } 64 void del(int a){//把a删除答案 65 int i,y,c=1; 66 for(i=head[a];i;i=nxt[i]){ 67 y=to[i],c=1; 68 if(y<0){ 69 y=-y;c=0; 70 } 71 if(Q[y][c].top().id==a){ 72 Q[y][c].pop(); 73 s.erase(s.find(f[y][c]+f[y][c^1]+len[y])); 74 while(!Q[y][c].empty()&&v[Q[y][c].top().id])Q[y][c].pop(); 75 if(!Q[y][c].empty())f[y][c]=Q[y][c].top().v; 76 else f[y][c]=-oo; 77 // if(f[y][c]+f[y][c^1]+len[y]==15)printf("%d %d %d ",a,f[y][c^1],f[y][c]); 78 s.insert(f[y][c]+f[y][c^1]+len[y]); 79 } 80 } 81 } 82 }G; 83 int head[M],nxt[M<<1],to[M<<1],w[M<<1],mark[M<<1]; 84 void ins(int a,int b,int c){//a->b 85 to[ec]=b;w[ec]=c;nxt[ec]=head[a];head[a]=ec++; 86 } 87 void build(int x,int par){ 88 int son=-1; 89 vis[x]=1; 90 sz[x]=1; 91 for(int i=head[x];i;i=nxt[i]){ 92 if(vis[to[i]])continue; 93 if(son==-1){ 94 son=0; 95 ed[++tot]=(EDGE){x,to[i],w[i]}; 96 build(to[i],x); 97 sz[x]+=sz[to[i]]; 98 } 99 else { 100 if(!son)son=++n,v[n]=1; 101 ins(son,to[i],w[i]); 102 } 103 } 104 if(son>0){ 105 ed[++tot]=(EDGE){x,son,0}; 106 build(son,x); 107 sz[x]+=sz[son]; 108 } 109 } 110 void dfs(int x,int par,int &id,int mx){ 111 fa[x]=par; 112 for(int i=head[x];~i;i=nxt[i]){ 113 if(to[i]==par||mark[i])continue; 114 int a=max(sz[to[i]],sum-sz[to[i]]); 115 if(a<mx){ 116 mx=a;id=i; 117 } 118 dfs(to[i],x,id,mx); 119 } 120 } 121 void rdfs(int x,int par,int d,int num,bool fi){ 122 if(!v[x]){ 123 Q[abs(num)][fi].push((node){x,d}); 124 G.ins(x,num,d);//x->num with length =d 125 } 126 sz[x]=1; 127 for(int i=head[x];~i;i=nxt[i]){ 128 if(to[i]==par||mark[i])continue; 129 rdfs(to[i],x,d+w[i],num,fi); 130 sz[x]+=sz[to[i]]; 131 } 132 } 133 void solve(int x){ 134 int id,a=0,b,c,mx=2e9,i,j; 135 sum=sz[x]; 136 if(sum==1)return; 137 dfs(x,0,id,mx); 138 a=to[id],b=fa[to[id]],c=w[id]; 139 mark[id]=mark[id^1]=1; 140 rdfs(a,b,0,++tot,1);//这里已经把sz算好了 141 rdfs(b,a,0,-tot,0); 142 len[tot]=c; 143 if(!Q[tot][0].empty())f[tot][0]=Q[tot][0].top().v; 144 else f[tot][0]=-oo; 145 if(!Q[tot][1].empty())f[tot][1]=Q[tot][1].top().v; 146 else f[tot][1]=-oo; 147 s.insert(f[tot][0]+f[tot][1]+c); 148 solve(a);solve(b); 149 } 150 int main(){ 151 int a,b,c,i,j,k;char str[4]; 152 rd(n); 153 for(i=1;i<n;i++){ 154 rd(a);rd(b);rd(c); 155 ins(a,b,c); 156 ins(b,a,c); 157 } 158 c=n; 159 build(1,1);//先重构 160 ec=0; 161 for(i=1;i<=n;i++)vis[i]=0,head[i]=-1; 162 for(i=1;i<=tot;i++){ 163 ins(ed[i].a,ed[i].b,ed[i].c); 164 ins(ed[i].b,ed[i].a,ed[i].c); 165 }//以上把新图建好了 166 tot=0; 167 solve(1); 168 rd(m); 169 if(n==1){ 170 while(m--){ 171 scanf("%s",str); 172 if(str[0]=='A'){ 173 if(!v[1])sc(0); 174 else puts("They have disappeared."); 175 } 176 else rd(a),v[a]^=1; 177 } 178 return 0; 179 } 180 while(m--){ 181 scanf("%s",str); 182 if(str[0]=='A'){ 183 if(c)sc(max(0,*s.rbegin())); 184 else puts("They have disappeared."); 185 } 186 else{ 187 rd(a);v[a]^=1; 188 if(v[a])c--,G.del(a);//delete 189 else c++,G.add(a); //add 190 } 191 } 192 return 0; 193 }
2017-03-02周四(124)
▲08:25:31 模拟赛T1 网络流建模+最小费用最大流
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #include<ctime> 6 #include<cstdlib> 7 #include<cmath> 8 #include<string> 9 #include<vector> 10 #include<map> 11 #include<queue> 12 #include<bitset> 13 #define ll long long 14 #define debug(x) cout<<#x<<" "<<x<<endl; 15 #define db(x,y)cout<<#x<<" "<<#y<<" "<<x<<" "<<y<<endl; 16 using namespace std; 17 inline void rd(int &res){ 18 res=0;char c; 19 while(c=getchar(),c<48); 20 do res=(res<<1)+(res<<3)+(c^48); 21 while(c=getchar(),c>=48); 22 } 23 void print(int x){ 24 if(!x)return ; 25 print(x/10); 26 putchar((x%10)^48); 27 } 28 void sc(int x){ 29 if(x<0){x=-x;putchar('-');} 30 print(x); 31 if(!x)putchar('0'); 32 putchar(' '); 33 } 34 inline void Max(int &x,int y){if(x<y)x=y;} 35 inline void Min(int &x,int y){if(x>y)x=y;} 36 const int M=105; 37 const int oo=1e9; 38 int x[M],mark[M][M],y[M],n,A[M],B[M]; 39 int pre[M],cntx[M],cnty[M],st,en,to[M*M*2],nxt[M*M*2],cap[M*M*2],head[M],hd[M],vis[M],dis[M],ec=0,Q[M],ans=0,cost[M*M*2]; 40 void ins(int a,int b,int c,int d){ 41 to[ec]=b;nxt[ec]=head[a];cap[ec]=c;cost[ec]=d;head[a]=ec++; 42 to[ec]=a;nxt[ec]=head[b];cap[ec]=0;cost[ec]=-d;head[b]=ec++; 43 } 44 bool SPFA(){ 45 int i,j,k,L=0,R=0; 46 for(i=1;i<=en;i++)dis[i]=oo; 47 dis[st]=0; 48 Q[R++]=st; 49 while(L<R){ 50 int a=Q[L++];vis[a]=0; 51 for(i=head[a];~i;i=nxt[i]){ 52 if(cap[i]&&dis[to[i]]>dis[a]+cost[i]){ 53 dis[to[i]]=dis[a]+cost[i]; 54 pre[to[i]]=i; 55 if(!vis[to[i]]){ 56 vis[to[i]]=1; 57 Q[R++]=to[i]; 58 } 59 } 60 } 61 } 62 if(dis[en]>=oo)return false; 63 k=en; 64 int mn=oo; 65 while(k!=st){ 66 mn=min(mn,cap[pre[k]]); 67 k=to[pre[k]^1]; 68 } 69 k=en; 70 while(k!=st){ 71 cap[pre[k]]-=mn; 72 cap[pre[k]^1]+=mn; 73 k=to[pre[k]^1]; 74 } 75 ans+=dis[en]*mn; 76 return true; 77 } 78 void solve(){ 79 int i,j,k,flow; 80 st=2*n+1,en=2*n+2; 81 for(i=1;i<=n;i++){ 82 if(!cntx[i])continue; 83 ins(st,i,cntx[i],0); 84 for(j=1;j<=n;j++){ 85 if(cnty[j]){ 86 if(mark[i][j])ins(i,j+n,1,1); 87 else ins(i,j+n,1,0);//cap,cost 88 } 89 }//只能一个 90 } 91 for(i=1;i<=n;i++){ 92 if(cnty[i])ins(i+n,en,cnty[i],0); 93 } 94 while(SPFA()); 95 printf("%d ",ans); 96 } 97 int main(){ 98 // freopen("chess.in","r",stdin); 99 // freopen("chess.out","w",stdout); 100 memset(head,-1,sizeof(head)); 101 int a,b,t=0,i,j,k; 102 rd(n); 103 for(i=1;i<=n;i++){ 104 rd(x[i]);rd(y[i]); 105 B[t]=y[i]; 106 A[t++]=x[i]; 107 } 108 sort(A,A+t);a=unique(A,A+t)-A; 109 sort(B,B+t);b=unique(B,B+t)-B; 110 for(i=1;i<=n;i++){ 111 x[i]=lower_bound(A,A+t,x[i])-A+1; 112 y[i]=lower_bound(B,B+t,y[i])-B+1; 113 mark[x[i]][y[i]]=1; 114 cntx[x[i]]++,cnty[y[i]]++; 115 }//离散 116 solve(); 117 return 0; 118 }
▲11:11:43 模拟赛T3 树形DP+扫描线 由条件给出的"圆之间不相切,不相交",可以发现,圆之间的包含关系可以看成树的结构,只要找到每个圆的直接fa(第一个包含它的圆),建立树就可以O(n)dp得到结果.
暴力方法:将所有圆根据半径从小到大排序,暴力找到第一个fa,根据圆心距离和半径和平方的关系判断包含关系.
正解:扫描线!先将每个圆安排两个事件,在x-r[i]插入,在x+r[i]时删除.插入一个节点时,保证它的父亲(若存在的话)一定已经插入到对应的数据结构里去了.我们找到x-r[i],对于集合中的每个半圆,找到x-r[i]对应的y,再求出大于y[i]的第一个y.加入这个y在下半圆,说明是兄弟,否则是父亲.
至于删除直接在set里删掉即可..
▲21:21:11 模拟赛T2 二分+BFS 根据题目中"最长距离最短"这个典型的问题,可以很容易想到二分.现在确定了最长长度dis,那就要保证每次跳动之间的距离小于dis.
这里有一个结论:任意两个点之间的距离<=dis的时间是一个区间.把式子列出来之后,整理每一项,可以得到一个二次函数,开口向上,现在要求f(x)<=0的部分,显然解集是一个区间,通过解方程可以直接解出任意两个点之间的有效区间[l,r].
显然地,对每条边进行插入和删除操作,将所有操作根据时间排序.对每个时间点,维护当前可以到达的节点.对于在某个点停留时间不能超过s这个条件,它对于一个联通块来说是没用的,因为可以在联通块之内跳来跳去.但对孤立的点是有用的,所以每次删掉一条边,就判断两个端点是否变成了孤立的点,把孤立的点都放在一个队列里,然后在进行操作过程中判断孤立的点是否坚持不住了..
对于插入操作,如果两个点在不同的联通块,并且一个块能到,另一个不能,那就从能到的点bfs到另一个块即可.
注意虽然题目给出的精度为1e-4 这里eps不是设为1e-4,而是1e-8或者1e-9之类的.
2017-03-03 周五(127)
▲07:24:55 POJ1273 最大流模板题 Dinic大法好~
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 const int M=205;// 7 const int oo=2e9+5; 8 int head[M],dis[M],Q[M],cap[M<<1],to[M<<1],nxt[M<<1],ec=0,n,m; 9 void ins(int a,int b,int c){ 10 to[ec]=b;nxt[ec]=head[a];cap[ec]=c;head[a]=ec++; 11 to[ec]=a;nxt[ec]=head[b];cap[ec]=0;head[b]=ec++; 12 } 13 inline void rd(int &res){ 14 res=0;char c; 15 while(c=getchar(),c<48); 16 do res=(res<<1)+(res<<3)+(c^48); 17 while(c=getchar(),c>=48); 18 } 19 int dfs(int x,int f){ 20 if(x==n)return f; 21 int res=0; 22 for(int i=head[x];~i;i=nxt[i]){ 23 int y=to[i]; 24 if(dis[y]==dis[x]+1&&cap[i]){ 25 int v=min(cap[i],f-res); 26 v=dfs(y,v); 27 cap[i]-=v; 28 cap[i^1]+=v; 29 res+=v; 30 if(res==f)return f; 31 } 32 } 33 return res; 34 } 35 bool BFS(int st,int en){ 36 int i,j,L=0,x,R=0; 37 for(i=1;i<=n;i++)dis[i]=-1; 38 dis[st]=0; 39 Q[R++]=st; 40 while(L<R){ 41 x=Q[L++]; 42 for(i=head[x];~i;i=nxt[i]){ 43 if(cap[i]&&dis[to[i]]==-1){ 44 dis[to[i]]=dis[x]+1; 45 Q[R++]=to[i]; 46 } 47 } 48 } 49 return dis[en]>=0; 50 } 51 void solve(){ 52 int i,a,b,c,ans=0; 53 ec=0; 54 for(i=1;i<=n;i++)head[i]=-1; 55 for(i=1;i<=m;i++){ 56 rd(a);rd(b);rd(c); 57 ins(a,b,c); 58 } 59 while(BFS(1,n))ans+=dfs(1,oo); 60 printf("%d ",ans); 61 } 62 int main(){ 63 // freopen("da.in","r",stdin); 64 while(scanf("%d %d",&m,&n)!=EOF)solve(); 65 return 0; 66 }
注意点: ec初始值为0!!head初始值为-1!!!!切记切记!!
▲08:06:12 POJ1274 二分图最大匹配 匈牙利算法 很简单的思路:枚举左边的每一个节点,假如它还没由对象就给他找对象,找到他心仪的每个女孩子,然后看女孩子有没有对象,假如女孩子没有对象,那就直接牵手成功,进行配对;否则要看女孩的对象能不能再找到一个对象,如果可以,那么女孩也愿意和自己在一起了..恩就这样.
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 using namespace std; 6 const int M=205; 7 int vis[M<<1],to[M*M*2],nxt[M*M*2],head[M<<1],ec=1,pr[M<<1],n,m; 8 void ins(int a,int b){ 9 to[ec]=b;nxt[ec]=head[a];head[a]=ec++; 10 to[ec]=a;nxt[ec]=head[b];head[b]=ec++; 11 } 12 bool dfs(int a){ 13 for(int i=head[a];i;i=nxt[i]){ 14 int b=to[i]; 15 if(!vis[b]){ 16 vis[b]=1; 17 if(!pr[b]||dfs(pr[b])){ 18 pr[a]=b;pr[b]=a; 19 return true; 20 } 21 } 22 } 23 return false; 24 } 25 void solve(){ 26 int i,j,k,ans=0,a; 27 for(i=1;i<=n;i++){ 28 scanf("%d",&k); 29 while(k--){ 30 scanf("%d",&a); 31 ins(i,a+n); 32 } 33 } 34 m+=n; 35 for(i=1;i<=n;i++){ 36 if(pr[i])continue; 37 for(j=1;j<=m;j++)vis[j]=0; 38 if(dfs(i))ans++; 39 } 40 printf("%d ",ans); 41 for(i=1;i<=m;i++)head[i]=pr[i]=0;ec=1; 42 } 43 int main(){ 44 // freopen("da.in","r",stdin); 45 while(scanf("%d %d",&n,&m)!=EOF)solve(); 46 return 0; 47 }
▲14:41:46 POJ1637 混合图判断欧拉回路
1)有向图 每个点 出度=入度
2)无向图 每个点的度为偶数
那么混合图需要把无向边强行转为有向边,使得每个点的出度=入度.
假如我们把每个无向边随意定一个方向,求出每个点的 入度-出度 的值,设为d[i].
假如现在将一条边反向,那么这条边的两个端点的d[a]+2,d[b]-2...也就是奇偶性是不变的,所以假如有的点的di为奇数,肯定是不可行的.
考虑偶数的情况,将所有d[i]<0的点与ST连边,流量为-d[i]/2,所有d[i]>0的点与EN连边,流量为d[i]/2
其他的边保持不变,流量为1.
然后跑最大流,假如是满流,说明存在欧拉回路.
假如存在一条边st-i-....j-en,我们把这些边全部反向,i和j之间的点的d都不变,而i的入度+1,出度-1,j的入度-1,出度+1,刚好满足情况,因此只要找到最大流就是最优方案了.
注意注意: 在做网络流题目时,数组范围要看清楚!!!!很多时候要向起点和汇点连边,所以数组要开大一点!!!!!!
2017-03-06周一(134)
2016CCPC杭州赛 整理:
▲A - ArcSoft's Office Rearrangement 贪心+模拟 终态已经确定 直接根据最后的结果模拟即可 注意LL!!!
▲B - Bomb 预处理出任意两个点之间的到达关系 建边,强连通缩点,然后求入度为0 的强连通分量中值最小的
▲C - Car 贪心 根据题意 注意LL!!
▲D - Difference 暴搜+剪枝 对于求满足条件的解的个数的情况,若解并不多,可以采取暴搜.
▲E - Equation 此题和上题类似 也是 暴搜出奇迹 优化:在一开始判断所有情况都满足的可能.
▲F - Four Operations 贪心+枚举
▲J - Just a Math Problem 数学+欧拉函数+容斥原理 g(k)=2^f(k)
可以把这个式子附上新的意义:g(k)表示 (p,q)的对数,使得gcd(p,q)=1并且p*q=k
首先f(k)表示k的不同素因子个数,那么2^f(k)相当于这f(k)个素因子的子集.也就是每个素因子取或不取得到的结果.那么∑g(i) =(p,q)的对数,满足gcd(p,q)=1并且p*q<=n
那么我们可以设p<=q,再枚举p.那现在问题转化为 求p<=q<=n/p并且gcd(p,q)=1的q的个数.可以用容斥原理进行求解.由于求出的q可能小于p,所以要减去phi(p).
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #define ll long long #include<cmath> using namespace std; const int M=1e6+5,P=1e9+7; int phi[M],bit[1<<10],son[M][8],head[M],to[M*10],nxt[M*10],ec=1; ll n; int m; void ins(int a,int b){ to[ec]=b;nxt[ec]=head[a];head[a]=ec++; } void init(){// int i,j,k,p,x,y,c; for(i=0;i<10;i++)bit[1<<i]=i+1; for(i=1;i<M;i++)phi[i]=i; for(i=2;i<M;i++){ if(phi[i]==i){ for(j=i;j<M;j+=i){ phi[j]=phi[j]/i*(i-1); k=++son[j][0]; son[j][k]=i; } } } for(i=2;i<M;i++){//奇数是+,偶数是- for(p=(1<<son[i][0]),j=1;j<p;j++){ for(x=j,y=1,c=0;x;x-=k){ k=x&-x; y*=son[i][bit[k]]; c^=1; } if(c)ins(i,y); else ins(i,-y);//负数表示是减掉 } } } void solve(){ cin>>n; m=sqrt(n);// int i,j,k,ans=n%P; for(i=2;i<=m;i++){//枚举p ll t=n/i,sum=0; for(j=head[i];j;j=nxt[j]){ sum=(sum+t/to[j])%P; }//现在的sum表示不和i互素的 <=t的所有数 ///那么 和i 互素的<=t的所有数 sum=t-sum; sum-=phi[i]; ans=ans+sum%P; if(ans>=P)ans-=P; } ans=(ans-1)*2%P+1; printf("%d ",ans); } int main(){ // freopen("da.in","r",stdin); int i,cas; init(); scanf("%d",&cas); for(i=1;i<=cas;i++){ printf("Case #%d: ",i); solve(); } return 0; }
▲K - Kingdom of Obsession 对于数据范围非常非常大的情况,可以考虑: 当n很大时,答案是确定的.比如此题,当[s+1,s+n]中含两个素数时,答案肯定为no.
但是有一个bug,假如[1,n]和[s+1,s+n]有相交的部分,就要把问题转化为-> [1,s] [n+1,n+s]然后用同样方法解决.
由于相邻两个素数之间的距离不会很大,那么我们就设一个key=500,假如n>key那就直接得到答案.
否则,可以用二分图最大匹配来解决这个问题,[1,n]里每个数和[s+1,s+n]中它的倍数连边,然后求最大匹配即可.
2017-03-07周二(140)
▲模拟赛T2 YY/终态枚举 由于题目的信息"赋值",并且改变某一个格子的方式就是将所在列赋值,为了让每一列都是黑色,肯定有一行都是黑色,并且在一行变为黑色之前不可能有列变为全黑,那问题就转化成把某一行变为全黑的最小代价.枚举行x,能把(x,i)变为黑色,只可能是某一行的第x列是黑色,那么只要x列有一个黑色点就可以将x一行都变黑色.遇到奇怪的题目,就YY吧!!!YY大法好!!
▲模拟赛T3 SAM/离线/分类决策 【原来SAM深度不是logn啊!!收获!!】
比赛时候的做法:
将每个询问的[a,b]拆成[1,a-1],[1,b]对答案的贡献分别为-1,1然后按照右端点排序.然后把串在SAM上跑一遍,对于每个右端点r,得到当前匹配的最长后缀左端点l,再回答当前(1到x)中所有以r为右端点的询问.用BIT维护以每个右端点的询问的左端点的前缀和,然后暴力在SAM树上走,得到对应的答案.当SAM深度较小时,很快的!!只要串不是"aaaaaaaaaaaaaaaaaaaaaaa",深度基本就是logn了.
然而遇上上述的数据...这个算法就狗带啦~这个算法的复杂度是 O(q*logm*dep+∑len)..当q很大时,就会T掉.
优化:题目中给出了一个制约条件 :∑len<=10^5 ,也就是当q很大时,每个串的len就很小..所以当len很小时直接暴力枚举所有子串,然后求出每个子串的答案 .当len取sqrt(m)时,复杂度为O(m*sqrt(m)).就可以过了~
▲模拟赛T1 线段树/YY
60分:数据随机 -> 一个数A[i]经过几次操作就会变为0或者-1了..所以对于值相同的区间,把除法看作进行区间加就可以了.
100分: 把条件再放宽一些: 当一个区间除以d时,每个值的增量都相同时,就可以当做区间加了.复杂度:任意两个相邻的数之间的差每次缩小一半,所以只要logC次就可以合并了.所以是可行的.
▲18:47:09 CF375D 启发式合并 处理x的子树,现将x的所有轻儿子递归求解,然后删掉轻儿子的贡献.再递归重儿子,不删掉.然后再加上自己的贡献,然后再把轻儿子加进来.就可以得到x子树的信息.
现在求cnt数>=k的个数,可以维护一个数组ans[i],表示当前cnt值>=i的点的个数.每次添加一个点x时,ans[++cnt[x]]++即可,这样可以保证原来的贡献不删掉.当去掉一个点x时,只要ans[cnt[x]--]--即可.
▲19:47:09 CF 208E - Blood Cousins 启发式合并/二分 +倍增
启发式合并做法: 维护每个子树时dep为k的点个数即可,注意要减去自己的贡献
二分做法: 维护每个dep的vector,把每个点的dfs丢到对应的vector里去,然后二分求出在[l[x],r[x]]之间的点个数.
▲21:47:07 CF 246E - Blood Cousins Return 启发式合并/离线BIT
启发式合并:用multiset来维护当前子树中dep为k的名字集合即可.复杂度O(nlogn^2)
离线BIT:可以把dep相同的询问放在一起回答!!这样就少了一维!!不用考虑dep的限制了,而且复杂度并没有提高.把dep放在一起处理之后,问题就变为求一个区间里不同的元素个数.这个可以用BIT完成.将所有区间根据右端点排序,和所有点一起排序.当进入一个点x,假如它的权值已经出现过,那么把之前位置删掉,把新的位置插入.遇到询问直接求[l,r]之间的和就可以了.复杂度O(nlogn)
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> using namespace std; const int M=1e5+5; string str[M]; int n,m,ec=1,B[M],pre[M],head[M],f[M],L[M],R[M],clo=-1,to[M],nxt[M],dep[M],C[M],res[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(' '); } struct node{ int d,l,r,id; bool operator<(const node &tmp)const{ if(d!=tmp.d)return d<tmp.d; return r<tmp.r; } }A[M]; bool cmpstr(int a,int b){return str[a]<str[b];} bool cmp(int a,int b){ if(dep[a]!=dep[b])return dep[a]<dep[b]; return L[a]<L[b]; } int sum(int a){ int res=0; while(a){ res+=C[a]; a-=a&-a; } return res; } void upd(int a,int b){ while(a<=n){ C[a]+=b; a+=a&-a; } } void add(int x){ if(pre[f[x]])upd(pre[f[x]],-1); pre[f[x]]=L[x]; upd(L[x],1); } void dfs(int x){ L[x]=++clo; for(int i=head[x];i;i=nxt[i]){ dep[to[i]]=dep[x]+1; dfs(to[i]); } R[x]=clo; } void ins(int a,int b){ to[ec]=b;nxt[ec]=head[a];head[a]=ec++; } void solve(){ int i,j,k,t,a,b,d,fa; rd(n); for(i=1;i<=n;i++){ cin>>str[i];rd(fa); B[i]=i; ins(fa,i); } sort(B+1,B+1+n,cmpstr); for(i=1,k=1;i<=n;i=j,k++){ for(j=i;j<=n&&str[B[i]]==str[B[j]];j++)f[B[j]]=k; } dfs(0); for(i=1;i<=n;i++)B[i]=i; rd(m); for(i=1;i<=m;i++){ rd(a);rd(d); A[i]=(node){d+dep[a],L[a],R[a],i}; } sort(A+1,A+1+m); sort(B+1,B+1+n,cmp); for(i=j=k=1;i<=dep[B[n]];i++){//枚举dep for(t=k;j<=m&&A[j].d==i;j++){//每个询问 for(;k<=n&&dep[B[k]]==i&&L[B[k]]<=A[j].r;k++)add(B[k]); res[A[j].id]=sum(A[j].r)-sum(A[j].l-1); } for(;t<k;t++){ if(pre[f[B[t]]]==L[B[t]])upd(L[B[t]],-1),pre[f[B[t]]]=0; } for(;k<=n&&dep[B[k]]==i;k++); } for(i=1;i<=m;i++)sc(res[i]); } int main(){ // freopen("da.in","r",stdin); solve(); }
2017-03-08 周三(142)
▲14:07:14 CF 291 E 291E - Tree-String Problem hash+离线BIT/AC自动机
hash做法:求以每个点为起点的答案数,先得到从根出发到每个点所得的哈希值,可以O(1)算出以x为起点的方案的终点对应的hash值y,那么问题就变成,求区间[l,r]中权值为y的个数,只要把query和更新按照hash值排序,用bit维护一下即可.
注意这里hash素数选取:最好选两个素数,保险一些!!!
AC自动机做法:预处理出fail[]和pre[][]这样失配时可以直接找到下一个了!!!
▲16:45:46 CF 343D 线段树搞一发即可!!!
2017-03-09 周四(145)
▲14:50:47 模拟赛T1 A题 树形DP/YY神题 对于每个条件,可以扩展为一个区间的水有还是没有.比赛时的做法:把每个有水操作搞成一个区间有水,然后再看这个条件和无水的条件冲突的建边,得到一个二分图.
①【“多么痛的领悟”之 要对拍!!!】比赛时傻了吧唧的去染色,然后求两个集合中较大者!!可是这是二分图啊!!染色不就是左边一排右边一排吗!!!而且还不对拍!!你就狗带吧!!! 60分就这么没了!!这种思路其实就是求这个图的最大独立集,最大独立集=点数-最大匹配数.所以求一发最大匹配就可以了.
②【“多么痛的领悟”之 切分要写得详细一点啊!】为什么连送的10分都没有拿到呢??因为切分写炸了!!不是切分的程序,而是判断语句!!记得要同时判n,m等所有条件!!不要只判一个!!!你就狗带吧!!
现在是正解:
把每个询问展开成区间,有一个神奇的性质:区间之间若有交集,一定是包含和被包含的关系.这个很好证明.确定了这个结论之后,就可以想到上次的"圆"那道题.把这种关系构造成树形结构.具体来说:假如[A,B]包含[a,b],那么A,B的高度>=a,b的高度.并且a,b的有水和无水不会影响到A,B有水和无水.但是(A,B)有水就需要它的儿子们都是有水.所以每个节点有两个选择,分别是有水和无水,记录每个节点表示的询问中,有水的个数,和无水的个数,再直接从儿子转移即可.
还有一个问题,如何求出每个询问对应的l,r,以及如何建图,求l,r时单调栈+二分.建图就比较麻烦.我把每个询问根据l和h为关键字排序,将l,r,h相同的合并为一个点.再把l,r相同的看成一个东西,然后再用堆找到每个点的fa.然后建图..DP...然后AC.
好吧我承认我一点都没想到树形结构...╭(╯^╰)╮原因有二:①没发现区间之间的包含关系 ②没发现区间包含关系的同时,h也有关系. 如果这两个都发现了,树形结构应该不难想到.
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> 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); } const int M=1e5+5,oo=1e9+5; int n,m,L[M],fa[M],R[M],stk[M],top,head[M],to[M],nxt[M],ec=1,f[M][2]; struct node{ int a,h,k; bool operator<(const node &tmp)const{ return a<tmp.a; } }A[M]; struct Lay{ int l,r,h,id,c[2]; Lay(){c[0]=c[1]=0;} bool operator==(const Lay &tmp)const{ return l==tmp.l&&r==tmp.r&&h==tmp.h; } bool operator<(const Lay &tmp)const{ if(l!=tmp.l)return l<tmp.l; return h>tmp.h; } }B[M]; priority_queue<Lay>Q; int qry(int v[],int l,int r,int h){//大于等于h的 int res;//返回stk[res] while(l<=r){ int mid=l+r>>1; if(v[stk[mid]]>=h){res=mid;l=mid+1;} else r=mid-1; } return stk[res]; } void ins(int a,int b){ to[ec]=b;nxt[ec]=head[a];head[a]=ec++;//单向 } void dfs(int x){ for(int i=head[x];i;i=nxt[i]){ dfs(to[i]); f[x][0]+=max(f[to[i]][0],f[to[i]][1]); f[x][1]+=f[to[i]][1]; } f[x][0]+=B[x].c[0]; f[x][1]+=B[x].c[1]; } void solve(){ int i,j,k,pre; rd(n);rd(m); L[1]=R[n]=oo; for(i=1;i<n;i++){ rd(R[i]); L[i+1]=R[i]; } for(i=1;i<=m;i++){ rd(A[i].a);rd(A[i].h);rd(A[i].k);A[i].h++; } sort(A+1,A+1+m);//按照a排序 for(i=m,j=n,top=0;i>=1;i--){ while(j>=1&&j>=A[i].a){ while(top&&R[stk[top]]<=R[j])top--; stk[++top]=j--; }//top越大 j越小 B[i].r=qry(R,1,top,A[i].h);//找到位置最大的点x 使得R[stk[x]]>=h B[i].h=A[i].h; } for(i=1,j=1,top=0;i<=m;i++){ while(j<=n&&j<=A[i].a){ while(top&&L[stk[top]]<=L[j])top--; stk[++top]=j++; } B[i].l=qry(L,1,top,A[i].h); B[i].c[A[i].k]=1; } sort(B+1,B+1+m); for(i=1;i<=m;i=k){ for(k=i,pre=0;k<=m&&B[k].l==B[i].l&&B[k].r==B[i].r;k=j){// for(j=k+1;j<=m&&B[j]==B[k];j++){//相同的一类合并起来 B[k].c[0]+=B[j].c[0]; B[k].c[1]+=B[j].c[1]; } if(i!=k)ins(pre,k); pre=k; } while(!Q.empty()&&Q.top().r<B[i].l)Q.pop(); if(!Q.empty()){ ins(Q.top().id,i); } else ins(0,i); B[pre].id=pre; Q.push(B[pre]); fa[pre]=i; } dfs(0); printf("%d ",f[0][0]); ec=1; for(i=0;i<=m;i++)B[i].c[0]=B[i].c[1]=0,head[i]=f[i][0]=f[i][1]=0; while(!Q.empty())Q.pop(); } int main(){ // freopen("A.in","r",stdin); // freopen("A.out","w",stdout); int cas; rd(cas); while(cas--){ solve(); } return 0; }
▲16:39:14 模拟赛T2 B题 博弈/二分图关键点/二分图最大匹配 【哈哈哈哈请叫我水神!!!!】
比赛时写了一个暴搜..结果拿了80分..希望运气补药在现在就用完啊~~
正解: 网格图,并且每次移动只能往相邻四个方向动.可以构成一个二分图!!而Alice和Bob走的路会是一条交替路,匹配边和非匹配边交替.假如最终没有增广路,也就是最后一条边是匹配边,那么Bob就会赢.满足这样的条件,必须保证起点是关键点,因此问题就转化成了求整个图的关键点/非关键点.先求出图的最大匹配.然后对于每个非匹配点,它相邻的都是匹配点,设为x,那么x相邻的每个点就是非关键点,然后依次类推,得到所有非关键点即可.
启示:①博弈题或者暴力题,如果想不出正解,在敲暴力时可以尽可能用暴搜(如果实现方便,错误率低),如果加上适当的优化或者break或者return这类的奇技淫巧配上并不走心的数据,说不定会有奇迹发生.状压或者循环这类复杂度稳定,不太可能出奇迹啊~谨记:暴搜出奇迹!!!②网格图是二分图啊!!!
#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; #define fi first #define se second 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=105; char mp[M][M]; int n,m,mark[M*M],id[M][M],pr[M*M],ans=0,head[M*M],to[M*M*8],nxt[M*M*8],ec=1,vis[M*M],clo=0,tot=0; int dx[]={0,1,0,-1},dy[]={1,0,-1,0}; bool chk(int a,int b){ if(a<=0||a>n||b<=0||b>m||mp[a][b]=='#')return false; return true; } bool dfs(int x){ vis[x]=clo; for(int i=head[x];i;i=nxt[i]){ int y=to[i]; if(vis[y]!=clo){ vis[y]=clo; if(!pr[y]||dfs(pr[y])){ pr[y]=x;pr[x]=y;return true; } } } return false; } void DFS(int x){ mark[x]=1; for(int i=head[x];i;i=nxt[i]){ int y=to[i];//相邻的点一定是匹配点 if(!mark[pr[y]])DFS(pr[y]); } } void ins(int a,int b){ to[ec]=b;nxt[ec]=head[a];head[a]=ec++; } void solve(){ int i,j,k,nx,ny; rd(n);rd(m); for(i=1;i<=n;i++)scanf("%s",mp[i]+1); for(i=1;i<=n;i++){ for(j=1;j<=m;j++){ if(mp[i][j]=='#')continue; if(!id[i][j])id[i][j]=++tot; for(k=0;k<4;k++){ if(!chk(nx=i+dx[k],ny=j+dy[k]))continue; if(!id[nx][ny])id[nx][ny]=++tot; ins(id[i][j],id[nx][ny]);//单向的 } } } int match=0,ans=0; for(i=1;i<=tot;i++){//找到一个最大匹配 if(!pr[i]){ ++clo;match+=dfs(i); } } for(i=1;i<=tot;i++){ if(!pr[i]&&!mark[i]){//找到所有非关键点 DFS(i); } } for(i=1;i<=tot;i++)ans+=mark[i]; printf("%d ",ans); for(i=1;i<=n;i++){ for(j=1;j<=m;j++){ if(mark[id[i][j]])printf("%d %d ",i,j); } } } int main(){ // freopen("B.in","r",stdin); // freopen("B.out","w",stdout); solve(); return 0; }
▲20:41:38 模拟赛T3 C题 计算几何/线段树/细节好多
比赛时有一个naive的想法:用线段树维护每个x的最优直线,然后暴力更新..期望得分是50分!但是由于种种原因...没有调出来.正解是在此基础上优化的.
遇到一个区间[l,r],假如这个区间没有线段,就把a放进去,否则就要进行一番比较,推陈出现.设原来的线段为b.可以O(1)求出a,b在[l,r]段的交点,那么一定有一个线段掌控的区间是小于等于[l,r]的一半的!!那么只要把这条线段递归下去更新[l,r]的儿子,而另一条留在这个区间.那么询问时,只要把经过x的logn条直线的值都求出来取最大值即可.
这个思路太神奇啦!!!注意有直线垂直于x轴和直线的左右端点位置不定的情况!!
#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 db double #define ll long long using namespace std; inline void rd(int &res){ res=0;char c;int k=1; while(c=getchar(),c<48&&c!='-'); if(c=='-'){k=-1;c=getchar();} do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); res*=k; } 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(db &x,db y){if(x<y)x=y;} inline void Min(int &x,int y){if(x>y)x=y;} const int M=2e5+5; const db eps=1e-10; struct node{ int l,r; double k,b; double cal(int a){return k*a+b;} }A[M]; double val[M],res; int n,m,f[M<<2]; int replace(int l,int r,int y,int x){//用x代替y db xl=A[x].cal(l),xr=A[x].cal(r); db yl=A[y].cal(l),yr=A[y].cal(r); if((yl-xl>eps||fabs(yl-xl)<eps)&&(yr-xr>eps||(fabs(yr-xr)<eps)))return 0;//不用代替 if(yl-xl<=eps&&yr-xr<=eps)return r;//整个代替 int x0=((A[y].b-A[x].b)/(A[x].k-A[y].k));//在x点相同 if(xl-yl>eps)return x0; return -x0-1;// } void upd(int L,int R,int l,int r,int x,int p){ if(L==l&&R==r){//更新这段线段 if(!f[p]){ f[p]=x;return; } int k=replace(l,r,f[p],x); if(!k)return; int y=f[p],mid=L+R>>1; if(L==R){ f[p]=x;return; } if(k>0){ if(k>mid){ f[p]=x; upd(mid+1,R,mid+1,r,y,p<<1|1); } else upd(L,mid,l,mid,x,p<<1); } else{ k=-k; if(k<=mid){ f[p]=x; upd(L,mid,l,mid,y,p<<1); } else upd(mid+1,R,mid+1,r,x,p<<1|1); } return; } int mid=L+R>>1; if(r<=mid)upd(L,mid,l,r,x,p<<1); else if(l>mid)upd(mid+1,R,l,r,x,p<<1|1); else upd(L,mid,l,mid,x,p<<1),upd(mid+1,R,mid+1,r,x,p<<1|1); } void qry(int L,int R,int x,int p){ if(f[p])res=max(res,A[f[p]].cal(x)); if(L==R)return; int mid=L+R>>1; if(x<=mid) qry(L,mid,x,p<<1); else qry(mid+1,R,x,p<<1|1); } void solve(){ int i,j,k,a,b,c,d,mx=0,op; for(i=1;i<=n;i++){ rd(a);rd(b);rd(c);rd(d); if(c!=a)A[i].k=1.0*(d-b)/(c-a),A[i].b=b-A[i].k*a; else A[i].k=max(b,d);//很大 if(a>c)swap(a,c); A[i].l=a,A[i].r=c; } m+=n; for(i=n+1;i<=m;i++){ rd(op); if(op){//询问 rd(A[i].l);mx=max(A[i].l,mx); A[i].r=1e9; } else{ rd(a);rd(b);rd(c);rd(d);// if(c!=a)A[i].k=1.0*(d-b)/(c-a),A[i].b=b-A[i].k*a; else A[i].k=max(b,d);//很大 if(a>c)swap(a,c); A[i].l=a,A[i].r=c; } } for(i=1;i<=mx;i++)val[i]=-2e9; for(i=1;i<=m;i++){ if(A[i].r<1e9){//插入 if(A[i].l==A[i].r){ Max(val[A[i].l],A[i].k);continue; } A[i].l=max(1,A[i].l); A[i].r=min(A[i].r,mx); if(A[i].l<=A[i].r)upd(1,mx,A[i].l,A[i].r,i,1); } else{ res=(db)(val[A[i].l]); qry(1,mx,A[i].l,1); if(res!=-2e9)printf("%.3lf ",res); else puts("0.000"); } } } int main(){ // freopen("C10.in","r",stdin); // freopen("C.out","w",stdout); rd(n);rd(m); solve(); return 0; }
2017-03-10 周五(146)
▲15:01:23 BZOJ1875 矩阵快速幂+YY 给出一个无向图,起点st,终点en,步数t,求出从st到en走t步并且相邻两步不能走同一条边的方案数.有重边无自环. 没有相邻两步的限制时,就是裸的矩阵快速幂,现在要求相邻两步不能走同一条边,假如每次移动看作是边之间的跳跃,那么条件就是每次不能跳到自己,所以只要把无向边看成两条有向边,假如两条不同的边(a,b)a的终点是b的起点并且a,b不是同一条无向边来的就在这两个边所对应的点之间连边.然后矩阵快速幂就可以了.
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int M=130; const int P=45989; int u[M],v[M],n,m,st,en,step; 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 Add(int &x,int y){ x+=y;if(x>=P)x-=P; } struct node{ int a[M][M]; node operator*(const node &tmp)const{ node c; for(int i=1;i<=m;i++){ for(int j=1;j<=m;j++){ c.a[i][j]=0; for(int k=1;k<=m;k++) Add(c.a[i][j],a[i][k]*tmp.a[k][j]%P); } } return c; } void init(){ for(int i=1;i<=m;i++)a[i][i]=1; } }A,res; void solve(){ int i,j,k,ans=0; rd(n);rd(m);rd(step);rd(st);rd(en);if(st>en)swap(st,en); if(!step){ printf("%d ",st==en);return; } for(i=1;i<=m;i++){ rd(v[i]);rd(u[i]); u[i+m]=v[i],v[i+m]=u[i]; } m*=2; for(i=1;i<=m;i++){ for(j=1;j<=m;j++){ if(u[i]==v[j]&&i+m/2!=j&&j+m/2!=i){ A.a[i][j]++; } } } step--; res.init(); while(step){ if(step&1)res=res*A; A=A*A; step>>=1; }//得到res for(i=1;i<=m;i++){//枚举第一条边 if(v[i]!=st)continue; for(j=1;j<=m;j++){//枚举最后一条边 if(u[j]!=en)continue; Add(ans,res.a[i][j]); } } printf("%d ",ans); } int main(){ // freopen("da.in","r",stdin); // freopen("my.out","w",stdout); solve(); }
2017-03-11 周六(148)
▲14:29:10 模拟赛T1 洗衣服 【辣鸡题目 毁我青春T^T】用堆求出st 和en然后答案就是max{st[i]+en[tot-i+1]}
▲22:09:55 模拟赛T2 编码 2-SAT建模/trie树 【奥妙重重!!!】
暴力做法:
设s[x][j]表示x串问号为j时的串,假如s[x][j]为s[y][i]的前缀,那么s[x][j]->s[y][i^1],s[y][i]->s[x][j^1]时肯定可以确定的.这就满足了2-sat的模型,暴力建边,跑一遍tarjan强连通分解,只要保证x[0]和x[1]不在同一个强连通分量里,就可以满足条件.
由于现在n很大...所以不能暴力建边,就要想别建边方式,使得x[i]能连接到每一个与它有前缀关系的串的同胞上.
首先考虑前缀关系该怎么搞.可以借助trie树,那么两个点是否有前缀关系就可以确定了.对于trie树上的节点t,t的所有儿子都和t是有前缀关系的,所以对于一个串x[i],让它和它在树上对应的节点编号t的儿子连边,这样可以保证得到以自己为前缀的所有情况了.再把每个串的节点t和串的同胞相连.
现在还有长度相同的情况.这个不能直接地把x[i]连向t,这样就会让x[i]能够走向自己的同胞了,这是不合法的.可以构建新点再用链表来跳过自己的同胞.就可以啦.
当然树上父亲要向儿子连边.
[优化]:设有n个串,满足不存在前缀关系的编码串总和至少nlogn,由于串长保证<=5e5,所以n>10w的是肯定无解的.
[注意]:边数和点数要算清楚!! 虽然trie树的空间复杂度是串长总和,但是由于每个串延伸成了两个串,所以大小为两倍!!
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<vector> using namespace std; const int N=(5e5+5);// const int M=N*8;//400w const int E=6000000; int ec=1,n,len,tot=0,head[M],to[E],nxt[E],id[N][2],trie[N*2][2]; int pre[M],suf[M],clo=0,scc=0,m,g[M],dfn[M],low[M],in[M],stk[M],top=0; char s[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){// to[ec]=b;nxt[ec]=head[a];head[a]=ec++; } vector<int>f[N*2];//记录trie树上每个节点对应的串 void add(int a,bool x,int b,bool y){ ins(a<<1|x,b<<1|y); x^=1,y^=1; ins(b<<1|y,a<<1|x); } int getid(){ int cur=0,i,k; for(i=1;i<=len;i++){ k=s[i]-'0'; if(!trie[cur][k])trie[cur][k]=++tot; cur=trie[cur][k]; } return cur; } void dfs(int x){ dfn[x]=low[x]=++clo; in[x]=1; stk[++top]=x; for(int i=head[x];i;i=nxt[i]){ int y=to[i]; if(!dfn[y]){ dfs(y); low[x]=min(low[x],low[y]); } else if(in[y])low[x]=min(low[x],dfn[y]); } if(low[x]==dfn[x]){ g[x]=++scc; in[x]=0; while(top&&stk[top]!=x){ int y=stk[top]; g[y]=scc;top--; in[y]=0; } top--; } } void solve(){ int i,j,k,t,lay=0; rd(n); if(n>1e5){puts("NO");return;} for(i=1;i<=n;i++){ scanf("%s",s+1); len=strlen(s+1); for(t=0,j=1;j<=len;j++){ if(s[j]=='?'){t=j;break;} } if(t){ s[t]='0';id[i][0]=getid(); s[t]='1';id[i][1]=getid(); } else id[i][0]=id[i][1]=getid(); f[id[i][1]].push_back(i<<1|1); f[id[i][0]].push_back(i<<1); } for(i=1;i<=tot;i++){ for(j=0;j<2;j++){ if(trie[i][j])add(i+n,1,trie[i][j]+n,1);//从i到它的一个儿子建边 } } for(i=1;i<=n;i++){ for(j=0;j<2;j++){ for(t=id[i][j],k=0;k<2;k++){ if(trie[t][k])add(i,j,trie[t][k]+n,1); } } } m=tot; tot+=n; for(i=1;i<=m;i++){ int sz=f[i].size(); for(j=0;j<sz;j++)add(i+n,1,f[i][j]>>1,(f[i][j]&1)^1);//插入同胞 if(sz<2)continue; k=++tot; add(tot,1,f[i][0]>>1,(f[i][0]&1)^1); for(j=1;j<sz;j++){ pre[j]=k; add(++tot,1,pre[j],1); add(tot,1,f[i][j]>>1,(f[i][j]&1)^1); k=tot; } k=++tot; suf[sz-1]=0; add(tot,1,f[i][sz-1]>>1,(f[i][sz-1]&1)^1); for(j=sz-2;j>=0;j--){ suf[j]=k; add(++tot,1,suf[j],1); add(tot,1,f[i][j]>>1,(f[i][j]&1)^1); k=tot; } for(j=0;j<sz;j++){ if(pre[j])add(f[i][j]>>1,f[i][j]&1,pre[j],1); if(suf[j])add(f[i][j]>>1,f[i][j]&1,suf[j],1); } } for(i=1;i<=tot;i++){ if(!dfn[i])dfs(i); } for(i=1;i<=tot;i++){ if(g[i<<1]==g[i<<1|1]){ puts("NO"); return; } } puts("YES"); } int main(){ // freopen("code.in","r",stdin);//ans is yes // int size = 64 << 20; // 32MB // char *p = (char*)malloc(size) + size; // __asm__("movl %0, %%esp " :: "r"(p)); solve(); return 0; }
2017-03-13周一(150)
▲14:03:26 模拟赛T3 暴搜出奇迹 我想的就是暴搜只是少了一小小点优化.优化:对于两个点的环,方案是确定的,肯定是讲编号小的设置为左括号.这样就可以将待考虑的点减少为n/4个了.然后暴搜就可以过了.对于点不多的情况,可以考虑暴搜+剪枝.
▲19:07:51 模拟赛T2 把题目转化成求一个区间的后缀的最大LCP.算出sa和h,按照h从大到小合并,首先得到每个串对应的询问是哪几个,用bitset记录.然后合并的过程中对于两个01串进行按位与运算,那么串上的1就是现在可以更新的询问了.为了让每个询问只被更新一遍,再开一个bitset维护当前哪些询问已经被更新,被更新的设为0,让当前准备更新的串和这个串与一下,就可以确定哪些还没被更新.更新时需要一个函数_Find_next(i)表示求i后面的第一个1的位置,也可以手写bitset..我就是手写,结果被卡T了..T_T
这里的空间复杂度是O(n*n/32) 但由于空间只有1024MB所以还是不够,又因为询问之间是没有联系的,可以把询问分成两次求解.以时间换空间.
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<bitset> using namespace std; const int M=1e5+5;// 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(int x){ if(!x)return; pt(x/10); putchar((x%10)^48); } void sc(int x){ if(x)pt(x); else putchar('0'); putchar(' '); } struct node{ int l,r,id; bool operator<(const node &tmp)const{ if(l!=tmp.l)return l<tmp.l; return id<tmp.id; } }q[M<<1]; #define ui unsigned int ui FUL=(1ll<<32)-1,ful=(1<<16)-1,mp[1<<16]; int len,fa[M],res[M],A[M],n,m,mm,h[M],sa[M],rk[M];//只需要这三样就够了 char s[M]; struct Bitset{ ui a[1567];//50000位/32 需要几个数字 //[0,31][32,63]...... void set(int p){//第p位设为1 a[p>>5]|=1<<(p&31); } void reset(int p){//第p位设为1 a[p>>5]&=FUL^(1<<(p&31)); } Bitset operator&(const Bitset &y)const{ Bitset z; for(int i=0;i<=len;++i)z.a[i]=a[i]&y.a[i]; return z; } Bitset operator|(const Bitset &y)const{ Bitset z; for(int i=0;i<=len;++i)z.a[i]=a[i]|y.a[i]; return z; } }f[M],tmp,done; void upd(Bitset t,int val){ for(int i=0,j=0;i<=len;++i){ ui x=t.a[i]&ful; while(x){//0~15 ui y=x&-x; res[mp[y]+j]=val; done.reset(mp[y]+j); x-=y; } j+=16; x=t.a[i]>>16; while(x){//[16~31] ui y=x&-x; res[mp[y]+j]=val; done.reset(mp[y]+j); x-=y; } j+=16; } } bool cmp(int a,int b){ if(h[a]!=h[b])return h[a]>h[b]; return a<b;// h一样的 谁小谁先 } namespace SuffixArray{ int cnt[M],x[M],y[M]; bool cmp(int a,int b,int d){ if(rk[a]!=rk[b])return 1; int a1=0,b1=0; if(a+d<=n)a1=rk[a+d]; if(b+d<=n)b1=rk[b+d]; return a1!=b1; } void solve(){ int i,j,k,t,d,m; for(i=0;i<=n;++i)cnt[i]=0; for(i=1;i<=n;++i)++cnt[s[i]-'0'+1]; cnt[2]+=cnt[1]; for(i=n;i>=1;--i)sa[cnt[s[i]-'0'+1]--]=i; rk[sa[1]]=1; for(i=2;i<=n;++i)rk[sa[i]]=rk[sa[i-1]]+((s[sa[i]]==s[sa[i-1]])?0:1); // for(i=1;i<=n;i++)printf("%d ",rk[i]); // for(i=1;i<=n;i++)printf("%d %d ",rk[i],sa[i]); m=rk[sa[n]]; // for(i=1;i<=n;i++)printf("%d %d ",rk[i],sa[i]); for(d=1;m<n&&d<=n;d<<=1){ for(t=0,i=n-d+1;i<=n;++i)x[++t]=i; for(i=1;i<=n;++i)if(sa[i]>d)x[++t]=sa[i]-d; for(i=1;i<=m;++i)cnt[i]=0; for(i=1;i<=n;++i)++cnt[rk[i]]; for(i=1;i<=m;++i)cnt[i]+=cnt[i-1]; for(i=n;i>=1;--i)sa[cnt[rk[x[i]]]--]=x[i]; y[sa[1]]=1; for(i=2;i<=n;++i)y[sa[i]]=y[sa[i-1]]+(cmp(sa[i],sa[i-1],d)); for(i=1;i<=n;++i)rk[i]=y[i]; m=rk[sa[n]]; } for(i=1,k=0;i<=n;++i){ if(rk[i]==1){h[1]=1e9;continue;} if(k)k--; j=sa[rk[i]-1]; for(;s[i+k]==s[j+k];k++); h[rk[i]]=k; } } } int get(int x){ if(fa[x]!=x)return fa[x]=get(fa[x]); return x; } void pre_work(){ int i; for(i=0;i<16;++i)mp[1<<i]=i; rd(n);rd(mm); scanf("%s",s+1); s[n+1]='0'-1; for(i=1;i<=(n>>1);++i)swap(s[i],s[n-i+1]); SuffixArray::solve();//deal about sa rk h h[1]=oo 因为它不影响值 所以在最前面就可以插入 if(mm>50000)m=mm>>1; else m=mm; } void solve(){ int i,j,k,op=m*2,x,y,z,up=0,now; len=m/32;//第len位为[len*32,len*32-1] for(i=1;i<=m;++i){//先读入一半 解决一半 输出一半 rd(q[i].l);rd(q[i].r); q[i].l=n-q[i].l+1; q[i].r=n-q[i].r+1;swap(q[i].l,q[i].r); q[i].id=i; q[i+m].l=q[i].r+1; q[i+m].id=-i; done.set(i);tmp.reset(i);//这里有初始化 } sort(q+1,q+1+op); for(i=1,j=1;i<=n;++i){//枚举的是后缀的位置 for(;j<=op&&q[j].l<=i;j++){ if(q[j].id<0)tmp.reset(-q[j].id); else tmp.set(q[j].id); } f[rk[i]]=tmp; fa[i]=A[i]=i; }//需要按照h值从大到小排序 sort(A+1,A+1+n,cmp); for(i=2;i<=n;++i){ x=A[i]; tmp=f[x]&done&f[x-1]; upd(tmp,h[x]); if(h[x-1]>=h[x]){ y=get(x-1); tmp=f[y-1]|f[y]; tmp=tmp&f[x]&done;//得到当前可以更新的东西 fa[x]=y; f[y]=f[y]|f[x]; upd(tmp,h[x]); // printf("%d %d %d %d ",i,x,h[x],res[1]); } // printf("afy %d %d %d %d ",i,x,h[x],res[1]); // f[x]=f[x]|f[x-1]; if(x+1<=n&&h[x+1]>h[x]){ y=get(x+1); z=fa[y]=get(x); // if(x==4)printf("%d %d ",y,z); tmp=f[z-1]|f[z]; tmp=tmp&done&f[y]; f[z]=f[z]|f[y]; upd(tmp,h[x]); } // printf("%d %d %d ",x,h[x],res[1]); } for(i=1;i<=m;++i)sc(res[i]); } int main(){ // freopen("A.in","r",stdin); // freopen("A.out","w",stdout); pre_work(); solve(); if(mm>50000)m=mm-m,solve(); return 0; }
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<bitset> using namespace std; const int M=1e5+5;// 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(int x){ if(!x)return; pt(x/10); putchar((x%10)^48); } void sc(int x){ if(x)pt(x); else putchar('0'); putchar(' '); } struct node{ int l,r,id; bool operator<(const node &tmp)const{ if(l!=tmp.l)return l<tmp.l; return id<tmp.id; } }q[M<<1]; typedef bitset<M/2> Bs; int len,fa[M],res[M],A[M],n,m,mm,h[M],sa[M],rk[M];//只需要这三样就够了 char s[M]; Bs f[M],tmp,done; int END=50002; void upd(Bs t,int val){ for(int i=t._Find_next(0);i!=END;i=t._Find_next(i)){ res[i]=val;done[i]=0; } } bool cmp(int a,int b){ if(h[a]!=h[b])return h[a]>h[b]; return a<b;// h一样的 谁小谁先 } namespace SuffixArray{ int cnt[M],x[M],y[M]; bool cmp(int a,int b,int d){ if(rk[a]!=rk[b])return 1; int a1=0,b1=0; if(a+d<=n)a1=rk[a+d]; if(b+d<=n)b1=rk[b+d]; return a1!=b1; } void solve(){ int i,j,k,t,d,m; for(i=0;i<=n;++i)cnt[i]=0; for(i=1;i<=n;++i)cnt[s[i]-'0'+1]++; cnt[2]+=cnt[1]; for(i=n;i>=1;--i)sa[cnt[s[i]-'0'+1]--]=i; rk[sa[1]]=1; for(i=2;i<=n;++i)rk[sa[i]]=rk[sa[i-1]]+((s[sa[i]]==s[sa[i-1]])?0:1); // for(i=1;i<=n;++i)printf("%d ",rk[i]); // for(i=1;i<=n;++i)printf("%d %d ",rk[i],sa[i]); m=rk[sa[n]]; // for(i=1;i<=n;++i)printf("%d %d ",rk[i],sa[i]); for(d=1;m<n&&d<=n;d<<=1){ for(t=0,i=n-d+1;i<=n;++i)x[++t]=i; for(i=1;i<=n;++i)if(sa[i]>d)x[++t]=sa[i]-d; for(i=1;i<=m;++i)cnt[i]=0; for(i=1;i<=n;++i)cnt[rk[i]]++; for(i=1;i<=m;++i)cnt[i]+=cnt[i-1]; for(i=n;i>=1;--i)sa[cnt[rk[x[i]]]--]=x[i]; y[sa[1]]=1; for(i=2;i<=n;++i)y[sa[i]]=y[sa[i-1]]+(cmp(sa[i],sa[i-1],d)); for(i=1;i<=n;++i)rk[i]=y[i]; m=rk[sa[n]]; } for(i=1,k=0;i<=n;++i){ if(rk[i]==1){h[1]=1e9;continue;} if(k)k--; j=sa[rk[i]-1]; for(;s[i+k]==s[j+k];k++); h[rk[i]]=k; } } } int get(int x){ if(fa[x]!=x)return fa[x]=get(fa[x]); return x; } void pre_work(){ int i,j,k; rd(n);rd(mm); scanf("%s",s+1); s[n+1]='0'-1; for(i=1;i<=(n>>1);++i)swap(s[i],s[n-i+1]); SuffixArray::solve();//deal about sa rk h h[1]=oo 因为它不影响值 所以在最前面就可以插入 if(mm>50000) m=mm>>1; else m=mm; } void solve(){ int i,j,k,op=m*2,x,y,z; for(i=1;i<=m;++i){//先读入一半 解决一半 输出一半 rd(q[i].l);rd(q[i].r); q[i].l=n-q[i].l+1; q[i].r=n-q[i].r+1;swap(q[i].l,q[i].r); q[i].id=i; q[i+m].l=q[i].r+1; q[i+m].id=-i; done.set(i);tmp.reset(i);//这里有初始化 } sort(q+1,q+1+op); for(i=1,j=1;i<=n;++i){//枚举的是后缀的位置 for(;j<=op&&q[j].l<=i;++j){ if(q[j].id<0)tmp.reset(-q[j].id); else tmp.set(q[j].id); } f[rk[i]]=tmp; fa[i]=A[i]=i; }//需要按照h值从大到小排序 sort(A+1,A+1+n,cmp); for(i=2;i<=n;++i){ x=A[i]; tmp=f[x]&done&f[x-1]; upd(tmp,h[x]); if(h[x-1]>=h[x]){ y=get(x-1); tmp=f[y-1]|f[y]; tmp=tmp&f[x]&done;//得到当前可以更新的东西 fa[x]=y; f[y]=f[y]|f[x]; upd(tmp,h[x]); // printf("%d %d %d %d ",i,x,h[x],res[1]); } // printf("afy %d %d %d %d ",i,x,h[x],res[1]); // f[x]=f[x]|f[x-1]; if(x+1<=n&&h[x+1]>h[x]){ y=get(x+1); z=fa[y]=get(x); // if(x==4)printf("%d %d ",y,z); tmp=f[z-1]|f[z]; tmp=tmp&done&f[y]; f[z]=f[z]|f[y]; upd(tmp,h[x]); } // printf("%d %d %d ",x,h[x],res[1]); } for(i=1;i<=m;++i)sc(res[i]); } int main(){ pre_work(); solve(); if(mm>50000)m=mm-m,solve(); return 0; }
2017-03-14周二(152)
▲09:29:06 模拟赛T2 树的重心/计数排序 题意:给定一棵树,对于每个点求至少删掉多少边(还有加上对应数量的边)能使得点i其他所有点的距离和最短.
这题最重要的是关于重心的性质及判定:
1)重心到其他所有点的距离和最短
2)重心的最大子树最小
3)重心的每个儿子大小都<=n/2
只要满足上面的任何条件就一定是重心,重心一定会满足上面的所有条件.
因此可以把题目中的条件转化成,最少删掉多少条边,能使得点i的最大子树<=n/2.
首先找到原树的重心rt,把rt作为根遍历一遍树.
对于节点i!=重心:可以确定当前i的所有儿子大小都一定满足<=n/2,所以不可能删除i子树内的边.而且当前若以i作为根,最大的子树一定是i连向父亲的这个子树.为了减少这个子树的大小,肯定要将重心和它的儿子的边删掉,再把每个重心的儿子直接连向i,因为重心,所以满足它的每个儿子都<=n/2,所以直接连向i肯定没问题,那么最后就剩下重心所在的联通块,要让这个块也<=n/2.现在当要删的边删掉之后有两种情况:①i和重心之间还是相连,那么重心所在的这个子树大小必须<=n/2,假设重心删掉的儿子个数为t,删掉的儿子的sz之和为sum,这个子树大小就是n-sum-sz[i],必须<=n/2. ②i和重心之间没有边相连了,那么只要保证重心所在的块大小<=n/2即可,只要贪心求出重心删掉几个儿子能让自己的块大小<=n/2即可.对于情况2,假设这个最少儿子数为ans,那么对于所有的点ans是肯定可以满足的,但是是否会出现情况1让答案更小呢?首先情况1的答案不可能<ans-1.(已证) 所以就考虑答案是否能为ans-1,为了让n-sum[ans-1]-sz[i]尽可能<=n/2,肯定是让sum[ans-1]取最大,肯定要求出重心的前ans-1大的儿子的sz和(并且不包括i所在的儿子)就可以了
只要用计数排序得到这个序列就可以了.复杂度为O(n).
这道题最关键的是得到重心的性质和终态枚举的想法.考虑最后的答案的形态和来源还有贪心和计数排序的优化.
▲22:11:47 BZOJ2298 DP/转化 把比我大ai个人,比我小bi个人看做"和我同分的人有xxx个",然后问题就变成了每个人表示一个区间,问最多有多少个不相交的区间.DP+BIT.
2017-03-15周三(154)
▲09:26:47 BZOJ1190 背包DP 根据每个物品重量为a*2^b,可以发现对于一个物品进行转移只要知道当前剩余的重量是2^bi的多少倍,而不用知道零头.所以可以分阶段决策:对于每个bi分别考虑.
#include<cstdio> #include<algorithm> using namespace std; const int M=105; int S=30,A[M],f[M][2005],n,v[M],a[M],b[M],w; bool cmp(int x,int y){ return b[x]>b[y];//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 Max(int &x,int y){if(x<y)x=y;} void solve(){ int i,j,k,x,res=0; for(i=1;i<=n;i++){ rd(x);rd(v[i]); for(j=S;j>=0;j--){ if(x%(1<<j)==0){ a[i]=x>>j; b[i]=j;break; } } A[i]=i; } sort(A+1,A+1+n,cmp); int y,z,d; for(i=1;i<=n;i=j){ int t=A[i],s=0; y=(1<<b[t]),z=1<<b[A[i-1]],d=b[A[i-1]]-b[t]; if(i==1)d=0; for(j=(x=min(1000,w/y));j>=0;j--){//j>>d 和 j&(1<<d-1) 两个部分 if(w%z>=y*(j&((1<<d)-1))){ f[i][j]=f[i-1][j>>d]; }//有可能上一个不是b[t]+1 else f[i][j]=f[i-1][(j>>d)+1]; } for(k=0;k+a[t]<=x;k++){ Max(f[i][k],f[i][k+a[t]]+v[t]); Max(res,f[i][k]); // if(i<16&&f[i][k]>590474)printf("%d %d %d %d ",t,i,k,f[i][k]); } for(j=i+1;j<=n&&b[A[j]]==b[t];j++){ for(k=0;k<=x;k++){ f[j][k]=f[j-1][k]; if(k+a[A[j]]<=x)Max(f[j][k],f[j-1][k+a[A[j]]]+v[A[j]]); Max(res,f[j][k]); } } } printf("%d ",res); } int main(){ // freopen("da.in","r",stdin); while(scanf("%d %d",&n,&w)&&~n){ solve(); // break; } return 0; }
Dp的时候记录状态只需要确定和自己转移有关的信息,多余的信息就不需要记录了.因此可以将信息相似的放在一起.
▲13:48:42 BZOJ4771 七彩树 主席树+小想法 【一道清新的题】给定一棵有根树,每个节点有一个颜色c,给出m个询问(x,d)求x子树内深度不超过dep[x]+d的点中有多少种不同的颜色.强制在线.
先考虑一下简单的变形:
1)假如 不强制在线 可以把所有询问根据dep排序,从小到大.现在问题就变成了求一个区间内的不同颜色个数.把相同dep的询问根据x的字典序从小到大,考虑等于D的询问,把dep=D的点根据dfs序大小顺序更新,更新时找到上一个颜色c出现的位置,然后删掉,把当前的位置+1.可用BIT维护.
2)假如 询问求一个子树内有多少不同的颜色:更新每个点,考虑如何去掉相同颜色的影响.假如当前已经维护好了每个子树的颜色数量,一个颜色为c的点加入,会导致原来包含颜色c的所有点的答案+1,只要找到当前颜色c的点中 和x的LCA深度最大的点y,把y的dfs序对应位置-1即可.至于y,y肯定是x在dfs序列上相邻的两个点中的一个.
现在把两者结合起来:
考虑每一层dep的更新,dep+1与dep之间的影响就是更新了深度为dep+1的这一些点,只要用主席树维护这些变化,并且用set来查找每个点x对应的y即可.
#include<cstdio> #include<cstring> #include<algorithm> #include<set> #include<iostream> using namespace std; /*memory 256MB 6400w*/ const int M=1e5+5,S=18,Sz=80;//每一层更新两个点 logn*2 int D,tot=0,v[M],dep[M],c[M],A[M],clo=0,R[M],L[M],rt[M],pre[M],fa[M][S],n,m,mp[1<<S];//2600w int ec=1,head[M],to[M],nxt[M]; set<int>st[M]; set<int>:: iterator it; 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(' '); } struct ChmT{//主席树 int ls[M*Sz],rs[M*Sz],sum[M*Sz]; void clear(){ for(int i=0;i<=tot;i++)ls[i]=rs[i]=sum[i]=0; tot=0; } void build(int &id,int l,int r){ id=++tot; if(l==r)return; int mid=l+r>>1; build(ls[id],l,mid); build(rs[id],mid+1,r); } void upd(int id,int od,int l,int r,int x,int v){ sum[id]+=v; if(l==r)return; int mid=l+r>>1; if(!ls[id])ls[id]=ls[od]; if(!rs[id])rs[id]=rs[od]; if(x<=mid){ if(ls[id]==ls[od]){ ls[id]=++tot; sum[ls[id]]=sum[ls[od]]; } upd(ls[id],ls[od],l,mid,x,v); } else{ if(rs[id]==rs[od]){ rs[id]=++tot; sum[rs[id]]=sum[rs[od]]; } upd(rs[id],rs[od],mid+1,r,x,v); } } int qry(int p,int L,int R,int l,int r){ // printf("%d %d %d %d %d ",p,L,R,l,r); if(l==L&&R==r)return sum[p]; int mid=L+R>>1; if(r<=mid)return qry(ls[p],L,mid,l,r); else if(l>mid)return qry(rs[p],mid+1,R,l,r); else return qry(ls[p],L,mid,l,mid)+qry(rs[p],mid+1,R,mid+1,r); } }T; bool cmp(int a,int b){ if(dep[a]!=dep[b])return dep[a]<dep[b]; return L[a]<L[b]; } void ins(int a,int b){ to[ec]=b;nxt[ec]=head[a];head[a]=ec++; } void dfs(int x){ L[x]=++clo; v[clo]=x; for(int i=head[x];i;i=nxt[i]){ int y=to[i]; dep[y]=dep[x]+1; dfs(y); } A[x]=x; R[x]=clo; } void Up(int &a,int step){ while(step){ int k=step&-step; a=fa[a][mp[k]]; step-=k; } } int LCA(int a,int b){ if(dep[a]<dep[b])swap(a,b); Up(a,dep[a]-dep[b]); if(a==b)return a; int i; for(i=S-1;i>=0;i--){ if(fa[a][i]!=fa[b][i])a=fa[a][i],b=fa[b][i]; } return fa[a][0]; } void solve(){ int i,j,k,x,y,d,z; rd(n);rd(m); for(i=1;i<=n;i++)rd(c[i]); for(i=2;i<=n;i++){ rd(fa[i][0]);ins(fa[i][0],i); } dep[1]=1; dfs(1);//得到dfs序和dep for(j=1;j<S;j++){ for(i=1;i<=n;i++)fa[i][j]=fa[fa[i][j-1]][j-1]; } sort(A+1,A+1+n,cmp); D=dep[A[n]];//记录最大深度 T.build(rt[0],1,n); for(i=1,j=1;i<=D;i++){ //先构出dep=i这一层的东西,然后再往上更新 rt[i]=++tot; T.sum[tot]=T.sum[rt[i-1]]; for(;j<=n&&dep[A[j]]==i;j++){ x=A[j];y=z=0; if(st[c[x]].size()){ it=st[c[x]].lower_bound(L[x]); if(it!=st[c[x]].end())y=LCA(x,v[*it]); if(it!=st[c[x]].begin())it--,z=LCA(x,v[*it]); if(y&&dep[y]>=dep[z])T.upd(rt[i],rt[i-1],1,n,L[y],-1); else if(z&&dep[z]>=dep[y])T.upd(rt[i],rt[i-1],1,n,L[z],-1); } T.upd(rt[i],rt[i-1],1,n,L[x],1); st[c[x]].insert(L[x]); } } int ans=0; while(m--){ rd(x);rd(d);// x^=ans,d^=ans; d=min(D,d+dep[x]); ans=T.qry(rt[d],1,n,L[x],R[x]); sc(ans); } ec=1;clo=0; for(i=1;i<=n;i++)head[i]=pre[i]=0,st[i].clear(); T.clear(); } int main(){ // freopen(".in","r",stdin); // freopen("my.out","w",stdout); int cas; for(int i=0;i<S;i++)mp[1<<i]=i; rd(cas); while(cas--)solve(); return 0; }
错误分析:找y的地方出错了,直接找到一个<L[x]的最大的y,忽略了y的dfs序可能是>L[x]的.
2017-03-16周四(156)
▲09:12:37 模拟赛T1 60分枚举+二分 对于"路径上最大值最小"问题可以考虑二分,对于两维的情况可以考虑枚举一维,二分另一维.复杂度为O(n^4logn).换个思路,假如枚举一维a,相当于询问每次加入一些边之后询问当前使得图为一个强连通分量时的最小b.首先考虑如何转化"互相抵达"这个条件.这个条件可以转化为每个点都能到达1,1能到达每个点,那肯定就能满足所有点之间互相到达了.现在求路径中的b的最大值的最小值,可以考虑floyd,设f[i][j]为从i到达j路径中最大值的最小值.转移为f[i][j]=min{max(f[i][k],f[k][j])}.每次加入一条边,暴力做法就是N^3再求一遍floyd,考虑优化.每次只添加一条边,那么它能影响的路径一定是经过这条边的路径,所以这样更新即可 f[i][j]=min{max(w,f[i][x],f[y][j])}.每次只要n^2更新即可,复杂度为O(n^4),对于n=150的情况是不是很不可思议..没错,所以要卡常数!!!!只要用指针,并且把高位数组改为低位数组即可.
#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==-1||x>y)x=y;} const int M=155; const int oo=2e9+5; struct node{ int a,b,x,y; bool operator<(const node &tmp)const{ return a<tmp.a; } }eb[M*M];//一共有n^2条边 满足要求至少要n条边 int m,n,cnt=0,ea[M*M],f[M*M];//只要判断scc个数即可 void solve(){ int i,j,k,a,b,c,t=0,ans,x,y,z,res=2e9; rd(n); for(i=0;i<n;i++){ for(j=0;j<n;j++){ rd(a); if(i==j)continue; ea[t++]=a; } } for(i=t=a=0;i<n;i++){ for(j=0;j<n;j++){ rd(b); f[a++]=oo; if(i==j){f[a-1]=0;continue;}//i==j f[i][j]=0 eb[t++]=(node){ea[t-1],b,i,j}; } } m=t; sort(eb,eb+m); for(i=0;i<m;i++){ x=eb[i].x,y=eb[i].y,z=eb[i].b,ans=-1; int tmp,*p1=f,*p2=f+x,*p3=f+y*n; for(j=0;j<n;j++,p2+=n){ for(p3=f+y*n,k=0;k<n;k++,p3++){ tmp=z; if(*p2>tmp)tmp=*p2; if(*p3>tmp)tmp=*p3; if(tmp<*p1)*p1=tmp; ++p1; } } p1=f;p2=f; for(j=0;j<n;j++){ Max(ans,*p1),Max(ans,*p2); p2+=n,++p1; } if(ans<oo){ Min(res,ans+eb[i].a); } } printf("%d ",res); } int main(){ // freopen("shuiti.in","r",stdin); // freopen("shuiti.out","w",stdout); // int cas; // rd(cas); // while(cas--){ solve(); // } return 0; }
▲22:07:13 模拟赛T2 线性基/YY "最大异或和/异或和为0 "这类关键词可以利用线性基来解决.假设现在有n个数,len为log数字的大小.那么这个集合的线性基的子集的异或和值域和原来集合的子集的异或和值域是相同的.具体做法看代码.
这样可以把对象的个数缩小为log数字级别的.所以这道题的暴力30分,对于任何一个边集都是割集,所以直接求线性基得到一个集合的最大异或和.
对于一般情况,这个思路就极巧妙~一个割集有什么特点:一条边的两个端点分别在不同的点集中,如果它们的同一个点集中就不产生影响!这个看起来有点眼熟,就是异或的性质,两个相同的数异或值等于0,相当于抵消了!!只要让一条边的两个端点带着这一条边的权值,那么只要两个端点在一个集合中或者都不在一个集合中,这条边都是不贡献的.所以问题就这样转化成了,存在更新,选出一个点集求它们的最大异或和.暴力做法有20分:每次更新操作就暴力求一遍线性基.
优化:每次加入一条边,只更新了两个点.考虑更新一个点x:回想一下求线性基的过程,对于每个点求一遍..只要能够将状态回到没有考虑x点时,就可以仅将x插入线性基.所以问题就是如何将当前的状态恢复,也就是如何把x的贡献去掉.可以记录每个val[i]是经过哪些点异或得到的.如果其中有x,那么就要去掉x的贡献,但是如果仅仅xor val[x],那么线性基的结构就会破坏,只要得到由x异或得到的点中第一个1位置最小的点或者val为0的点y.将每个经过x点异或得到的val都异或一下val[t],这样可以消除x的影响,也可以维护每个元素第一个1的性质.然后再把val[y]的值异或当前要更新的值,也就是边权值,再插入线性基即可.过程用bitset来优化.
线性基扩展:
1)求一个集合的第K小的异或和. 得到线性基,并且变形一下,对于h[i]=最高位为i的数字.
对于所有j>i,假如h[j]的第i位也是1,则h[j]^=h[i].这样保证每一位只存在在一个数字中,这样可以统计出所有可得到的异或和的方案了.
2)判断一个集合的数能否异或和为0.
求出线性基,判断线性基的大小和原集合的大小是否相等,如果相等则无法异或和为0.
3)性质:线性基的任何一个子集异或和!=0.
for(e=1;e<=m;e++){ rd(k);rd(k); scanf("%s",s); len=strlen(s); Max(mx,len); for(i=0;i<len;i++)f[e][i]=s[len-i-1]-'0'; for(i=len-1;i>=0;i--){ if(!f[e][i])continue; if(!h[i]){h[i]=e;break;} else f[e]=f[e]^f[h[i]]; } ans.reset(); for(i=mx-1;i>=0;i--){ if(cmp(ans^f[h[i]],ans))ans=ans^f[h[i]]; } pt(ans); }
2017-03-18周六(158)
▲10:18:45 ZJOI2016小星星【能为如此简单粗暴的题意架上如此可爱诗意的情景也是不容易】 容斥+DP 20分暴力:枚举n的阶乘 直接chk即可 20分链:相当于求原图中存在多少条长度为n的链,直接搜是不行滴,遇到完全图就会挂,因为n较小,可以状态dp,f[i][j]表示i点开始,当前已经有j点走过了的,长度为n的链.直接计划搜索即可.
正解:考虑一般的做法,肯定是考虑树上的节点x和图上的点如何匹配的问题,所以可以这样定义状态:f[i][s]表示树上节点i的子树对应原图上的s点集.转移时枚举子集即可.但是会T掉.我们可以不考虑这么细,点不一定要一一对应,可以树上的很多点对应图上的一个点,来满足对应的边的关系.这样DP就很好求了,但是这样求出的答案肯定不是正确答案,因为不是一一对应的关系,那么我们考虑去掉错误的答案,我们只想知道n个点对应n个点的方案数,但是把n个点对应1,2,3,.....n个点的方案数都求出来了,所以只要分别减去对应1,2,3...n-1个点的方案数,那么就可以利用容斥,枚举具体对应哪些点集,然后计算对答案的贡献即可.
#include<cstdio> #include<cstring> #include<iostream> #include<cmath> #include<algorithm> #define ll long long using namespace std; const int M=18*18; int head[M],to[M],ful,nxt[M],ec=1,n,m,u[M],v[M]; ll f[18][1<<18];//<1000w int 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){ to[ec]=b;nxt[ec]=head[a];head[a]=ec++; to[ec]=a;nxt[ec]=head[b];head[b]=ec++; } ll DP(int x,int msk){ if(~f[x][msk])return f[x][msk]; ll &res=f[x][msk],i,j,k; res=0; for(i=head[x];i;i=nxt[i]){ if(msk&(1<<to[i]-1))continue;//已经用了 res+=DP(to[i],msk|(1<<x-1)); } if((msk|(1<<x-1))==ful)res=1; return res; } void solve(){ ll res=0; memset(f,-1,sizeof(f)); int i,j,k,a,b; ful=(1<<n)-1; for(i=1;i<=m;i++){ rd(a);rd(b); ins(a,b);//双向边 } for(i=1;i<n;i++){ rd(a);rd(b); } for(i=1;i<=n;i++)res+=DP(i,0); cout<<res<<endl; }//dp[i][j]表示起点为i,已经用的点集为j的 能延伸为整个链的个数 namespace BF{ int A[M],e[15][15]; bool chk(){ for(int i=1;i<n;i++){ int x=A[u[i]],y=A[v[i]]; if(!e[x][y])return false; } return true; } void solve(){ int i,j,k,a,b,ans=0; memset(e,0,sizeof(e)); for(i=1;i<=m;i++){ rd(a);rd(b); e[a][b]=e[b][a]=1; } for(i=1;i<n;i++){ rd(u[i]);rd(v[i]); } for(i=1;i<=n;i++)A[i]=i; do{ if(chk())ans++; }while(next_permutation(A+1,A+1+n)); printf("%d ",ans); } } int main(){ // freopen("star.in","r",stdin); rd(n);rd(m); if(n<=10)BF::solve(); else solve(); return 0; }
▲22:30:51 模拟赛T2 贪心+想法 每次进行一次操作之后,每一位上1的个数是不变的,而且经过操作可以将1尽可能地集中起来,而且这样是会让结果更大的,所以只要记录每一位上有几个1,然后贪心得到每个数字即可.
2017-03-19 周日(160)
▲10:22:13 模拟赛T1 凸包/ 【两行代码就是40分啊!!!!!!!(ಥ _ ಥ)】 找到点1到每个节点的路径,每条路径在t时刻的长度可以表示为dis+e*t,也就是一个一次函数的形式,只要我们实时维护函数的凸包就好了.
错误的地方: 在后退时 有可能是代替i的全部,但时又不能取代i-1的任何部分,也就是和i的交点在i的范围之前,但和i-1的交点在范围之后,这种情况就考虑落掉了!而且对拍的数据太水了(><)所以没有发现错误.
啊要是把这种状况判一下就能过了.
#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 N=2505; const int M=10005; const int P=1e9+7; const int oo=2e9+5;//数组可以开12800w 1.2亿 struct node{ int nxt,dis,e;//表示到达nxt 的路径长度和边数 }; int n,m,T,to[M],nxt[M],w[M],head[N],ec=1,ans=0; int L[M],R[M],seq[M],val[M];//记录前缀和以及对应的 void Add(int &x,int y){ x+=y; if(x>=P)x-=P; } void ins(int a,int b,int c){ to[ec]=b;nxt[ec]=head[a];w[ec]=c;head[a]=ec++; to[ec]=a;nxt[ec]=head[b];w[ec]=c;head[b]=ec++; } queue<node>Q; namespace PLay{ int hd[N],ecnt; node p[2000000];//相当于 3000w int bool Ins(node y){ int x=y.nxt;//后面来的e肯定大于等于 if(!hd[x]||y.dis<p[hd[x]].dis){ if(!hd[x]||y.e!=p[hd[x]].e){ y.nxt=hd[x]; p[ecnt]=y; hd[x]=ecnt++; } else p[hd[x]].dis=y.dis;//相当于把上一个删掉了 ,把当前的较短的替换进去 return true; } return false; } void init(){ for(int i=0;i<=n;i++)hd[i]=0; ecnt=1; } int calc(node a,node b){//求出交点 位置t int t=(double)(b.dis-a.dis)*1.0/(a.e-b.e);//e相减是不可能等于0的 return t; } int sig(int x){return 1ll*x*(x+1)/2%P;} int calcval(int id,int l,int r){ int val=1ll*p[id].dis*(r-l+1)%P,d=sig(r)-sig(l-1); if(d<0)d+=P; Add(val,1ll*p[id].e*d%P); return val; } void findpos(int id,int &tot,int &pre){ int t; for(;tot>=1;tot--){ t=calc(p[seq[tot]],p[id])+1;//开始的位置 pre=t; if(t>R[tot])break; Add(ans,-val[tot]+P); if(t<L[tot])continue; if(t-1>=L[tot]){ R[tot]=t-1; val[tot]=calcval(seq[tot],L[tot],R[tot]); Add(ans,val[tot]); } else tot--; break; } } void work(int x){ int i,j,k,pre=0,lst=hd[x],tot=0; for(i=p[lst].nxt;i;i=p[i].nxt){//枚举每一个信息 int t=calc(p[lst],p[i]); if(pre<=T&&t>=pre){ //(t-pre+1)*dis + (t*(t+1)/2-(pre-1)*pre/2)*e t=min(T,t); val[++tot]=calcval(lst,pre,t); Add(ans,val[tot]); seq[tot]=lst; L[tot]=pre; R[tot]=t; pre=t+1; }//有可能pre=T+1, t和T取min了之后等于t了 但是其实 else if(t<pre)findpos(i,tot,pre);//pre表示i这个东西 开始的位置 lst=i; } if(T>=pre)Add(ans,calcval(lst,pre,T)); } void solve(){ int i,j,k,a,b,c,x,y,dis,e; node now=(node){1,0,0}; for(i=1;i<=m;i++){ rd(a);rd(b);rd(c); ins(a,b,c);//双向边 } init(); Q.push((node){1,0,0}); while(!Q.empty()){//原图的dis不会超过int now=Q.front();Q.pop(); x=now.nxt; dis=now.dis,e=now.e; for(i=head[x];i;i=nxt[i]){//往下走 y=to[i]; node tmp=(node){y,dis+w[i],e+1}; if(Ins(tmp))Q.push(tmp); } } for(i=2;i<=n;i++)work(i);//处理到达i点的情况 printf("%d ",ans); } } int main(){ // freopen("A.in","r",stdin); // freopen("A.out","w",stdout); rd(n);rd(m);rd(T); PLay::solve(); }
▲18:36:31 模拟赛T2 概率DP 把最后要求的期望移动距离拆开,考虑从x点出发的路程的贡献.因为到了x点后到达每个点的概率是相等的.所以可以统一计算出x点到达每个点的距离和.现在问题就转化成了求x点会被到达几次.
2017-03-27 周一(161)
▲19:22:08 BZOJ1023 求仙人掌直径 tarjan+树形DP+单调队列
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> using namespace std; const int M=1e5+5; int dfn[M],low[M],dep[M],f[M],fa[M],head[M],to[M<<1],nxt[M<<1]; int ec=1,clo=0,ans=0,n,m; int q[M],a[M],b[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); } inline void Max(int &x,int y){if(x<y)x=y;} inline void Min(int &x,int y){if(x>y)x=y;} 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++; } void Dp(int x,int y){ int l=1,r=1,i,len; n=dep[y]-dep[x];//一共有这么多个点 len=n>>1; for(i=n;i>=0;i--,y=fa[y]){ a[i]=f[y],b[i]=f[y]+i; } q[l]=0; for(i=1;i<=n;i++){//[i-len,i-1]才能用ai 选最大值 b[i]=max(b[i-1],b[i]);//b[i]表示前缀最值 while(l<=r&&q[l]<i-len)l++; if(l<=r)Max(ans,a[q[l]]-q[l]+a[i]+i); if(i-len-1>=0)Max(ans,b[i-len-1]+a[i]-i+n+1); Max(f[x],min(i,n-i+1)+a[i]); while(l<=r&&a[q[r]]-q[r]<=a[i]-i)r--; q[++r]=i; } } void dfs(int x){ dfn[x]=low[x]=++clo; for(int i=head[x];i;i=nxt[i]){ int y=to[i]; if(y==fa[x])continue; if(!dfn[y]){ fa[y]=x;dep[y]=dep[x]+1; dfs(y); } Min(low[x],low[y]); if(low[y]>dfn[x])Max(ans,f[x]+f[y]+1),Max(f[x],f[y]+1);//桥边 直接更新 else if(fa[y]!=x)Dp(x,y);//这条边是某个环的返祖边 } } void solve(){ int i,x,k,lst; rd(n),rd(m); while(m--){ rd(k),rd(lst); for(i=1;i<k;i++,lst=x){ rd(x),ins(lst,x); } } dfs(1); printf("%d ",ans); } int main(){ // freopen(".in","r",stdin); solve(); return 0; }
▲21:28:49 HDU3594 判定有向图是否是仙人掌 三个条件:①没有横向边 ②没有桥边 ③每个点x的儿子y满足low[y]<=dfn[fa[x]]的y的数量<2 用tarjan跑一遍即可.
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int M=5e4+5; inline void Min(int &x,int y){if(x>y)x=y;} 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 n,ec=1,clo=0,dfn[M],low[M],vis[M],to[M],nxt[M],head[M]; bool dfs(int x,int p){ // printf("%d %d ",x,p); dfn[x]=low[x]=++clo; vis[x]=1; int c=0; for(int i=head[x];i;i=nxt[i]){ int y=to[i]; if(vis[y]==-1)return false; if(!dfn[y]&&!dfs(y,x))return false; Min(low[x],low[y]); if(low[y]>dfn[x])return false;//bridge if(low[y]<=dfn[p]){ c++; if(c>1)return false; } } vis[x]=-1; return true; } void ins(int a,int b){ to[ec]=b;nxt[ec]=head[a];head[a]=ec++; } int main(){ freopen("da.in","r",stdin); int cas,i,a,b; rd(cas); while(cas--){ rd(n); while(1){ rd(a),rd(b); if(!a&&!b)break; a++,b++; ins(a,b);///one-way edge } if(dfs(1,0))puts("YES"); else puts("NO"); for(i=1;i<=n;i++)head[i]=dfn[i]=low[i]=vis[i]=0; ec=1,clo=0; // break; } return 0; }
2017-03-28 周二(162)
▲20:25:06 HAOI2016 地图 无向仙人掌图
①把仙人掌图看成树,用一个区间来表示一棵子树. 先tarjan算一遍,把环上的节点的先后关系确定好,再标记不能走的边.第二遍dfs时先把pre遍历,保证一棵子树一定是连续的一段.
②现在问题就转化成,询问一个区间中的点权值<=y并且出现次数mod 2为k的权值个数.可以使用莫队算法.考虑如何更新和求值.现在要求的是一个前缀中出现次数为奇数次的数的个数.可以考虑树状数组,那么更新和求和的复杂度都是logn,总的复杂度是m*sqrt(n)*logn,显然不够优秀,能否实现O(1)更新呢?可以考虑分块,每次只更新自己和自己所在的块的信息,那么求和的时候只要for每个块和终点所在的块即可.复杂度m*sqrt(n)
求一个区间的信息,单点更新,而且每个点之间的贡献是独立的可以分块实现O(1)更新.
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<cmath> using namespace std; const int M=100005; int head[M],to[M*3],nxt[M*3],ec=0; int dfn[M],v[M],dep[M],clo=0,en[M],fa[M],ed[M];//fa表示到达x的边 int n,m,Q,tot=0,w[M],res[M],tmp[M],cnt[M],sum[M][2],id[M],sz,s;//tot表示所有不同的拉面个数 int mark[M*3],pre[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 pt(int x){ if(!x)return; pt(x/10); putchar((x%10)^48); } void sc(int x){ if(x)pt(x); else putchar('0'); putchar(' '); } struct node{ int id,l,r,y,z;//y表示值,z表示 bool operator<(const node &tmp)const{ int a=l/s,b=tmp.l/s; if(a!=b)return a<b; return (r<tmp.r); } }q[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++; } void dfs(int x,int e){ dfn[x]=++clo; for(int i=head[x];~i;i=nxt[i]){ if(i==e)continue; int y=to[i]; if(!dfn[y]){ fa[y]=x; dfs(y,i^1); } else if(dfn[y]<dfn[x]){ int z=x; while(z!=y){ if(fa[z]!=y)pre[fa[z]]=z; z=fa[z]; } mark[i]=mark[i^1]=1; } } } void rdfs(int x){ if(pre[x])rdfs(pre[x]); dfn[x]=++clo; v[clo]=x; for(int i=head[x];~i;i=nxt[i]){ if(mark[i]||to[i]==pre[x]||fa[to[i]]!=x)continue; rdfs(to[i]); } en[x]=clo; } int calc(int y,int z){ if(!y)return 0; int i,res=0; for(i=0;i<id[y];i++){ res+=sum[i][z]; } for(i=id[y]*sz;i<=y;i++){ if(cnt[i]&&(cnt[i]&1)==z)res++; } return res; } void add(int x){ x=w[v[x]];//得到对应的点 if(cnt[x])sum[id[x]][cnt[x]&1]--; cnt[x]++; sum[id[x]][cnt[x]&1]++; } void del(int x){ x=w[v[x]];//得到对应的点 sum[id[x]][cnt[x]&1]--; cnt[x]--; if(cnt[x])sum[id[x]][cnt[x]&1]++; } void solve(){ rd(n);rd(m); int i,j,k,a,b,x,y,z; for(i=1;i<=n;i++){ rd(w[i]); head[i]=ed[i]=-1; tmp[tot++]=w[i]; } sort(tmp,tmp+tot); tot=unique(tmp,tmp+tot)-tmp; sz=sqrt(tot); s=sqrt(n); for(i=1;i<=tot;i++)id[i]=i/sz; for(i=1;i<=n;i++)w[i]=lower_bound(tmp,tmp+tot,w[i])-tmp+1; for(i=1;i<=m;i++){ rd(a);rd(b);ins(a,b);//双向边 } dfs(1,-1);//得到每个点的区间 clo=0; rdfs(1); rd(m); for(i=1;i<=m;i++){ rd(z);rd(x);rd(y); y=upper_bound(tmp,tmp+tot,y)-tmp;//找的是<=y的第一个 也就是upper-1 q[i]=(node){i,dfn[x],en[x],y,z}; } sort(q+1,q+1+m); int L=1,R=0; for(i=1;i<=m;i++){ while(L>q[i].l)add(--L); while(R<q[i].r)add(++R); while(L<q[i].l)del(L++); while(R>q[i].r)del(R--); res[q[i].id]=calc(q[i].y,q[i].z); } for(i=1;i<=m;i++){ sc(res[i]); } } int main(){ // freopen("da.in","r",stdin); // freopen("my.out","w",stdout); // puts(" "); solve(); return 0; }
2017-03-30周四(163)
▲20:28:56 NOI2013 向量内积 随机化.判定是否存在一个解,可以转化成判定每个可能解都不合法.这样能够简化判定的思路,并且利用上随机化的想法.
2017-03-31周五(164)
▲20:51:51 POJ2420 模拟退火 给定n个点,求出某个点到所有点的距离和最小值
模拟退火的基本步骤:
设定初始温度T和初始状态x,每次找到下一步状态y,判断是否更优,如果更优,就把y作为当前状态,否则以exp((f(y)-f(x))/t)的概率接受.每轮再将t*delta.
#include<cstdio> #include<algorithm> #include<algorithm> #include<iostream> #include<ctime> #include<cstdlib> #include<cmath> using namespace std; #define db double const db eps=1e-8,oo=1e99; const db T=100,delta=0.98; const int M=105; struct node{ db x,y; }a[M],b; int dx[]={0,0,-1,1},dy[]={1,-1,0,0}; int n; db dist(node a,node b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } db sum(node b){ db ans=0; for(int i=1;i<=n;i++)ans+=dist(b,a[i]); return ans; } db rnd(){ return rand()%100*1.0/100; } db query(){ int i,j,k; db t,s,ans=oo; node b=a[1],c; for(t=T;t>eps;t*=delta){ for(j=0;j<20;j++){ for(i=0;i<4;i++){ c.x=b.x+dx[i]*t; c.y=b.y+dy[i]*t; s=sum(c); if(s<ans||rnd()<exp((ans-s)/t)){ ans=s,b=c; } } } } return ans; } int main(){ // srand(time(NULL)); // freopen("da.in","r",stdin); while(scanf("%d",&n)!=EOF){ for(int i=1;i<=n;i++)scanf("%lf %lf",&a[i].x,&a[i].y); printf("%.0f ",query()); } return 0; }