今天早上学校停电被迫放假半天,当时我可开心伤心坏了。
对,没错,下午又开始考试。。。
这大概又是哪一届学长考过的题?
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-- -++
*/
好了就是这样,欢乐的一天结束了,希望我明天好运(明天好像是考数学,我感觉要遭)
有错欢迎指正(虽然大概率是没人看)