蒟蒻Yzm
20160530
bzoj2333 http://www.lydsy.com/JudgeOnline/problem.php?id=2333
题意:
有N个节点,M个操作:连接两个节点、单个节点的权值增加v、节点所在的连通块的所有节点的权值增加v、所有节点的权值增加v、询问节点当前的权值、询问节点所在的连通块中权值最大的节点的权值、询问所有节点中权值最大的节点的权值。
N,M≤300000
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <set> 5 #define inc(i,j,k) for(int i=j;i<=k;i++) 6 #define maxn 300100 7 #define INF 0x3fffffff 8 using namespace std; 9 10 int fa[maxn],ch[maxn][2],tg[maxn],v[maxn],n,m,add; 11 multiset <int> st; 12 void pushdown(int x){ 13 if(tg[x]){ 14 if(ch[x][0])tg[ch[x][0]]+=tg[x],v[ch[x][0]]+=tg[x]; 15 if(ch[x][1])tg[ch[x][1]]+=tg[x],v[ch[x][1]]+=tg[x]; 16 tg[x]=0; 17 } 18 } 19 int dt[maxn],dts; 20 int find(int x){ 21 dt[dts=1]=x; while(fa[x])x=fa[x],dt[++dts]=x; 22 for(int i=dts;i>=1;i--)pushdown(dt[i]); return x; 23 } 24 int merge(int x,int y){ 25 if(!x||!y)return x+y; if(v[x]<v[y])swap(x,y); pushdown(x); 26 ch[x][1]=merge(ch[x][1],y); fa[ch[x][1]]=x; swap(ch[x][0],ch[x][1]); return x; 27 } 28 int del(int x){ 29 int t=merge(ch[x][0],ch[x][1]),f=fa[x]; fa[x]=ch[x][0]=ch[x][1]=0; 30 fa[t]=f; if(f)ch[f][ch[f][1]==x]=t; return t; 31 } 32 void update1(int x,int val){ 33 int y=find(x); int t=del(x); v[x]+=val; 34 if(y!=x){ 35 int z=merge(y,x); st.erase(st.find(v[y])); st.insert(v[z]); 36 }else{ 37 if(t){ 38 int z=merge(t,x); st.erase(st.find(v[x]-val)),st.insert(v[z]); 39 }else st.erase(st.find(v[x]-val)),st.insert(v[x]); 40 } 41 } 42 void update2(int x,int val){ 43 x=find(x); tg[x]+=val; v[x]+=val; if(!fa[x])st.erase(st.find(v[x]-val)),st.insert(v[x]); 44 } 45 void update3(int val){add+=val;} 46 int query1(int x){find(x); return v[x];} 47 int query2(int x){int y=find(x); return v[y];} 48 int query3(){return * --st.find(INF);} 49 void connect(int x,int y){ 50 int xx=find(x),yy=find(y); if(xx==yy)return; int z=merge(xx,yy); 51 if(z==xx)st.erase(st.find(v[yy]));else st.erase(st.find(v[xx])); 52 } 53 char opt[3]; 54 int main(){ 55 //freopen("test.txt","r",stdin); 56 scanf("%d",&n); add=0; st.clear(); 57 inc(i,1,n){ 58 scanf("%d",&v[i]); st.insert(v[i]); fa[i]=ch[i][0]=ch[i][1]=tg[i]=0; 59 } 60 scanf("%d",&m); 61 inc(i,1,m){ 62 scanf("%s",opt); int x,y; 63 if(opt[0]==‘U‘)scanf("%d%d",&x,&y),connect(x,y); 64 if(opt[0]==‘A‘){ 65 if(opt[1]==‘1‘)scanf("%d%d",&x,&y),update1(x,y); 66 if(opt[1]==‘2‘)scanf("%d%d",&x,&y),update2(x,y); 67 if(opt[1]==‘3‘)scanf("%d",&x),update3(x); 68 } 69 if(opt[0]==‘F‘){ 70 if(opt[1]==‘1‘)scanf("%d",&x),printf("%d\n",query1(x)+add); 71 if(opt[1]==‘2‘)scanf("%d",&x),printf("%d\n",query2(x)+add); 72 if(opt[1]==‘3‘)printf("%d\n",query3()+add); 73 } 74 //if(i==2)break; 75 } 76 return 0; 77 }
题解:可并堆,虽然听说配对堆非常快,但教程太少了不会写,所以去学了斜堆,比较好写。斜堆实际上是一棵二叉树,核心是合并操作,这是一个递归过程,有点像treap的删除操作。斜堆保证复杂度的方法是每次递归合并右节点,合并完后交换左右节点,使整棵树和splay一样,可以“自动”平衡,也是玄学。要修改整个连通块,打标记就行了。这道题特殊的一点在于询问所有节点权值的最大值,可以用STL的set维护所有连通块的根节点,当连边和修改权值时如果根节点被修改需要维护一下set。
2、bzoj2039 http://www.lydsy.com/JudgeOnline/problem.php?id=2039
题意:有N个经理,Ei,j表示i经理对j经理的了解程度,当经理i和经理j同时被雇佣时,利润增加Ei,j*2。同时,雇佣每一个经理都需要花费一定的金钱Ai。没有被雇佣的人会被竞争对手所雇佣,使得所赚得的利润减少Ei,j(意思是经理i和j如果只雇佣一个,就会少Ei,j,如果两个都没被雇佣就不扣钱)。求最大利润。N≤1000
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <queue> 5 #define maxn 1100 6 #define inc(i,j,k) for(int i=j;i<=k;i++) 7 #define INF 1000000000000000000 8 #define ll long long 9 using namespace std; 10 11 struct e{int t;ll c;int n;}; e es[maxn*2000]; int g[maxn],ess; 12 inline void pe(int f,int t,ll c){ 13 es[++ess]=(e){t,c,g[f]}; g[f]=ess; es[++ess]=(e){f,0,g[t]}; g[t]=ess; 14 } 15 inline void pe2(int f,int t,ll c){ 16 es[++ess]=(e){t,c,g[f]}; g[f]=ess; es[++ess]=(e){f,c,g[t]}; g[t]=ess; 17 } 18 inline void init(){ 19 ess=-1; memset(g,-1,sizeof(g)); 20 } 21 queue <int> q; int h[maxn]; 22 bool bfs(int s,int t){ 23 memset(h,-1,sizeof(h)); while(!q.empty())q.pop(); h[s]=0; q.push(s); 24 while(! q.empty()){ 25 int x=q.front(); q.pop(); 26 for(int i=g[x];i!=-1;i=es[i].n)if(es[i].c&&h[es[i].t]==-1)h[es[i].t]=h[x]+1,q.push(es[i].t); 27 } 28 return h[t]!=-1; 29 } 30 ll dfs(int x,int t,ll f){ 31 if(x==t)return f; ll u=0; 32 for(int i=g[x];i!=-1;i=es[i].n)if(es[i].c&&h[es[i].t]==h[x]+1){ 33 ll w=dfs(es[i].t,t,min(f,es[i].c)); f-=w; u+=w; es[i].c-=w; es[i^1].c+=w; if(f==0)return u; 34 } 35 if(u==0)h[x]=-1; return u; 36 } 37 ll dinic(int s,int t){ 38 ll f=0; while(bfs(s,t))f+=dfs(s,t,INF); return f; 39 } 40 int n,s,t; ll a[maxn],b,tot; 41 int main(){ 42 scanf("%d",&n); s=0; t=n+1; init(); inc(i,1,n)scanf("%lld",&b),pe(s,i,b); memset(a,0,sizeof(a)); 43 inc(i,1,n)inc(j,1,n){ 44 scanf("%lld",&b); if(i>j||b==0)continue; a[i]+=b; a[j]+=b; pe2(i,j,2*b); tot+=2*b; 45 } 46 inc(i,1,n)pe(i,t,a[i]); printf("%lld",tot-dinic(s,t)); return 0; 47 }
题解:S集表示雇佣,T集表示不雇佣。每个经理拆成x,y两点。s向所有x点连,流量为雇佣费用。对于每个Ei,j,i,j经理连一条流量为2*Ei,j的无向边,同时i和j都向t连流量为Ei,j的边,最小割为所有Ei,j*2减最大流。由于边数大,需要合并一下边。
20160531
3、bzoj1588 http://www.lydsy.com/JudgeOnline/problem.php?id=1588
题意:n天,每天得到一个值,要求输出每一天和这天得到的值相差最小的之前天得到的值与这个值的差的和。n不知道,不过O(nlog2n)可写。
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <set> 5 #define INF 2147483647 6 #define inc(i,j,k) for(int i=j;i<=k;i++) 7 using namespace std; 8 9 int n,ans,x; set <int> st; 10 int main(){ 11 scanf("%d",&n); st.clear(); st.insert(INF); st.insert(-INF); ans=0; 12 inc(i,1,n){ 13 scanf("%d",&x); 14 if(i==1)ans+=x;else{ 15 int y=* (st.lower_bound(x)),z=* (--st.lower_bound(x)); //printf("%d %d\n",y,z); 16 if(y==INF)ans+=abs(x-z);else if(z==-INF)ans+=abs(x-y);else ans+=min(abs(x-z),abs(x-y)); 17 } 18 st.insert(x); 19 } 20 printf("%d",ans); return 0; 21 }
题解:说是平衡树模板题,不过可以用set水过去。先在set插入一个-INF和INF防溢出(yyl大爷教我的)每次在set中lower_bound这天得到的值,求出的是≤它的最大值,比较一下这个值和set中这个值的后继与这天得到的值的差。
4、bzoj2809 http://www.lydsy.com/JudgeOnline/problem.php?id=2809
题意:n个点组成一棵树,每个点都有一个领导力和费用,可以让一个点当领导,然后在这个点的子树中选择一些费用之和不超过m的点,得到领导的领导力乘选择的点的个数(领导可不被选择)的利润。求利润最大值。n≤100000
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define inc(i,j,k) for(int i=j;i<=k;i++) 5 #define maxn 100100 6 #define ll long long 7 using namespace std; 8 9 int ch[maxn][2],rt[maxn],n,m,st; ll sz[maxn],v[maxn],ans,sm[maxn],gd[maxn]; 10 struct e{int t,n;}; e es[maxn]; int ess,g[maxn]; 11 void pe(int f,int t){es[++ess]=(e){t,g[f]}; g[f]=ess;} 12 void init(){ess=0; memset(g,0,sizeof(g));} 13 void update(int x){sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1; sm[x]=sm[ch[x][0]]+sm[ch[x][1]]+v[x];} 14 int merge(int x,int y){ 15 if(!x||!y)return x+y; if(v[x]<v[y])swap(x,y); ch[x][1]=merge(ch[x][1],y); 16 swap(ch[x][0],ch[x][1]); update(x); return x; 17 } 18 void pop(int &x){ 19 int y=merge(ch[x][0],ch[x][1]); ch[x][0]=ch[x][1]=0; sz[x]=1; sm[x]=v[x]; x=y; 20 } 21 void dfs(int x){ 22 rt[x]=x; for(int i=g[x];i;i=es[i].n)dfs(es[i].t),rt[x]=merge(rt[x],rt[es[i].t]); 23 while(sz[rt[x]]&&sm[rt[x]]>m)pop(rt[x]); ans=max(ans,sz[rt[x]]*gd[x]); 24 } 25 int main(){ 26 scanf("%d%d",&n,&m); init(); 27 inc(i,1,n){ 28 int a; ll b,c; scanf("%d%lld%lld",&a,&b,&c); if(a)pe(a,i);else st=i; 29 v[i]=sm[i]=b; sz[i]=1; gd[i]=c; ch[i][0]=ch[i][1]=rt[i]=0; 30 } 31 ans=0; dfs(st); printf("%lld",ans); return 0; 32 }
题解:可并堆。可以得到一个结论,就是在子树中选点的时候,先选所有点,如果费用超了,就不断把费用最大的剔除,知道费用不超,这样得到的选点数量最大,这过程可以用堆维护。同时每个点的堆都可以由子树的堆合并得到,所以需要可并堆。
5、bzoj1196 http://www.lydsy.com/JudgeOnline/problem.php?id=1196
题意:修n-1条公路将n个点连通,每个点可建一级公路也可建二级公路,要求一级公路必须有k条,要求花费最多的公路花费最少。
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define inc(i,j,k) for(int i=j;i<=k;i++) 5 #define maxn 10500 6 using namespace std; 7 8 int n,k,m,u[maxn*2],v[maxn*2],c1[maxn*2],c2[maxn*2],mx,fa[maxn],cnt; 9 int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);} 10 bool check(int lim){ 11 inc(i,1,n)fa[i]=i; cnt=0; 12 inc(i,1,m)if(c1[i]<=lim){ 13 int x=find(u[i]),y=find(v[i]); if(x==y)continue; fa[y]=x; cnt++; 14 } 15 if(cnt<k)return 0; 16 inc(i,1,m)if(c2[i]<=lim){ 17 int x=find(u[i]),y=find(v[i]); if(x==y)continue; fa[y]=x; cnt++; 18 } 19 if(cnt<n-1)return 0; return 1; 20 } 21 int main(){ 22 scanf("%d%d%d",&n,&k,&m); mx=0; 23 inc(i,1,m-1)scanf("%d%d%d%d",&u[i],&v[i],&c1[i],&c2[i]),mx=max(mx,c1[i]),mx=max(mx,c2[i]); 24 int l=0,r=mx,ans; 25 while(l<=r){ 26 int mid=(l+r)>>1; 27 if(check(mid))ans=mid,r=mid-1;else l=mid+1; 28 } 29 printf("%d",ans); return 0; 30 }
题解:首先二分,接着判定:先在不产生环的前提下(用并查集维护)让每条路尽量修一级公路,如果最后
20160602
6、bzoj1218 http://www.lydsy.com/JudgeOnline/problem.php?id=1218
题意:坐标系上有n个目标,每个目标有一个价值,现在求一个边与坐标轴平行,边长为R的正方形,使在其内部(原题是不包括边界,然而实际上不是这样)的目标价值最大。所有坐标为0到5000的整数,n≤10000
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define maxn 5500 5 #define inc(i,j,k) for(int i=j;i<=k;i++) 6 using namespace std; 7 8 int sum[maxn][maxn],n,mxxy,r,mx; 9 int main(){ 10 scanf("%d%d",&n,&r); mxxy=mx=0; 11 inc(i,1,n){ 12 int x,y,z; scanf("%d%d%d",&x,&y,&z); x++; y++; 13 mxxy=max(mxxy,x); mxxy=max(mxxy,y); sum[x][y]+=z; 14 } 15 inc(i,1,mxxy)inc(j,1,mxxy) 16 sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]; 17 inc(i,r,mxxy)inc(j,r,mxxy) 18 mx=max(mx,sum[i][j]-sum[i][j-r]-sum[i-r][j]+sum[i-r][j-r]); 19 printf("%d",mx); return 0; 20 }
题解:预处理一下以横纵坐标为节点的二维前缀和,然后枚举正方形右上角坐标即可。注意可以将坐标系向右上移动一个单位使前缀和不用考虑负数。
反思:蒟蒻好弱啊,枚举时i和j的边界都应该是所以节点横坐标最大值与纵坐标最大值的最大值。蒟蒻一开始没注意到这一点,以为自己预处理写错。改来改去,WA来WA去。最后对着标程一点点改才发现问题。QAQ
7、bzoj1295 http://www.lydsy.com/JudgeOnline/problem.php?id=1295
题意:N*M块地,如果两块地都没有障碍物,则互相可达。如果两块地互相可达(可经过其他地)则它们之间的距离为它们中心点的欧几里得距离,求如果能移走不大于T个障碍物,土地间的最大距离。N,M≤30
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <queue> 5 #include <cmath> 6 #include <cctype> 7 #define maxn 100 8 #define inc(i,j,k) for(int i=j;i<=k;i++) 9 using namespace std; 10 11 12 int map[maxn][maxn],n,m,k; double mx; 13 bool vis[maxn][maxn],inq[maxn][maxn]; int d[maxn][maxn]; 14 struct qn{int x,y;}; queue <qn> q; 15 void spfa(int sx,int sy){ 16 while(!q.empty())q.pop(); q.push((qn){sx,sy}); memset(d,-1,sizeof(d)); memset(inq,0,sizeof(inq)); 17 memset(vis,0,sizeof(vis)); d[sx][sy]=map[sx][sy]; inq[sx][sy]=1; 18 while(!q.empty()){ 19 qn x=q.front(); q.pop(); inq[x.x][x.y]=0; 20 if(x.x!=1&&(d[x.x-1][x.y]==-1||d[x.x-1][x.y]>d[x.x][x.y]+map[x.x-1][x.y])){ 21 d[x.x-1][x.y]=d[x.x][x.y]+map[x.x-1][x.y]; 22 if(!inq[x.x-1][x.y])inq[x.x-1][x.y]=1,q.push((qn){x.x-1,x.y}); 23 } 24 if(x.y!=1&&(d[x.x][x.y-1]==-1||d[x.x][x.y-1]>d[x.x][x.y]+map[x.x][x.y-1])){ 25 d[x.x][x.y-1]=d[x.x][x.y]+map[x.x][x.y-1]; 26 if(!inq[x.x][x.y-1])inq[x.x][x.y-1]=1,q.push((qn){x.x,x.y-1}); 27 } 28 if(x.x!=n&&(d[x.x+1][x.y]==-1||d[x.x+1][x.y]>d[x.x][x.y]+map[x.x+1][x.y])){ 29 d[x.x+1][x.y]=d[x.x][x.y]+map[x.x+1][x.y]; 30 if(!inq[x.x+1][x.y])inq[x.x+1][x.y]=1,q.push((qn){x.x+1,x.y}); 31 } 32 if(x.y!=m&&(d[x.x][x.y+1]==-1||d[x.x][x.y+1]>d[x.x][x.y]+map[x.x][x.y+1])){ 33 d[x.x][x.y+1]=d[x.x][x.y]+map[x.x][x.y+1]; 34 if(!inq[x.x][x.y+1])inq[x.x][x.y+1]=1,q.push((qn){x.x,x.y+1}); 35 } 36 } 37 inc(i,1,n)inc(j,1,m)if(d[i][j]<=k)vis[i][j]=1; 38 } 39 int main(){ 40 scanf("%d%d%d",&n,&m,&k); char c; 41 inc(i,1,n)inc(j,1,m){ 42 while(!isdigit(c=getchar())); map[i][j]=c-‘0‘; 43 } 44 inc(i,1,n)inc(j,1,m){ 45 spfa(i,j); inc(i0,1,n)inc(j0,1,m)if(vis[i0][j0])mx=max(mx,sqrt((i0-i)*(i0-i)+(j0-j)*(j0-j))); 46 } 47 printf("%.6lf",mx); return 0; 48 }
题解:把经过一个障碍物视为边长度为1,求出每两个点之间要跨越的边长度,如果长度小于等于T,就将其欧几里得距离和答案比较。
8、bzoj1221 http://www.lydsy.com/JudgeOnline/problem.php?id=1221
题意:n天,每天需要ai条消毒毛巾,这种消毒毛巾使用一天后必须再做消毒处理后才能使用。消毒方式有两种,A种方式的消毒需要a天时间,一条费用fA,B种方式的消毒需要b天,一条费用fB,买一块新毛巾的费用为f(新毛巾是已消毒的,当天可以使用),求最小费用。n≤1000
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <queue> 5 #define inc(i,j,k) for(int i=j;i<=k;i++) 6 #define maxn 3000 7 #define INF 0x3fffffff 8 using namespace std; 9 10 struct e{int f,t,c,w,n;}; e es[maxn*40]; int ess,g[maxn]; 11 inline void pe(int f,int t,int c,int w){ 12 es[++ess]=(e){f,t,c,w,g[f]}; g[f]=ess; es[++ess]=(e){t,f,0,-w,g[t]}; g[t]=ess; 13 } 14 void init(){ess=-1; memset(g,-1,sizeof(g));} 15 int d[maxn],fr[maxn]; bool inq[maxn]; queue <int> q; 16 bool spfa(int s,int t){ 17 while(!q.empty())q.pop(); memset(inq,0,sizeof(inq)); memset(d,-1,sizeof(d)); 18 inq[s]=1; d[s]=0; q.push(s); fr[s]=-1; 19 while(! q.empty()){ 20 int x=q.front(); q.pop(); inq[x]=0; 21 for(int i=g[x];i!=-1;i=es[i].n)if(es[i].c&&(d[es[i].t]==-1||d[es[i].t]>d[x]+es[i].w)){ 22 d[es[i].t]=d[x]+es[i].w; fr[es[i].t]=i; if(!inq[es[i].t])inq[es[i].t]=1,q.push(es[i].t); 23 } 24 } 25 return d[t]!=-1; 26 } 27 int advanced(int s,int t){ 28 int a=INF,c=0; 29 for(int i=fr[t];i!=-1;i=fr[es[i].f])a=min(a,es[i].c); 30 for(int i=fr[t];i!=-1;i=fr[es[i].f])es[i].c-=a,es[i^1].c+=a,c+=(a*es[i].w); 31 return c; 32 } 33 int maxflowmincost(int s,int t){ 34 int c=0; while(spfa(s,t))c+=advanced(s,t); return c; 35 } 36 int n,a,b,f,fa,fb,s,t,x; 37 int main(){ 38 scanf("%d%d%d%d%d%d",&n,&a,&b,&f,&fa,&fb); s=0; t=2*n+1; init(); 39 inc(i,1,n)scanf("%d",&x),pe(s,i,x,0),pe(n+i,t,x,0); 40 inc(i,1,n){ 41 if(i+a<n)pe(i,n+i+a+1,INF,fa); if(i+b<n)pe(i,n+i+b+1,INF,fb); 42 if(i<n)pe(i,i+1,INF,0); pe(s,i+n,INF,f); 43 } 44 printf("%d",maxflowmincost(s,t)); return 0; 45 }
题解:费用流。每天拆成x和y。s向所有x连边表示有的毛巾,所有y向t连边表示用的毛巾,流量为需要毛巾数费用0。xi向yi+a连边,表示a方式消毒,xi向yi+b连边,表示b方式消毒,s向所有y连边,表示买新的,xi向xi+1连边,表示前一天没用的拿到第二天用。
反思:费用流关键是要抓住“流量优先”。
9、bzoj1293 http://www.lydsy.com/JudgeOnline/problem.php?id=1293
题意:数轴上N个点,分为K种。可以有多个点出现在同一个位置上。需要一个最短区间使里面有K种点,求这个区间长度。N≤1000000
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define inc(i,j,k) for(int i=j;i<=k;i++) 5 #define INF 0x3fffffff 6 using namespace std; 7 8 struct ball{int a,b;}; ball balls[1000500]; 9 bool cmp(ball a,ball b){return a.a<b.a;} 10 int n,k,l,r,bs,kn[70],ks,mn; 11 int main(){ 12 scanf("%d%d",&n,&k); 13 inc(i,1,k){ 14 int x,y; scanf("%d",&x); inc(j,1,x)scanf("%d",&y),balls[++bs]=(ball){y,i}; 15 } 16 sort(balls+1,balls+1+bs,cmp); l=1; r=0; 17 memset(kn,0,sizeof(kn)); ks=0; mn=INF; 18 while(ks<k){ 19 if(r<n){r++; kn[balls[r].b]++; if(kn[balls[r].b]==1)ks++;} 20 while(r<n&&balls[r].a==balls[r+1].a){r++; kn[balls[r].b]++; if(kn[balls[r].b]==1)ks++;} 21 if(r==n)break; 22 } 23 if(ks==k)mn=min(mn,balls[r].a-balls[l].a); 24 while(1){ 25 while(l<r&&balls[l].a==balls[l+1].a){kn[balls[l].b]--; if(kn[balls[l].b]==0)ks--; l++;} 26 if(l<r){kn[balls[l].b]--; if(kn[balls[l].b]==0)ks--; l++;} 27 if(ks==k)mn=min(mn,balls[r].a-balls[l].a);else{ 28 while(ks<k){ 29 if(r<n){r++; kn[balls[r].b]++; if(kn[balls[r].b]==1)ks++;} 30 while(r<n&&balls[r].a==balls[r+1].a){r++; kn[balls[r].b]++; if(kn[balls[r].b]==1)ks++;} 31 if(r==n)break; 32 } 33 if(ks==k)mn=min(mn,balls[r].a-balls[l].a); 34 } 35 if(l==r)break; 36 } 37 printf("%d",mn); return 0; 38 }
题解:先排序,然后用两个指针分别指向区间两个端点,每次l指针往左移并更新答案直到区间里没有K种点,再把r指针向右移直到区间里有K种点,更新一下答案。
20160603
10、bzoj1787 http://www.lydsy.com/JudgeOnline/problem.php?id=1787
题意:给个树,每次给三个点,求与这三个点距离最小的点。点数、询问数≤500000
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define maxn 500500 5 #define inc(i,j,k) for(int i=j;i<=k;i++) 6 using namespace std; 7 8 inline int read(){ 9 char ch=getchar(); int f=1,x=0; 10 while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1; ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘)x=x*10+ch-‘0‘,ch=getchar(); 11 return x*f; 12 } 13 int fa[maxn][20],dep[maxn]; 14 struct e{int t,n;}; e es[maxn*2]; int g[maxn],ess; 15 void pe(int f,int t){ 16 es[++ess]=(e){t,g[f]}; g[f]=ess; es[++ess]=(e){f,g[t]}; g[t]=ess; 17 } 18 void init(){ess=0; memset(g,0,sizeof(g));} 19 void dfs(int x,int f){ 20 for(int i=g[x];i;i=es[i].n)if(es[i].t!=f){ 21 dep[es[i].t]=dep[x]+1; fa[es[i].t][0]=x; dfs(es[i].t,x); 22 } 23 } 24 int n,m; 25 void build(){ 26 for(int j=1;(1<<j)<=n;j++)inc(i,1,n)fa[i][j]=fa[fa[i][j-1]][j-1]; 27 } 28 int lca(int x,int y){ 29 if(dep[x]<dep[y])swap(x,y); int t=dep[x]-dep[y]; 30 for(int i=0;(1<<i)<=n;i++)if(t&(1<<i))x=fa[x][i]; 31 for(int i=18;i>=0;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i]; 32 if(x==y)return x;else return fa[x][0]; 33 } 34 int dis(int x,int y){ 35 int z=lca(x,y); return dep[x]-dep[z]+dep[y]-dep[z]; 36 } 37 int main(){ 38 n=read(); m=read(); init(); inc(i,1,n-1){int a=read(),b=read(); pe(a,b);} 39 dep[1]=0; fa[1][0]=0; dfs(1,0); build(); 40 inc(i,1,m){ 41 int a=read(),b=read(),c=read(); int x=lca(a,b),y=lca(a,c),z=lca(b,c); 42 if(x==y)printf("%d %d\n",z,dis(a,z)+dis(b,z)+dis(c,z)); 43 else if(x==z)printf("%d %d\n",y,dis(a,y)+dis(b,y)+dis(c,y)); 44 else if(y==z)printf("%d %d\n",x,dis(a,x)+dis(b,x)+dis(c,x)); 45 } 46 return 0; 47 }
题解:倍增求出两两之间的LCA后,比较容易理解的做法是挑出两个LCA再做一次LCA,比较所有挑法。但画kan出ti图jie可知其中有两个LCA是相等的,而所求就是那个与它们不等的LCA(我也不知为什么)。
11、bzoj1934 http://www.lydsy.com/JudgeOnline/problem.php?id=1934
题意:n个小朋友通过投票来决定睡不睡午觉。每个人都有自己的主见,但也可以投和自己本来意愿相反的票。冲突总数为好朋友之间发生冲突的总数加上和自己本来意愿发生冲突的人数。求最小冲突数。
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <queue> 5 #define maxn 500 6 #define inc(i,j,k) for(int i=j;i<=k;i++) 7 #define INF 0x3fffffff 8 using namespace std; 9 10 struct e{int t,c,n;}; e es[maxn*2000]; int g[maxn],ess; 11 inline void pe(int f,int t,int c){ 12 es[++ess]=(e){t,c,g[f]}; g[f]=ess; es[++ess]=(e){f,0,g[t]}; g[t]=ess; 13 } 14 inline void pe2(int f,int t,int c){ 15 es[++ess]=(e){t,c,g[f]}; g[f]=ess; es[++ess]=(e){f,c,g[t]}; g[t]=ess; 16 } 17 inline void init(){ 18 ess=-1; memset(g,-1,sizeof(g)); 19 } 20 queue <int> q; int h[maxn]; 21 bool bfs(int s,int t){ 22 memset(h,-1,sizeof(h)); while(!q.empty())q.pop(); h[s]=0; q.push(s); 23 while(! q.empty()){ 24 int x=q.front(); q.pop(); 25 for(int i=g[x];i!=-1;i=es[i].n)if(es[i].c&&h[es[i].t]==-1)h[es[i].t]=h[x]+1,q.push(es[i].t); 26 } 27 return h[t]!=-1; 28 } 29 int dfs(int x,int t,int f){ 30 if(x==t)return f; int u=0; 31 for(int i=g[x];i!=-1;i=es[i].n)if(es[i].c&&h[es[i].t]==h[x]+1){ 32 int w=dfs(es[i].t,t,min(f,es[i].c)); f-=w; u+=w; es[i].c-=w; es[i^1].c+=w; if(f==0)return u; 33 } 34 if(u==0)h[x]=-1; return u; 35 } 36 int dinic(int s,int t){ 37 int f=0; while(bfs(s,t))f+=dfs(s,t,INF); return f; 38 } 39 inline int read(){ 40 char ch=getchar(); int f=1,x=0; 41 while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1; ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘)x=x*10+ch-‘0‘,ch=getchar(); 42 return x*f; 43 } 44 int n,m,s,t; 45 int main(){ 46 n=read(); m=read(); s=0; t=n+1; init(); inc(i,1,n){int x=read(); if(x)pe(s,i,1);else pe(i,t,1);} 47 inc(i,1,m){int x=read(),y=read(); pe2(x,y,1);} printf("%d",dinic(s,t)); return 0; 48 }
题解:最小割,s向每个选1的人连边流量为1,每个选0的人连边流量为1。好朋友之间连流量为1的双向边。
反思:理解错题意……以为冲突总数是每个人投票的冲突数之和,每个人投票冲突数是与朋友之间发生冲突的总数加上和这个人本来意愿发生冲突的人数。脑洞好大QAQ
12、bzoj1452 http://www.lydsy.com/JudgeOnline/problem.php?id=1452
题意:n*m矩阵,支持两个操作,修改某个格子权值和查询某个子矩阵特定权值出现次数。n,m≤300,权值为1到100的整数。
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define maxn 301 5 #define inc(i,j,k) for(int i=j;i<=k;i++) 6 #define lb(x) x&-x 7 using namespace std; 8 9 int sm[maxn][maxn][maxn/3+1],col[maxn][maxn],n,m; 10 void update(int x,int y,int z,int v){ 11 for(int i=x;i<=n;i+=lb(i))for(int j=y;j<=m;j+=lb(j))sm[i][j][z]+=v; 12 } 13 int query(int x,int y,int z){ 14 int q=0; for(int i=x;i>=1;i-=lb(i))for(int j=y;j>=1;j-=lb(j))q+=sm[i][j][z]; return q; 15 } 16 inline int read(){ 17 char ch=getchar(); int f=1,x=0; 18 while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1; ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘)x=x*10+ch-‘0‘,ch=getchar(); 19 return f*x; 20 } 21 int main(){ 22 n=read(); m=read(); inc(i,1,n)inc(j,1,m){col[i][j]=read(); update(i,j,col[i][j],1);} int q=read(); 23 inc(i,1,q){ 24 int x=read(); 25 if(x==1){ 26 int x0=read(),x1=read(),x2=read(); 27 update(x0,x1,col[x0][x1],-1); update(x0,x1,x2,1); col[x0][x1]=x2; 28 } 29 if(x==2){ 30 int x1=read(),x2=read(),y1=read(),y2=read(),c=read(); 31 printf("%d\n",query(x2,y2,c)-query(x1-1,y2,c)-query(x2,y1-1,c)+query(x1-1,y1-1,c)); 32 } 33 } 34 return 0; 35 }
题解:原来二维前缀和也可以用树状数组维护,只要那个不断增加/减少lowbit的循环再嵌套一层就行了。同时因为权值是1到100的整数,所以二维前缀和多一维维护特定权值个数就行了。
13、bzoj1406 http://www.lydsy.com/JudgeOnline/problem.php?id=1406
题意:输出1到n-1中平方后模n等于1的整数
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <cmath> 5 #define inc(i,j,k) for(int i=j;i<=k;i++) 6 #define maxn 100 7 using namespace std; 8 9 int yss,ys[maxn],st[maxn*5000],sts; 10 int main(){ 11 int n; scanf("%d",&n); inc(i,1,(int)sqrt(n))if(n%i==0)ys[++yss]=n/i; st[sts=1]=1; 12 inc(i,1,yss){ 13 for(int j=ys[i];j<=n;j+=ys[i]){ 14 if((j-2)%(n/ys[i])==0)st[++sts]=j-1; 15 if(j<n&&(j+2)%(n/ys[i])==0)st[++sts]=j+1; 16 } 17 } 18 if(sts==0||n==1)printf("None");else{ 19 sort(st+1,st+1+sts); int m=unique(st+1,st+1+sts)-st-1; inc(i,1,m)printf("%d\n",st[i]); 20 } 21 return 0; 22 }
题解:设所求数x,化简得(x+1)(x-1)=n*k,设n1*n2等于k,(x+1)%n1==0,(x-1)%n2==0,因此n1、n2都为n的因数,且一个≤sqrt(n),一个≥(sqrt(n))。据说int以内的数的因数都不超过30个,所以可以先求出所有≥sqrt(n)的因数,然后对于每个因数,求出所有<n的这个因数的倍数,用它尝试被x+1模。因为这些因数都≥sqrt(n),所以这个枚举的过程不会超时。