A-网络流来了
题目描述
想哥带着叶姐去了游乐园,有个项目可以让他们在一个m*n (m,n<=30)方格中,取走一些礼物,同时要求任意2个取走的礼物所在方格没有公共边,且取出的礼物让叶姐的满意度最大。
想哥忙于学(lian)习(ai),难以完成,所以求助于你。
时间限制:1000ms,内存限制:65536KB
输入
第1行有2个正整数m和n,分别表示棋盘的行数和列数。
接下来的m行,每行有n个正整数,表示方格中的礼物的满意度。
输出
输出一行,为最大满意度
输入样例
输出样例
AC代码(EK)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| #include<cstdio> #include<cstring> #include<iostream> #include<cstdlib> #include<queue> #define INF 2147483647 std::queue<int> q; int n,m,start,end; int a[35][35],map[1000][1000],path[1500],flow[1500]; void insert(int u,int v,int c){ map[u][v]=c; map[v][u]=0; } int EK_bfs() { int i,t; while(!q.empty()) q.pop(); memset(path,-1,sizeof(path)); path[start]=0; flow[start]=INF; q.push(start); while(!q.empty()) { t=q.front(); q.pop(); if(t==end) break; for(i=1; i<=end; i++) { if(i!=start && path[i]==-1 && map[t][i]) { flow[i]=flow[t]<map[t][i]?flow[t]:map[t][i]; q.push(i); path[i]=t; } } } if(path[end]==-1) return -1; return flow[end]; } int EK() { int max_flow=0,step,now,pre; while((step=EK_bfs())!=-1) { max_flow+=step; now=end; while(now!=start) { pre=path[now]; map[pre][now]-=step; map[now][pre]+=step; now=pre; } } return max_flow; } int main(){ int i,j,cnt=0,sum=0; int S,T; scanf("%d%d",&m,&n); S=0,T=n*m+1; start=S,end=T; memset(map,0,sizeof(map)); memset(a,0,sizeof(a)); for(i=1;i<=m;i++){ for(j=1;j<=n;j++){ scanf("%d",&a[i][j]); sum+=a[i][j]; cnt++; if((i+j)%2){ insert(S,cnt,a[i][j]); if(j<n) insert(cnt,cnt+1,INF); if(j>1) insert(cnt,cnt-1,INF); if(i<m) insert(cnt,cnt+n,INF); if(i>1) insert(cnt,cnt-n,INF); } else{ insert(cnt,T,a[i][j]); } } } printf("%d",sum-EK()); }
|
AC代码(Dinic)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| #include<cstdio> #include<cstdlib> #include<cstring> #include<queue> #define INF 2147483647 #define max 1000000 int min(int a,int b){ if(a<b) return a; return b; } struct Edge{ int v; int c; int next; }e[max]; int head[max],e_num=-1; int n,m,S,T;
void add(int u,int v,int c){ e_num++; e[e_num].v=v; e[e_num].c=c; e[e_num].next=head[u]; head[u]=e_num; } void insert(int u,int v,int c){ add(u,v,c); add(v,u,0); } int depth[max]; bool bfs() { std::queue<int> q; while (!q.empty()) q.pop(); memset(depth,-1,sizeof(depth)); depth[S]=0; q.push(S); while(!q.empty()){ int u=q.front(); q.pop(); for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].v; if(e[i].c>0&&depth[v]==-1){ q.push(v); depth[v]=depth[u]+1; } } } return (depth[T]!=-1); } int dfs(int u,int flow){ if(u==T){ return flow; } int res=0; for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].v; if(e[i].c>0&&depth[u]+1==depth[v]){ int tmp=dfs(v,min(flow,e[i].c)); flow-=tmp; e[i].c-=tmp; res+=tmp; e[i^1].c+=tmp; if(flow==0){ break; } } } if(res==0){ depth[u]=-1; } return res; } int dinic(){ int res=0; while(bfs()){ res+=dfs(S,INF); } return res; } int main(){ int a[35][35],sum=0,eid=0; memset(head,-1,sizeof(head)); scanf("%d%d",&m,&n); S=0,T=n*m+1; for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ scanf("%d",&a[i][j]); sum+=a[i][j]; eid++; if((i+j)%2){ insert(S,eid,a[i][j]); if(j<n) insert(eid,eid+1,INF); if(j>1) insert(eid,eid-1,INF); if(i<m) insert(eid,eid+n,INF); if(i>1) insert(eid,eid-n,INF); } else{ insert(eid,T,a[i][j]); } } } printf("%d",sum-dinic()); return 0; }
|
分析
首先要弄明白是怎么化成最大流(最小割)问题的,由题可知,相邻的不让取,因此可以想到分开染色再建图即可,将一部分染成黑色,与其相邻的染成白色,然后将这些看成顶点分开放置,黑色连接源点,边权即黑色点点权(即礼物满意度),白色连接汇点(边权等于点权同理),相邻的黑色和白色连边,边权为INF(最大值),然后因为要让不相邻的满意度和最大,所以即把相邻的满意度和最小求出来即可,而相邻的满意度即对应我们所建图的割(如果想不明白可以自己画图割一割),所以我们要让相邻的满意度和最小,即求最小割即可,所以就是让我们求我们所建图的最大流,最后再用所有满意度之和减去最大流即为答案,所以套板子即可(EK、Dinic均可)。
HINT
需要注意建图时怎样更方便,即相邻染色怎么染的问题。
B-婚车
题目描述
航哥是个土豪,他想在让城市布满他的婚车。但是城市的每条道路单位时间能通过的婚车是有限的,超出则会造成拥堵。他在1号点屯了足够数量的车子,他想知道从城市1号点派出婚车去n号点迎接新娘,在买通交警只允许他的婚车在车道上行驶的条件下,足够多时间之后,n号点单位时间内最多能容纳多少量婚车。
道路都是双向的
时间限制:1000ms,内存限制:65536KB
输入
第一行两个整数,n和m,n为点数,m为边数,点的标号为1~n。
接下来M行,每行三个整数a, b, c, 表示城市中两个点之间有一条单位时间最多通行c辆车的道路。
建图连边之前请注意审题……
1≤n≤1000
1≤m≤100000
1≤a,b≤n,a≠b
1≤c≤10
输出
输出一个整数,点n处单位时间内最多接受的婚车数量。
输入样例
1 2 3 4 5 6 7
| 4 6 1 2 5 1 3 2 1 4 3 2 3 3 2 4 3 3 4 10
|
输出样例
AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<queue> #define INF 2147483647 using namespace std; queue<int> q; int n,m,tend,start; int map[1001][1001],path[1001],flow[1001]; int EK_bfs() { int i,t; while(!q.empty()) q.pop(); memset(path,-1,sizeof(path)); path[start]=0; flow[start]=INF; q.push(start); while(!q.empty()) { t=q.front(); q.pop(); if(t==tend) break; for(i=1; i<=n; i++) { if(i!=start && path[i]==-1 && map[t][i]) { flow[i]=flow[t]<map[t][i]?flow[t]:map[t][i]; q.push(i); path[i]=t; } } } if(path[tend]==-1) return -1; return flow[n]; } int EK() { int max_flow=0,step,now,pre; while((step=EK_bfs())!=-1) { max_flow+=step; now=tend; while(now!=start) { pre=path[now]; map[pre][now]-=step; map[now][pre]+=step; now=pre; } } return max_flow; } int main() { int i,j,a,b,c; scanf("%d%d",&n,&m); tend=n,start=1; memset(map,0,sizeof(map)); for(i=1; i<=m; i++) { scanf("%d%d%d",&a,&b,&c); map[a][b]=c; map[b][a]=c; } printf("%d",EK()); return 0; }
|
分析
从题目描述可以非常清晰的知道是一道间的最大流问题,直接套板子即可(EK,Dinic均可,这里只提供EK),唯一需要注意的就是道路是双向的,即双向边。还有就是这道题EK如果初始化不用memset可能会超时,用Dinic没有任何问题。
C-要成为魔法少女吗
题目描述
酸奶酱是一位魔法少女,并且她很热衷于点化她的其他小伙伴和她一起成为魔法少女。
现在有一个棘手的问题摆在酸奶酱面前——她有M套成为魔法少女不可缺少的魔法战斗服,以及N个想成为魔法少女的小伙伴。魔法战斗服是有灵性的,它有想要跟随的主人。酸奶酱想尽可能多的把更多的魔法战斗服分给她的小伙伴,她现在想知道最多能有几套魔法战斗服能被交到她的小伙伴手里。
注意:一位小伙伴只能拿一件魔法战斗服,一件魔法战斗服也只能交给一位小伙伴。
时间限制:1000ms,内存限制:65536KB
输入
第一行为两个整数N和M,分别表示小伙伴的数量和魔法战斗服的数量。(0<=N,M<=100)
接下来M行,第i行的第一个整数K表示第i件魔法战斗服想要跟随的主人的数量。接下来K个整数num,表示魔法战斗服想要跟随的主人编号。(0<=K,num<=N)
输出
对于每组数据,输出一行,为最多能送出的魔法战斗服的数量。
输入样例
输出样例
AC代码(最大二分匹配)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include<iostream> #include<cstdio> #include<cstring>
int ans=0,n,m; int link[1005],use[1005],map[105][105]; bool dfs(int x) { for(int i=1; i<=n; i++) { if(!use[i]&& map[x][i]) { use[i] = 1; if(!link[i] || dfs(link[i])) { link[i] = x; return true; } } } return false; }
void xyl( ) { memset(link, 0, sizeof(link)); for(int i=1; i<=n; i++) { memset(use,0,sizeof(use)); if(dfs(i)) ans++; } }
int main( ) { int i,k,num; int a[10005],b[10005]; scanf("%d%d",&n,&m); ans=0; memset(map, false, sizeof(map)); for(i=1;i<=m;i++){ scanf("%d",&k); for(int j=1;j<=k;j++){ scanf("%d",&num); map[i][num]=true; } }
xyl(); printf("%d\n",ans); }
|
AC代码(贪心)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include<stdio.h> #include<stdlib.h> int times[105]={}; struct node{ int k; int num[105]; }a[105]; int cmp(const void *a,const void *b){ int sum1=0,sum2=0; struct node c,d; c=*(struct node *)a; d=*(struct node *)b; for(int i=0;i<c.k;i++) sum1+=times[c.num[i]]; for(int i=0;i<d.k;i++) sum2+=times[d.num[i]]; if(c.k!=d.k) return c.k>d.k; else{ return sum1>sum2; } } int cmp2(const void *a,const void *b){ int c,d; c=*(int *)a; d=*(int *)b; return times[c]>times[d]; } int main(){ int n,m,i,j,ans=0,flag[105]={}; scanf("%d%d",&n,&m); for(i=0;i<m;i++){ scanf("%d",&a[i].k); for(j=0;j<a[i].k;j++){ scanf("%d",&a[i].num[j]); times[a[i].num[j]]++; } } qsort(a,m,sizeof(struct node),cmp); for(i=0;i<m;i++){ qsort(a[i].num,a[i].k,sizeof(int),cmp2); } for(i=0;i<m;i++){ for(j=0;j<a[i].k;j++){ if(!flag[a[i].num[j]]){ flag[a[i].num[j]]=1; ans++; break; } } } printf("%d",ans); }
|
分析
很明显这是一道最大二分匹配的问题,还是简简单单套板子即可(匈牙利算法)。
然后后来发现可以用贪心做这道题。首先可以知道如果魔法战斗服只想跟随一个魔法少女,那肯定是要先对它进行分配的,所以首先肯定对魔法战斗服想跟随的魔法少女的数量进行排序。再考虑这种情况:如果魔法战斗服(把它称为a
)最少都有两个想跟随的魔法少女的话,那么该怎么选择呢,肯定需要对想跟随这两个魔法少女的所有魔法战斗服的数量进行排序,并且把a
战斗服给想跟随的魔法战斗服的数量更少的那一位。还有一种情况:两件魔法战斗服想跟随的魔法少女数量相同,这时还是需要对这两件魔法战斗服想跟随的所有魔法少女,求出想跟随她们的所有魔法战斗服的数量总和然后进行排序,对更小的先取即可。所以将上面所有情况考虑即可。(然后本人还了解到有其他贪心的方法,即将魔法少女作为结构体来处理而非魔法战斗服,这里不再赘述,供读者自行思考)
D-SkyLee的脱单大计
题目描述
SkyLee想要脱单,可是他又不想拆散可能在一起的有缘人,毕竟SkyLee是一个善良的人。
SkyLee想知道最理想的情况下,即可能在一起的人数最多时,还有哪些女生仍然是单身。假设学校男女比非常和谐,恰好为1:1
时间限制:1000ms,内存限制:65536kb
输入
多组数据输入
第一行一个整数n,为学校男生数量或女生数量(都一样的啦)保证n<10000
接下来1行,每行n个整数a[i] (表示男生i暗恋的女生编号)
接下来1行,每行n个整数b[i] (表示女生i暗恋的男生编号)
(如果暗恋的人编号为0说明没有暗恋的人)
输出
对于每组数据,输出一行,为最后剩下的女生的人数
输入样例
输出样例
AC代码(二分匹配)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| #include<iostream> #include<cstdio> #include<cstring> #include<queue> #define inf 2147483647
int ans=0,n,dis; int dx[10005],dy[10005],cx[10005],cy[10005]; bool used[10005]; int man[10005]; int woman[10005]; bool searchP() { std::queue<int> q; dis=inf; memset(dx,-1,sizeof(dx)); memset(dy,-1,sizeof(dy)); for(int i=1;i<=n;i++){ if(cx[i]==-1) {q.push(i);dx[i]=0;} } while(!q.empty()) { int u=q.front(); q.pop(); if(dx[u]>dis) break; for(int j=1;j<=n;j++) { if((man[u]==j||woman[j]==u)&&dy[j]==-1){ dy[j]=dx[u]+1; if(cy[j]==-1) dis=dy[j]; else{ dx[cy[j]]=dy[j]+1; q.push(cy[j]); } } } } return dis!=inf; } bool findpath(int x) { for(int j=1;j<=n;j++) { if(!used[j]&&(man[x]==j||woman[j]==x)&&dy[j]==dx[x]+1) { used[j]=1; if(cy[j]!=-1&&dis==dy[j]) continue; if(cy[j]==-1||findpath(cy[j])) { cy[j]=x;cx[x]=j; return true; } } } return false; } int hk() { int ans=0; memset(cx,-1,sizeof(cx)); memset(cy,-1,sizeof(cy)); while(searchP()) { memset(used,0,sizeof(used)); for(int i=1;i<=n;i++){ if(cx[i]==-1) { if(findpath(i)) ans++; } } } return ans; } int main( ) { int i; int a[10005],b[10005]; while(~scanf("%d",&n)) { ans=0; for(i=1; i<=n; i++) { scanf("%d",&a[i]); man[i]=a[i]; } for(i=1; i<=n; i++) { scanf("%d",&b[i]); woman[i]=b[i]; } printf("%d\n",n-hk()); } }
|
AC代码(贪心)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| #include<cstdio> #include<cstdlib> #include<vector> #include<algorithm> #include<cstring> using namespace std; int ttime[10005]={}; struct man{ int times; vector<int> num; }woman[10005]; vector<int> m[10005]; int cmp(const void *a,const void *b){ struct man c,d; int sumc=0,sumd=0; c=*(struct man*)a; d=*(struct man*)b; for(int i=1;i<=c.times;i++){ sumc+=ttime[c.num[i]]; } for(int i=1;i<=d.times;i++){ sumd+=ttime[d.num[i]]; } if(c.times==d.times){ sumc>sumd; } return c.times>d.times; } bool com(int a,int b) { return ttime[a]>ttime[b]; } int main(){ int n,i,j,wom,flag[10005]={}; while(~scanf("%d",&n)){ int ans=0; for(i=1;i<=n;i++){ woman[i].times=0; woman[i].num.clear(); ttime[i]=0; flag[i]=0; } for(i=1;i<=n;i++){ scanf("%d",&wom); if(wom) { ttime[i]++; woman[wom].times++; if(woman[wom].num.empty()){ woman[wom].num.push_back(0); } woman[wom].num.push_back(i); } } for(i=1;i<=n;i++){ scanf("%d",&wom); if(wom) { ttime[wom]++; for(j=0;j<woman[i].num.size();j++){ if(woman[i].num[j]==wom) break; } if(j==woman[i].num.size()){ woman[i].times++; if(woman[i].num.empty()){ woman[i].num.push_back(0); } woman[i].num.push_back(wom); } } } qsort(woman,n,sizeof(struct man),cmp); for(i=1;i<=n;i++){ int min=2147483647; for(int j=1;j<=woman[i].times;j++){ int temp=woman[i].num[j];
for(int k=1;k<=woman[i].times;k++){ if(woman[i].num[j]<min) min=woman[i].num[j]; }
if(!flag[min]){ flag[min]=1; ans++; break; } } } printf("%d\n",n-ans); } }
|
分析
从题目可以看出这是一道非常明显的二分匹配,但是注意给出的n<10000并且内存只有65536KB,如果直接建图,肯定分分钟MLE,所以需要一些技巧,因为从题意可以知道,每个男生只有一个心仪的女生,同样对女生也同样。所以我们可以直接定义两个一维数组(woman[]和man[])即可,这样内存完全够用,然后在判断的时候就不用判断是否为true
了,而是判断man[u]==[v],woman[v]==u
即可。然后还是套板子就可以了(匈牙利、HK均可,这里用的HK)
从上一题的贪心可以知道,这道题肯定是可以贪心解决的,具体就不赘述,一样的道理。只要注意使用vector就可以了。
E-计网的烦恼
题目描述
计网课上有一道题:一条街道安装无线网络,需要放置M个路由器。整条街道上一共有N户居民,分布在一条直线上,每一户居民必须被至少一台路由器覆盖到。现在的问题是所有路由器的覆盖半径是一样的,我们希望用覆盖半径尽可能小的路由器来完成任务,因为这样可以节省成本。
输入
输入第一行包含两个整数M和N,以下N行每行一个整数Hi表示该户居民在街道上相对于某个点的坐标。
输出
输出仅包含一个数,表示最小的覆盖半径,保留一位小数。
输入样例
输出样例
HINT
【样例输出】(在2,10位置上各放一个)
【数据规模】
对于100%的数据,有1 ≤N, M ≤100000,-10000000 ≤Hi ≤10000000。
AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #define INF 2147483647 #define MaxSize 100005 using namespace std; int n, m; int a[MaxSize], B[MaxSize]; int is(double t) { int cur = a[0]; int cnt = 0; for(int i=1;i<n;i++){ if(a[i]-cur>2*t){ cnt++; cur = a[i]; } else continue; } if (cnt<m) return 0; else return 1; } int main() { scanf("%d%d", &m, &n); for (int i=0;i<n;i++) { scanf("%d",&a[i]); } sort(a,a+n); double left = 0, right = 10000000, mid, ans; while (right - left >= 1e-9) { mid= (left + right)/2; if (is(mid)){ left = mid; } else{ right = mid; ans = mid; } } printf("%.1lf", ans); }
|
分析
这是一道很明显的二分题,同时是一维的覆盖。原理如下:我们找一个半径去覆盖所有,如果不能覆盖,则把半径变大,如果已经完全覆盖了,则把半径减小。怎么判断是否已经覆盖了呢,我们同时通过计数器来判断是否到达m
然后如果没有覆盖,则让该坐标最为下次的起始位置即可,详情可查看代码。
题目来源
北航OJ