2022.4.30 五一假期快乐的第一天

今天早上学校停电被迫放假半天,当时我可开心伤心坏了。

对,没错,下午又开始考试。。。

这大概又是哪一届学长考过的题?

T1 Pilot

题目描述

飞行大队有若干个来自各地的驾驶员,专门驾驶一种型号的飞机,这种飞机每架有两 个驾驶员,需一个正驾驶员和一个副驾驶员。由于种种原因,例如相互配合的问题,有些 驾驶员不能在同一架飞机上飞行,问如何搭配驾驶员才能使出航的飞机最多。 因为驾驶工作分工严格,两个正驾驶员或两个副驾驶员都不能同机飞行。

输入格式

第一行,两个整数 n 与 m,表示共有 n 个飞行员,其中有 m 名飞行员是正驾驶员。 下面有若干行,每行有 2 个数字 a、b。表示正驾驶员 a 和副驾驶员 b 可以同机飞行。 注:正驾驶员的编号在前,即正驾驶员的编号小于副驾驶员的编号。

输出格式

仅一行一个整数,表示最大起飞的飞机数。

样例输入

10 5

1 7

2 6

2 10

3 7

4 8

5 9

样例输出

4

数据范围与提示

2<=n<=100

分析

这道题很显然是一道二分图最大匹配问题(一个正驾驶员匹配一个副驾驶员)。(机房有大佬贪心写过了)

所以这道题我们选择使用匈牙利算法

奉上我丑陋的代码

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 105;
const int MAXM = 2 * MAXN;

int inpt()
{
	int x = 0, f = 1;
	char ch;
	for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
	if(ch == '-'){
		f = -1;
		ch = getchar();
	}
	do{
		x = (x << 1) + (x << 3) + ch - '0';
		ch = getchar();
	}while(ch >= '0' && ch <= '9');
	return x * f;
}

int n, m;

int hd[MAXN], nxt[MAXM], to[MAXM], tot = 0;
void Add(int x, int y)
{
	nxt[++tot] = hd[x];
	hd[x] = tot;
	to[tot] = y;
}

bool vis[MAXN];
int mat[MAXN], ans = 0;
bool Match(int x)
{
	vis[x] = true;
	for(int i = hd[x]; i; i = nxt[i]){
		int y = to[i];
		if(vis[y]) continue;
		vis[y] = true;
		if(!mat[y] || Match(mat[y])){
			mat[y] = x;
			return true;
		}
	}
	return false;
}

int main()
{
	freopen("pilot.in", "r", stdin);
	freopen("pilot.out", "w", stdout);
	
	n = inpt(), m = inpt();
	int xx, yy;
	while(cin >> xx >> yy){
		Add(xx, yy);
		Add(yy, xx);
	}
	for(int i = 1; i <= m; i++){
		memset(vis, 0, sizeof(vis));
		if(Match(i)) ans++;
	}
	printf("%d", ans);
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T2 Sequence

题目描述

给定正整数序列 x1~xn,以下递增子序列均为非严格递增。

1. 计算其最长递增子序列的长度 s 。

2. 计算从给定的序列中最多可取出多少个长度为 s 的递增子序列。

3. 如果允许在取出的序列中多次使用 x1 和 xn,则从给定序列中最多可取 出多少个长度为 s的递增子序列。

输入格式

文件第 1 行有 1 个正整数 ,表示给定序列的长度。接下来的 1 行有 n 个正整数 x1~xn。

输出格式

第 1 行是最长递增子序列的长度 s。第 2 行是可取出的长度为 s 的递增子序列个数。 第 3 行是允许在取出的序列中多次使用 x1 和 xn 时可取出的长度为 的递增子序列个数。

样例输入

4

3 6 2 5

样例输出

2 2 3

数据范围与提示

1<=n<=500

洛谷题号 P2766

分析 

第一个问题

        很容易可以看出是DP里最基础的LIS问题。我们记下以 i 结尾的最长不下降子序列长度f [ i ]。

第二个问题

        因为要求所有的位置都不能重复,所以我们类比第一题的二分图匹配,只不过这里不再是两两匹配,而是ss匹配。而二分图匹配可以用网络流做 ,那么ss匹配当然也可以。

        我们假定原序列为a [ i ](虽然题面写的是x) 。

        我们把每一个数当做一个树上的点, 因为每个点都只能经过一次,所以我们把每一个点 i 拆开成为两个点(我考试的时候没想到,然后就挂~掉~啦~),这里方便就记为 i 和 i + n ,在两个点上连接一条流量为1的边(当然也需要一条流量为0的反向边)。每一个点 i 连向一个点 j 有且仅当满足i > j 且 a [ i ] > a [ j ] 且 f [ i ] == f [ j ] + 1时, 因为这样才能保证是以当前位置结尾的单调不下降子序列。这里我们只需要给 i + n 和 j 之间连一条流量为 1 的边即可。        

        然后我们新建一个虚拟的源点 s 和汇点 t 这里假定为 0 和 2 * n + 1。源点向每一个 f [ i ] == s 的点连一条流量为 1 的边,汇点向每一个 f [ i ] == 1 的点连一条流量为 1 的边(当然这个过程可以反过来,对应的前面的步骤也需要反向)。

        最后,我们从源点向汇点跑一遍最大流就可以啦,这里我们选用的是DInic算法(我考场上用的是EK,甚至还 T 了一个点)。

第三个问题

        因为 a[ 1 ](x1) 和 a [ n ] (xn)可以重复使用了,所以我们只需要把原边重置,再把 1 和 1 + n ,n 和 n + n 之间的和 源点与n + n 和 汇点与1 之间的流量变为无穷大再跑一次最大流就可以了(我的代码中是重新建边的写法,考场上脑子抽了,后来直接沿用的考场代码)。

记得特判,代码里有标注

代码

奉上我丑陋的代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 505 * 2;
const int MAXM = 4 * MAXN;
const int INF = 0x3f3f3f3f;

int inpt()
{
	int x = 0, f = 1;
	char ch;
	for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
	if(ch == '-'){
		f = -1;
		ch = getchar();
	}
	do{
		x = (x << 1) + (x << 3) + ch - '0';
		ch = getchar();
	}while(ch >= '0' && ch <= '9');
	return x * f;
}

int n;
int a[MAXN];

int f[MAXN], s = 1;

int hd[MAXN], nxt[MAXM], to[MAXM], w[MAXM], tot = 1;
void Add(int x, int y, int z)
{
	nxt[++tot] = hd[x];
	hd[x] = tot;
	to[tot] = y;
	w[tot] = z;
}

int ps, pt;

int dpt[MAXN], nw[MAXM];
int MaxFlow = 0;
bool bfs()
{
	memset(dpt, 0, sizeof(dpt));
	queue<int> q;
	q.push(ps);
	dpt[ps] = 1;
	nw[ps] = hd[ps];
	while(q.size()){
		int x = q.front();
		q.pop();
		for(int i = hd[x]; i; i = nxt[i]){
			int y = to[i];
			if(w[i] && !dpt[y]){
				q.push(y);
				nw[y] = hd[y];
				dpt[y] = dpt[x] + 1;
				if(y == pt){
					return true;
				}
			}
		}
	}
	return false;
}
int Dinic(int x, int Flow)
{
	if(x == pt){
		return Flow;
	}
	int Rest = Flow;
	int i;
	for(i = nw[x]; i && Rest; i = nxt[i]){
		int y = to[i];
		if(w[i] && dpt[y] == dpt[x] + 1){
			int k = Dinic(y, min(Rest, w[i]));
			if(!k){
				dpt[y] = 0;
			}
			w[i] -= k;
			w[i ^ 1] += k;
			Rest -= k;
		}
	}
	nw[i] = i;
	return Flow - Rest;
}

int ans1, ans2;

int main()
{
	freopen("sequence.in", "r", stdin);
	freopen("sequence.out", "w", stdout);

	n = inpt();
	for(int i = 1; i <= n; i++){
		a[i] = inpt();
		f[i] = 1;
	}
    //LIS
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= i - 1; j++){
			if(a[i] >= a[j]){
				f[i] = max(f[i], f[j] + 1);
				s = max(s, f[i]);
			}
		}
	}
	
    //第一次
	memset(nw, 0, sizeof(nw));
	memset(dpt, 0, sizeof(dpt));
	for(int i = 1; i <= n; i++){
		Add(i, i + n, 1);
		Add(i + n, i, 0);
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= i - 1; j++){
			if(a[i] >= a[j] && f[j] == f[i] - 1){
				Add(i + n, j, 1);
				Add(j, i + n, 0);
			}
		}
	}//0源 2 * n + 1 汇
	for(int i = 1; i <= n; i++){
		if(f[i] == s){
			Add(0, i, 1);
			Add(i, 0, 0); 
		}
		if(f[i] == 1){
			Add(i + n, 2 * n + 1, 1);
			Add(2 * n + 1, i, 0);
		}
	}
	ps = 0, pt = 2 * n + 1;
	while(bfs()){
		int Flow;
		while(Flow = Dinic(ps, INF)){
			MaxFlow += Flow;
		}
	}
	int ans1 = MaxFlow;
	
    //第二次
	MaxFlow = 0;
	tot = 1;
	memset(hd, 0, sizeof(hd));//其实只在原图上在需要的位置多加一条
	memset(nxt, 0, sizeof(nxt));//流量为INF的边或者直接修改再跑就行 
	memset(to, 0, sizeof(to));//记得还得重置边?
	memset(w, 0, sizeof(w));
	memset(nw, 0, sizeof(nw));
	memset(dpt, 0, sizeof(dpt));//当然这里有些memset是废的
	for(int i = 1; i <= n; i++){
		if(i == 1 || i == n){
			Add(i, i + n, INF);//加无限边
			Add(i + n, i, 0);
		}else{
			Add(i, i + n, 1);
			Add(i + n, i, 0);
		}
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= i - 1; j++){
			if(a[i] >= a[j] && f[j] == f[i] - 1){
				Add(i + n, j, 1);
				Add(j, i + n, 0);
			}
		}
	}//0源 2 * n + 1 汇
	for(int i = 1; i <= n; i++){
		if(f[i] == s){
			if(i == n){
				Add(0, i, INF);//加无限边
				Add(i, 0, 0);
			}else{
				Add(0, i, 1);
				Add(i, 0, 0); 
			}
		}
		if(f[i] == 1){
			if(i == 1){
				Add(i + n, 2 * n + 1, INF);
				Add(2 * n + 1, i + n, 0);
			}else{
				Add(i + n, 2 * n + 1, 1);
				Add(2 * n + 1, i + n, 0);
			}
		}
	}
	while(bfs()){
		int Flow;
		while(Flow = Dinic(ps, INF)){
			MaxFlow += Flow;
		}
	}
	int ans2 = MaxFlow;
	
	if(n == 1){
		printf("1\n1\n1");//你猜为什么要加这一句
	}else{
		printf("%d\n%d\n%d", s, ans1, ans2);
	}

	fclose(stdin);
	fclose(stdout);
	return 0;
}
/*
4
3 6 2 5

6
3 6 2 5 8 7
*/

 Update 2022.5.1

给大家贴一下杰哥的简洁代码

#include<bits/stdc++.h>
using namespace std;
const int INF = INT_MAX;

inline int read() {
	char ch = getchar();
	int res = 0;
	while (ch < '0' || ch > '9') ch = getchar();
	while (ch >= '0' && ch <= '9') {
		res = (res << 3) + (res << 1) + (ch ^ 48);
		ch = getchar();
	}
	return res;
}

int n, a[1505];
int dp[1505];

struct edge {
	int v, flow, nxt;
}e[5000005];
int head[1505], cnt, s, t;
int dep[1505], maxflow = 0;

void addedge(int u, int v, int flow) {
	e[cnt] = {v, flow, head[u]};
	head[u] = cnt++;
}

int bfs() {
	memset(dep, 0, sizeof(dep));
	deque<int> q;
	q.push_back(s);
	dep[s] = 1;
	while (!q.empty()) {
		int u = q.front(); q.pop_front();
		for (int i = head[u]; ~i; i = e[i].nxt) {
			int v = e[i].v;
			if (dep[v] || !e[i].flow) continue;
			dep[v] = dep[u] + 1;
			q.push_back(v);
		}
	} 
	return dep[t];
}

int dfs(int u, int limit) {
	if (u == t || !limit) return limit;
	int flow = 0, k;
	for (int i = head[u]; ~i; i = e[i].nxt) {
		int v = e[i].v, w = e[i].flow;
		if (dep[v] == dep[u] + 1 && (k = dfs(v, min(limit, w)))) {
			e[i].flow -= k;
			e[i ^ 1].flow += k;
			limit -= k;
			flow += k;
		}
	}
	return flow;
}

void dinic() {
	maxflow = 0;
	int k;
	while (bfs()) {
		while (k = dfs(s, INF)) maxflow += k;
	}
}

bool cgval(int u, int v, int val) {
	for (int i = head[u]; ~i; i = e[i].nxt) {
		if (e[i].v == v) {
			e[i].flow = val;
			e[i ^ 1].flow = 0;
			return true;
		}
	}
	return false;
}

int main() {
//	freopen("sequence.in", "r", stdin);
//	freopen("sequence.out", "w", stdout);
	memset(head, -1, sizeof(head));
	n = read();
	for (int i = 1; i <= n; i++) a[i] = read();
	int len = 1; dp[1] = 1;
	for (int i = 2; i <= n; i++) {
		dp[i] = 1;
		for (int j = 1; j <= i - 1; j++) {
			if (a[j] <= a[i]) dp[i] = max(dp[i], dp[j] + 1);
		}
		len = max(len, dp[i]);
	}
	printf("%d\n", len);
	for (int i = 1; i <= n; i++) {
		addedge(i, i + n, 1); addedge(i + n, i, 0); 
		for (int j = i + 1; j <= n; j++) {
			if (a[i] <= a[j] && dp[j] == dp[i] + 1) {
				addedge(i + n, j, 1); addedge(j, i + n, 0); 
			}
		}
	}
	s = 0, t = 2 * n + 1;
	for (int i = 1; i <= n; i++) {
		if (dp[i] == 1) addedge(s, i, 1), addedge(i, s, 0); 
		if (dp[i] == len) addedge(i + n, t, 1), addedge(t, i + n, 0);
	}
	dinic();
	printf("%d\n", maxflow);
	for (int i = 0; i <= cnt; i += 2) {
		e[i].flow = 1;
		e[i ^ 1].flow = 0;
	}
	cgval(s, 1, INF);
	cgval(1, 1 + n, INF);
	cgval(n, n + n, INF);
	cgval(n + n, t, INF);
	dinic();
	if(n == 1) {
		printf("1");
	}
	else {
		printf("%d\n", maxflow);
	}
	
}

T3 Software

题目描述

某公司发现其研制的一个软件中有 n 个错误,随即为该软件发放了一批共 m 个补丁程 序。每一个补丁程序都有其特定的适用环境,某个补丁只有在软件中包含某些错误而同时 又不包含另一些错误时才可以使用。一个补丁在排除某些错误的同时,往往会加入另一些 错误。 换句话说,对于每一个补丁 i ,都有 2 个与之相应的错误集合 B1(i)和 B2(i),使得仅 当软件包含 B1(i)中的所有错误,而不包含 B2(i)中的任何错误时,才可以使用补丁 i。补丁 i 将修复软件中的某些错误 F1(i),而同时加入另一些错误 F2(i)。另外,每个补丁都耗费一 定的时间。 试设计一个算法,利用公司提供的 m 个补丁程序将原软件修复成一个没有错误的软 件,并使修复后的软件耗时最少。

输入格式

文件第 1 行有 2 个正整数 n 和 m ,n 表示错误总数,m 表示补丁总数。接下来 m 行 给出了 m 个补丁的信息。每行包括一个正整数,表示运行补丁程序 i 所需时间,以及 2 个 长度为 n 的字符串,中间用一个空格符隔开。 第 1 个字符串中,如果第 k 个字符 bk 为 +,则表示第 k 个错误属于 B1(i)。若为 -, 则表示第 k 个错误属于 B2(i) ,若为 0,则第 i 个错误既不属于 B1(i) 也不属于 B2(i) ,即软 件中是否包含第 k 个错误并不影响补丁 i 的可用性。 第 2 个字符串中,如果第 k 个字符 bk 为 -,则表示第 k 个错误属于 F1(i) ,若为 +, 则表示第 k 个错误属于 F2(i),若为 0,则第 i 个错误既不属于 F1(i) 也不属于 F2(i),即软 件中是否包含第 k 个错误不会因使用补丁 i 而改变。

输出格式

输出最小耗时,如果问题无解,则输出 0。

样例输入

3 3

1 000 00-

1 00- 0-+

2 0-- -++

样例输出

8

数据范围与提示

1<=n<=20, 1<=m<=100

洛谷题号 P2761

分析 

这道题我考场上瓜了,完全没有思路。

经过杰哥(机房大佬)的讲解后我如醍醐灌顶,大致思路如下:

因为错误数量小于 20 个所以我们可以考虑用二进制压缩的方式来表示状态,1 表示有错,0 表示无错,于是显然初始状态就是全是1。

因为是要求最小时间花费,所以我们可以以状态为点,以补丁包为边,以时间为边权跑最短路。一条边可以走的条件就是题面中提到的那样,我们同样可以预处理每一个补丁包的四个数组为二进制状态,这样我们就可以 O(1) 判断这条边是否能走了。这条边所连接的点就是按照题面描述处理后的状态。

这里我们选择使用的Dijsktra算法,SPFA好像也可以?

代码

奉上我丑陋的代码

#include<bits/stdc++.h>

const int MAXN = 105;
const int MAXM = 25;
const int MAXK = (1 << 20) + 5;

using namespace std;
int inpt()
{
	int x = 0, f = 1;
	char ch;
	for(ch = getchar(); (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
	if(ch == '-'){
		f = -1;
		ch = getchar();
	}
	do{
		x = (x << 1) + (x << 3) + ch - '0';
		ch = getchar();
	}while(ch >= '0' && ch <= '9');
	return x * f;
}

int n, m;
int t[MAXN];
int b1[MAXN], b2[MAXN], f1[MAXN], f2[MAXN];
int AllWrong;

struct node{
	int id;
	int val;
	bool operator < (node qwq)const{
		return val > qwq.val;
	}
};
int d[MAXK];
bool vis[MAXK];
void Dijkstra(int Start)
{
	memset(d, 0x3f, sizeof(d));
	memset(vis, false, sizeof(vis));
	d[Start] = 0;
	priority_queue<node> q;
	q.push((node){Start, 0});
	while(q.size()){
		int x = q.top().id;
		q.pop();
		if(vis[x]) continue;
		vis[x] = true;
		for(int i = 1; i <= m; i++){
			if((x & b1[i]) != b1[i] || x & b2[i]) continue;//判断转移是否合法
			int y = (x & (~f1[i])) | f2[i], z = t[i];//转移
			if(vis[y]) continue;
			if(d[y] > d[x] + z){
				d[y] = d[x] + z;
				q.push((node){y, d[y]});
			}
		} 
	}
}

int main()
{
	freopen("software.in", "r", stdin);
	freopen("software.out", "w", stdout);

	n = inpt(), m = inpt();
	AllWrong = (1 << n) - 1;//初始状态
	for(int i = 1; i <= m; i++){
		t[i] = inpt();
		char ch[MAXM];
		scanf("%s", ch + 1);
		for(int j = 1; j <= n; j++){
			if(ch[j] == '+'){
				b1[i] |= 1 << (j - 1);//预处理
			}else{
				if(ch[j] == '-'){
					b2[i] |= 1 << (j - 1);
				}
			}
		}
		scanf("%s", ch + 1);
		for(int j = 1; j <= n; j++){
			if(ch[j] == '-'){
				f1[i] |= 1 << (j - 1);
			}else{
				if(ch[j] == '+'){
					f2[i] |= 1 << (j - 1);
				}
			}
		} 
	}
    
	Dijkstra(AllWrong);//最短路
    
	if(d[0] != 0x3f3f3f3f){//判断有解
		printf("%d", d[0]);
	}else{
		printf("0");
	}

	fclose(stdin);
	fclose(stdout);
	return 0;
}
/*
3 3
1 000 00-
1 00- 0-+
2 0-- -++
*/

好了就是这样,欢乐的一天结束了,希望我明天好运(明天好像是考数学,我感觉要遭)

有错欢迎指正(虽然大概率是没人看)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值