20160523
bzoj2561 http://www.lydsy.com/JudgeOnline/problem.php?id=2561
题意:给定一个连通无向图,假设现在加入一条边权为L的边(u,v),求需要删掉最少多少条边,才能够使得这条边既可能出现在最小生成树上,也可能出现在最大生成树上。N≤20000,M≤200000
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <queue> 5 #define maxn 30000 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*40]; 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 init(){ 15 ess=-1; memset(g,-1,sizeof(g)); 16 } 17 queue <int> q; int h[maxn]; 18 bool bfs(int s,int t){ 19 memset(h,-1,sizeof(h)); while(!q.empty())q.pop(); h[s]=0; q.push(s); 20 while(! q.empty()){ 21 int x=q.front(); q.pop(); 22 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); 23 } 24 return h[t]!=-1; 25 } 26 int dfs(int x,int t,int f){ 27 if(x==t)return f; int u=0; 28 for(int i=g[x];i!=-1;i=es[i].n)if(es[i].c&&h[es[i].t]==h[x]+1){ 29 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; 30 } 31 if(u==0)h[x]=-1; return u; 32 } 33 int dinic(int s,int t){ 34 int f=0; while(bfs(s,t))f+=dfs(s,t,INF); return f; 35 } 36 int n,m,u[maxn*10],v[maxn*10],w[maxn*10],U,V,L; 37 int main(){ 38 scanf("%d%d",&n,&m); inc(i,1,m)scanf("%d%d%d",&u[i],&v[i],&w[i]); scanf("%d%d%d",&U,&V,&L); int ans=0; 39 init(); inc(i,1,m)if(w[i]<L)pe(u[i],v[i],1),pe(v[i],u[i],1); ans+=dinic(U,V); 40 init(); inc(i,1,m)if(w[i]>L)pe(u[i],v[i],1),pe(v[i],u[i],1); ans+=dinic(U,V); 41 printf("%d",ans); return 0; 42 }
题解:最小割。如果一个边出现在最小生成树上,那么权值比它小的边一定不能使图联通。因为要求删掉最少,所以当加入这条边后整个图刚好联通。因此可以将这条边的一个端点作为源,另一端点作为汇,插入所以权值比L小的边,每条边流量为1,跑最小割,求出来的答案就是使源、汇不联通最少删掉边。最大生成树同理,插入的是权值比L大的。最后答案是两次跑最小割的结果相加。
吐槽:注意边要开到4倍,而且图中边是无向边,在网络流插边时要插两个方向。这道题也告诉我们实际上数据范围上万的可能也是用网络流。dinic/ISAP的玄学复杂度QAQ
2、bzoj1024 http://www.lydsy.com/JudgeOnline/problem.php?id=1024
题意:一个矩形蛋糕边长分别为X和Y,须切成N块面积相等的蛋糕。每一切只能平行于一块蛋糕的任意一边,并且必须把这块蛋糕切成两块。因此必须切 N-1 次。求 N块蛋糕的长边与短边的比值的最大值的最小值。X,Y≤10000,N≤10
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define INF 0x3fffffff 5 #define inc(i,j,k) for(double i=j;i<=k;i++) 6 using namespace std; 7 8 double dfs(double x,double y,double cnt){ 9 if(cnt==1)return max(x,y)/min(x,y); double ans=INF; 10 inc(i,1,cnt-1){ 11 ans=min(ans,max(dfs(x/cnt*i,y,i),dfs(x/cnt*(cnt-i),y,cnt-i))); 12 ans=min(ans,max(dfs(x,y/cnt*i,i),dfs(x,y/cnt*(cnt-i),cnt-i))); 13 } 14 return ans; 15 } 16 int x,y,n; 17 int main(){ 18 scanf("%d%d%d",&x,&y,&n); printf("%.6lf",dfs((double)x,(double)y,(double)n)); return 0; 19 }
题解:爆搜,dfs(x,y,cnt)表示要把长为x宽为y的蛋糕切成cnt块,因为只能切在x/cnt或y/cnt的倍数的位置上,所以每次枚举切哪个位置就行了。
20160524
3、bzoj1034 http://www.lydsy.com/JudgeOnline/problem.php?id=1034
题意:n场比赛,知道自己所有选手的能力值和对方所有选手的能力值,能力值大的一定赢。比赛赢一场得2分,平局得1分,输了不得分。对方随机决定选手顺序,你想知道自己最多能得多少分和最少能得多少分。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 200000 6 using namespace std; 7 8 int a[maxn],b[maxn],n,ans,la,ra,lb,rb; 9 int main(){ 10 scanf("%d",&n); inc(i,1,n)scanf("%d",&a[i]); inc(i,1,n)scanf("%d",&b[i]); sort(a+1,a+1+n); sort(b+1,b+1+n); 11 ans=0; la=1; ra=n; lb=1; rb=n; 12 inc(i,1,n){ 13 if(a[ra]>b[rb])ra--,rb--,ans+=2;else if(a[la]>b[lb])la++,lb++,ans+=2;else ans+=(a[la]==b[rb]),la++,rb--; 14 } 15 printf("%d ",ans); swap(a,b); 16 ans=0; la=1; ra=n; lb=1; rb=n; 17 inc(i,1,n){ 18 if(a[ra]>b[rb])ra--,rb--,ans+=2;else if(a[la]>b[lb])la++,lb++,ans+=2;else ans+=(a[la]==b[rb]),la++,rb--; 19 } 20 printf("%d",2*n-ans); return 0; 21 }
题解:贪心。设一个高分方和低分方,将两方选手按能力排好序。如果高分方目前最强能赢低分方目前最强,就让他们比赛;
如果高分方目前最弱能赢低分方目前最弱,也让他们比赛;否则用高分方最弱的和低分方最强的打。开始先让自己方为高分方,求最大值,再让对方做高分方,本方最小值就是2*n-对方得分。
4、bzoj1497 http://www.lydsy.com/JudgeOnline/problem.php?id=1497
题意:N个地方,在i处建立通讯中转站需要的成本为Pi。M个用户,第i个用户会使用中转站Ai和中转站Bi进行通讯,公司可以获益Ci。求净获利最大值。N≤5000,M≤50000
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <queue> 5 #define maxn 60000 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*20]; 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 init(){ 15 ess=-1; memset(g,-1,sizeof(g)); 16 } 17 queue <int> q; int h[maxn]; 18 bool bfs(int s,int t){ 19 memset(h,-1,sizeof(h)); while(!q.empty())q.pop(); h[s]=0; q.push(s); 20 while(! q.empty()){ 21 int x=q.front(); q.pop(); 22 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); 23 } 24 return h[t]!=-1; 25 } 26 int dfs(int x,int t,int f){ 27 if(x==t)return f; int u=0; 28 for(int i=g[x];i!=-1;i=es[i].n)if(es[i].c&&h[es[i].t]==h[x]+1){ 29 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; 30 } 31 if(u==0)h[x]=-1; return u; 32 } 33 int dinic(int s,int t){ 34 int f=0; while(bfs(s,t))f+=dfs(s,t,INF); return f; 35 } 36 int n,m,s,t,tot; 37 int main(){ 38 scanf("%d%d",&n,&m); s=0; t=n+m+1; init(); 39 inc(i,1,n){int a; scanf("%d",&a); pe(s,i,a);} 40 inc(i,1,m){int a,b,c; scanf("%d%d%d",&a,&b,&c); pe(a,n+i,INF); pe(b,n+i,INF); pe(n+i,t,c); tot+=c;} 41 printf("%d",tot-dinic(s,t)); return 0; 42 }
题解:最小割。源点向所有地方连边,流量为建站成本,第Ai个地方和第Bi个地方分别向第i个用户连边,流量无穷,所有用户向汇点连边,流量为获益。这样割源点与地方的连边表示付出成本,割用户与汇点的连边表示放弃利益。最后答案是所有获益和-最小割。
20160525
5、bzoj1878 http://www.lydsy.com/JudgeOnline/problem.php?id=1878
题意:N个数,M个询问求区间[L,R]中包含了多少种不同的数。N≤50000,M≤200000
代码:
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 50100 6 #define lb(x) x&-x; 7 using namespace std; 8 9 int a[maxn],last[maxn*20],next[maxn],sm[maxn],n,m; 10 struct ask{int l,r,ans,id;}; ask asks[maxn*6]; 11 bool cmp1(ask a,ask b){return a.l<b.l;} 12 bool cmp2(ask a,ask b){return a.id<b.id;} 13 inline void update(int x,int v){while(x<=n){sm[x]+=v,x+=lb(x);}} 14 inline int query(int x){int q=0; while(x>0){q+=sm[x],x-=lb(x);} return q;} 15 int main(){ 16 scanf("%d",&n); inc(i,1,n)scanf("%d",&a[i]); 17 scanf("%d",&m); inc(i,1,m)scanf("%d%d",&asks[i].l,&asks[i].r),asks[i].id=i; 18 memset(last,0,sizeof(last)); inc(i,1,n){if(last[a[i]])next[last[a[i]]]=i;else update(i,1); last[a[i]]=i;} 19 sort(asks+1,asks+1+m,cmp1); int l=1; 20 inc(i,1,m){ 21 while(l<asks[i].l){if(next[l])update(next[l],1); l++;} 22 asks[i].ans=query(asks[i].r)-query(asks[i].l-1); 23 } 24 sort(asks+1,asks+1+m,cmp2); inc(i,1,m)printf("%d\n",asks[i].ans); 25 return 0; 26 }
题解:莫队好像可以做~但正解是树状数组。先将询问按左端点排序,并求出每个数的下一个与它相等的数的位置,同时将每个数第一次出现的位置在树状数组中置为1,此时query(x)求出来的就是1到x里有多少个不同的数。枚举排序后的询问,将当前左端点向右移动,每右移一位就将原位置的数的下一个与它相同的数的位置在树状数组中置为1,保证如果这个数在[l,r]中出现不会漏算。当左端点移动到询问的左端点位置时,就输出query(r)-query(l-1),表示l到r里有多少个不同的数。
20160527
6、bzoj1047 http://www.lydsy.com/JudgeOnline/problem.php?id=1047
题意:有一个a*b的整数组成的矩阵,求一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。a,b≤1000,n≤100
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define maxn 1500 5 #define inc(i,j,k) for(int i=j;i<=k;i++) 6 #define INF 2147483647 7 using namespace std; 8 9 int maxh[maxn][maxn],minh[maxn][maxn],maxl[maxn][maxn],minl[maxn][maxn]; 10 int v[maxn][maxn],q1[maxn],q2[maxn],a,b,n,l,r,ans; 11 int main(){ 12 scanf("%d%d%d",&a,&b,&n); inc(i,1,a)inc(j,1,b)scanf("%d",&v[i][j]); 13 inc(i,1,a){ 14 l=1; r=0; 15 inc(j,1,n){while(r>=l&&v[i][j]>q1[r])r--; q1[++r]=v[i][j]; q2[r]=j;} maxh[i][1]=q1[l]; 16 inc(j,n+1,b){ 17 if(j-n>=q2[l])l++; while(r>=l&&v[i][j]>q1[r])r--; 18 q1[++r]=v[i][j]; q2[r]=j; maxh[i][j-n+1]=q1[l]; 19 } 20 } 21 inc(i,1,a){ 22 l=1; r=0; 23 inc(j,1,n){while(r>=l&&v[i][j]<q1[r])r--; q1[++r]=v[i][j]; q2[r]=j;} minh[i][1]=q1[l]; 24 inc(j,n+1,b){ 25 if(j-n>=q2[l])l++; while(r>=l&&v[i][j]<q1[r])r--; 26 q1[++r]=v[i][j]; q2[r]=j; minh[i][j-n+1]=q1[l]; 27 } 28 } 29 inc(j,1,b){ 30 l=1; r=0; 31 inc(i,1,n){while(r>=l&&maxh[i][j]>q1[r])r--; q1[++r]=maxh[i][j]; q2[r]=i;} maxl[1][j]=q1[l]; 32 inc(i,n+1,a){ 33 if(i-n>=q2[l])l++; while(r>=l&&maxh[i][j]>q1[r])r--; 34 q1[++r]=maxh[i][j]; q2[r]=i; maxl[i-n+1][j]=q1[l]; 35 } 36 } 37 inc(j,1,b){ 38 l=1; r=0; 39 inc(i,1,n){while(r>=l&&minh[i][j]<q1[r])r--; q1[++r]=minh[i][j]; q2[r]=i;} minl[1][j]=q1[l]; 40 inc(i,n+1,a){ 41 if(i-n>=q2[l])l++; while(r>=l&&minh[i][j]<q1[r])r--; 42 q1[++r]=minh[i][j]; q2[r]=i; minl[i-n+1][j]=q1[l]; 43 } 44 } 45 ans=INF; 46 inc(i,1,a-n+1)inc(j,1,b-n+1){ 47 ans=min(ans,maxl[i][j]-minl[i][j]); 48 } 49 printf("%d",ans); return 0; 50 }
题解:做4次单调队列。先利用单调队列求出第i行第j列到第i行第j+n-1列的最大最小值,再利用这个求出第i行第j列到第i+n-1行第j+n-1列的最大最小值。最后枚举一下求最小的差就行了。
吐槽:本蒟蒻单调队列开始各种符号写反,比如判断是否要l++的那个条件。以及因为INF设得太小WA了一发,拍都拍不出,最后改成2147483647乱交一发结果过了。
7、bzoj1927 http://www.lydsy.。com/JudgeOnline/problem.php?id=1927
题意:赛车大赛的赛场由N颗行星和M条双向星际航路构成,其中每颗行星都有一个不同的引力值。大赛要求车手们从一颗与这N颗行星之间没有任何航路的天体出发,访问这N颗行星每颗恰好一次。赛车超能电驴在高速航行模式下,沿星际航路航行,但只能由每个星球飞往引力比它大的星球。在能力爆发模式下,超能电驴在经过一段时间的定位之后,能瞬间移动到任意一个行星。求完成比赛最短时间。N≤800,M≤15000
代码:
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 2000 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,m,s,t; 37 int main(){ 38 scanf("%d%d",&n,&m); s=0; t=2*n+1; init(); 39 inc(i,1,n){int a; scanf("%d",&a); pe(s,i+n,1,a);} 40 inc(i,1,m){int a,b,c; scanf("%d%d%d",&a,&b,&c); pe(min(a,b),max(a,b)+n,1,c);} 41 inc(i,1,n)pe(s,i,1,0),pe(i+n,t,1,0); 42 printf("%d",maxflowmincost(s,t)); return 0; 43 }
题解:费用流。对每个点拆成X,Y两个点,源向每个Y点连边,流量为1,费用为对这个行星的定位时间,表示直接经过这个行星。源再向每个X点连边流量1,费用0,每个Y点向汇连边,流量1,费用0。X与Y之间按“星际航路”连边,表示从X点到Y点。我们不关心从哪里到这个行星再到哪里去,我们只考虑每个行星只能经过一次。
吐槽:本智障一开始看不懂任何题解,后来发现自己以为是能力爆发模式需要受引力限制,不审题退役QAQ