综合题题解1

A-网络流来了

题目描述

想哥带着叶姐去了游乐园,有个项目可以让他们在一个m*n (m,n<=30)方格中,取走一些礼物,同时要求任意2个取走的礼物所在方格没有公共边,且取出的礼物让叶姐的满意度最大。

想哥忙于学(lian)习(ai),难以完成,所以求助于你。

时间限制:1000ms,内存限制:65536KB

输入

第1行有2个正整数m和n,分别表示棋盘的行数和列数。

接下来的m行,每行有n个正整数,表示方格中的礼物的满意度。

输出

输出一行,为最大满意度

输入样例

3 3
1 2 3
3 2 3
2 3 1

输出样例

11

AC代码(EK)

#include
#include
#include
#include
#include
#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)

#include
#include
#include
#include
#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;//定义一个bfs寻找分层图时的队列
    while (!q.empty()) q.pop();
    memset(depth,-1,sizeof(depth));
    depth[S]=0;//源点深度为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){
             //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));    // 递归计算顶点 v,用 c(u, v) 来更新当前流量上限
            flow-=tmp;
            e[i].c-=tmp;
            res+=tmp;
            e[i^1].c+=tmp;      // 修改反向弧的容量
            if(flow==0){
             // 流量达到上限,不必继续搜索了
                break;
            }
        }
    }
    if(res==0){
          // 当前没有经过顶点 u 的可行流,不再搜索顶点 u
        depth[u]=-1;
    }
    return res;
}
int dinic(){
             // 函数返回值就是最大流的结果
    int res=0;
    while(bfs()){
     
        res+=dfs(S,INF);    // 初始流量上限为 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处单位时间内最多接受的婚车数量。

输入样例

4 6
1 2 5
1 3 2
1 4 3
2 3 3
2 4 3
3 4 10

输出样例

10

AC代码

#include
#include
#include
#include
#include
#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)

输出

对于每组数据,输出一行,为最多能送出的魔法战斗服的数量。

输入样例

3 4
1 2
3 1 2 3
1 1
0

输出样例

3

AC代码(最大二分匹配)

#include
#include
#include

int ans=0,n,m;
int link[1005],use[1005],map[105][105];//map数组为邻接矩阵,use表示当前点是否匹配,link[i]表示与顶点i所连的点
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;
		}
	}
	/*for(i=1; i<=n; i++) {
		scanf("%d",&a[i]);
		map[i][a[i]]=true;
	}
	for(i=1; i<=n; i++) {
		scanf("%d",&b[i]);
		map[b[i]][i]=true;
	} //输入map*/
	xyl();
	printf("%d\n",ans);// ans为最大匹配数
}

AC代码(贪心)

#include
#include
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说明没有暗恋的人)

输出

对于每组数据,输出一行,为最后剩下的女生的人数

输入样例

3
1 1 1
2 0 1

输出样例

1

AC代码(二分匹配)

#include
#include
#include
#include
#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代码(贪心)

#include
#include
#include
#include
#include
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];
				/*if(woman[i].times>1){
					sort(woman[i].num.begin(),woman[i].num.end(),com);
				} */
				for(int k=1;k<=woman[i].times;k++){
     
					if(woman[i].num[j]<min) min=woman[i].num[j];
				}
				/*if(!flag[woman[i].num[j]]){
					flag[woman[i].num[j]]=1;
					ans++;
					break;
				}*/
				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表示该户居民在街道上相对于某个点的坐标。

输出

输出仅包含一个数,表示最小的覆盖半径,保留一位小数。

输入样例

2 3
1
3
10 

输出样例

1.0

HINT

【样例输出】(在2,10位置上各放一个)

【数据规模】

对于100%的数据,有1 ≤N, M ≤100000,-10000000 ≤Hi ≤10000000。

AC代码

#include
#include
#include
#include
#include
#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



你可能感兴趣的:(算法与数据结构,算法,c++,c语言,数据结构)