今日小结:今天主要复习了一下二分图,题目做的不是太多,因为下午和晚上用了些时间来准备明天的演讲。

一. 完成的题目:

洛谷P1640,洛谷P1894,洛谷P2071,洛谷P1402,洛谷P2756,SP2713

二.

1.当日完成题目数:6道。

2. 未完成6个题目的原因:

3. 复习的知识点:线段树,二分图匹配。

4.不会题目:洛谷P1231

三:

1. 洛谷P1640 [SCOI2010]连续攻击游戏

题目描述

\(lxhgww\)最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有\(2\)个属性,这些属性的值用\([1,10000]\)之间的数表示。当他使用某种装备时,他只能使用该装备的某一个属性。并且每种装备最多只能使用一次。游戏进行到最后,\(lxhgww\)遇到了终极\(boss\),这个终极\(boss\)很奇怪,攻击他的装备所使用的属性值必须从\(1\)开始连续递增地攻击,才能对\(boss\)产生伤害。也就是说一开始的时候,\(lxhgww\)只能使用某个属性值为\(1\)的装备攻击\(boss\),然后只能使用某个属性值为\(2\)的装备攻击\(boss\),然后只能使用某个属性值为\(3\)的装备攻击\(boss……\)以此类推。现在\(lxhgww\)想知道他最多能连续攻击\(boss\)多少次?

输入输出格式

输入格式:

输入的第一行是一个整数\(N\),表示\(lxhgww\)拥有\(N\)种装备接下来\(N\)行,是对这\(N\)种装备的描述,每行\(2\)个数字,表示第i种装备的\(2\)个属性值

输出格式:

输出一行,包括\(1\)个数字,表示\(lxhgww\)最多能连续攻击的次数。

输入输出样例

输入样例#1:

3
1 2
3 2
4 5

输出样例#1:

2

说明

\(Limitation\)

对于\(30\%\)的数据,保证\(N < =1000\)

对于\(100\%\)的数据,保证\(N < =1000000\)

来源:SCOI 2010

思路:

二分图的最大匹配问题,做法很巧妙,但是很难想到。

第一眼看到这个题想到的是将某个物品的两个属性分成左右部点,但是很难解决本题,尤其是在处理一个物品只能用一种属性的时候。所以我们不妨换一种思路,对于物品i的属性 \(a,b\),分别从\(a\)\(b\)\(i\)连一条有向边。将物品的属性当做左部点,编号当做右部点,求最大匹配即可。

这样为什么是正确的呢?我们可以考虑匈牙利算法的具体过程:在匹配值为i的技能时,那么\(1\)\(i-1\)的属性肯定已经匹配完成,所以如果\(i\)对应的编号\(j\)被匹配了的话,那么就让匹配\(j\)的那个属性\(p\)再去找别的物品标号匹配,形象地说,就是用别的物品来释放攻击力为\(p\)的这个技能,用\(j\)这个物品释放攻击力为i的技能。如果找到这样一条增广路,那么就说明当前可以匹配,\(ans++\)

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cctype>
#define maxn 1000007
using namespace std;
int n,link[maxn],num,head[maxn],zrj,maxx;
bool vis[maxn];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,nxt;
}e[maxn<<1];
inline void ct(int u, int v) {
  e[++num].v=v;
  e[num].nxt=head[u];
  head[u]=num;
}
bool dfs(int u) {
  for(int i=head[u];i;i=e[i].nxt) {
    int v=e[i].v;
    if(!vis[v]) {
      vis[v]=1;
      if(!link[v]||dfs(link[v])) {
        link[v]=u;
        return 1;
      }
    }
  }
  return 0;
}
int main() {
  n=qread();
  for(int i=1,u,v;i<=n;++i) {
    u=qread(),v=qread();
    ct(u,i),ct(v,i);
    maxx=max(maxx,max(u,v));
  }
  for(int i=1;i<=maxx;++i) {
    memset(vis,0,sizeof(vis));
    if(dfs(i)) ++zrj;
    else break;
  }
  printf("%d\n",zrj);
  return 0;
}

2. 洛谷 P1894 [USACO4.2]完美的牛栏The Perfect Stall

题目描述

农夫约翰上个星期刚刚建好了他的新牛棚,他使用了最新的挤奶技术。不幸的是,由于工程问题,每个牛栏都不一样。第一个星期,农夫约翰随便地让奶牛们进入牛栏,但是问题很快地显露出来:每头奶牛都只愿意在她们喜欢的那些牛栏中产奶。上个星期,农夫约翰刚刚收集到了奶牛们的爱好的信息(每头奶牛喜欢在哪些牛栏产奶)。一个牛栏只能容纳一头奶牛,当然,一头奶牛只能在一个牛栏中产奶。

给出奶牛们的爱好的信息,计算最大分配方案。

输入输出格式

输入格式:

第一行 两个整数,N (0 <= N <= 200) 和 M (0 <= M <= 200) 。N 是农夫约翰的奶牛数量,M 是新牛棚的牛栏数量。

第二行到第N+1行 一共 N 行,每行对应一只奶牛。第一个数字 (Si) 是这头奶牛愿意在其中产奶的牛栏的数目 (0 <= Si <= M)。后面的 Si 个数表示这些牛栏的编号。牛栏的编号限定在区间 (1..M) 中,在同一行,一个牛栏不会被列出两次。

输出格式:

只有一行。输出一个整数,表示最多能分配到的牛栏的数量.

输入输出样例

输入样例#1:

5 5
2 2 5
3 2 3 4
2 1 5
3 1 2 5
1 2

输出样例#1:

4

说明

N (0 <= N <= 200)

M (0 <= M <= 200)

思路:二分图最大匹配——匈牙利算法板子题,就不用多说了吧。直接看代码。

代码:

#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#define maxn 207
using namespace std;
int n,link[maxn],head[maxn],num,zrj,m,k,x;
bool vis[maxn];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,nxt;
}e[100007];
inline void ct(int u, int v) {
  e[++num].v=v;
  e[num].nxt=head[u];
  head[u]=num;
} 
bool dfs(int u) {
  for(int i=head[u];i;i=e[i].nxt) {
    int v=e[i].v;
    if(!vis[v]) {
      vis[v]=1;
      if(!link[v]||dfs(link[v])) {
        link[v]=u;
        return 1;
      }
    }
  }
  return 0;
}
int main() {
  n=qread(),m=qread();
  for(int i=1;i<=n;++i) {
    k=qread();
    while(k--) {
      x=qread();
      ct(i,x);
    }
  }
  for(int i=1;i<=n;++i) {
    memset(vis,0,sizeof(vis));
    if(dfs(i)) ++zrj;
  }
  printf("%d\n",zrj);
  return 0;
}

3. 洛谷P2071 座位安排

题目背景

公元二零一四年四月十七日,小明参加了省赛,在一路上,他遇到了许多问题,请你帮他解决。

题目描述

已知车上有N排座位,有N*2个人参加省赛,每排座位只能坐两人,且每个人都有自己想坐的排数,问最多使多少人坐到自己想坐的位置。

输入输出格式

输入格式:

第一行,一个正整数N。

第二行至第N*2+1行,每行两个正整数Si1,Si2,为每个人想坐的排数。

输出格式:

一个非负整数,为最多使得多少人满意。

输入输出样例

输入样例#1:

4
1 2
1 3
1 2
1 3
1 3
2 4
1 3
2 3

输出样例#1:

7

说明

对于10%的数据 N≤10

对于30%的数据 N≤50

对于60%的数据 N≤200

对于100%的数据 N≤2000

算法提示:二分图的最大匹配

思路:看完这道题目后第一感觉就是二分图最大匹配,而且题目中也提示了……仔细读题发现,这道题与普通二分图匹配题目唯一的区别在于这道题目第二部分的一个点可以与第一部分的两个点匹配,我们把\(link\)数组开成二维就好了,分情况讨论一下。

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cctype>
#define maxn 4007
using namespace std;
int n,link[maxn][2],head[maxn],num,zrj;
bool vis[maxn];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,nxt;
}e[maxn<<1];
inline void ct(int u, int v) {
  e[++num].v=v;
  e[num].nxt=head[u];
  head[u]=num;
} 
bool dfs(int u) {
  for(int i=head[u];i;i=e[i].nxt) {
    int v=e[i].v;
    if(!vis[v]) {
      vis[v]=1;
      if(!link[v][0]||dfs(link[v][0])) {
        link[v][0]=u;
        return 1;
      }
      if(!link[v][1]||dfs(link[v][1])) {
        link[v][1]=u;
        return 1;
      }
    }
  }
  return 0;
}
int main() {
  n=qread();
  for(int i=1,u,v;i<=n<<1;++i) {
    u=qread(),v=qread();
    ct(i,u),ct(i,v);
  }
  for(int i=1;i<=n<<1;++i) {
    memset(vis,0,sizeof(vis));
    if(dfs(i)) ++zrj;
  }
  printf("%d\n",zrj);
  return 0;
}

4. 洛谷P1402 酒店之王

题目描述

XX酒店的老板想成为酒店之王,本着这种希望,第一步要将酒店变得人性化。由于很多来住店的旅客有自己喜好的房间色调、阳光等,也有自己所爱的菜,但是该酒店只有p间房间,一天只有固定的q道不同的菜。

有一天来了n个客人,每个客人说出了自己喜欢哪些房间,喜欢哪道菜。但是很不幸,可能做不到让所有顾客满意(满意的条件是住进喜欢的房间,吃到喜欢的菜)。

这里要怎么分配,能使最多顾客满意呢?

输入输出格式

输入格式:

第一行给出三个正整数表示n,p,q(<=100)。

之后n行,每行p个数包含0或1,第i个数表示喜不喜欢第i个房间(1表示喜欢,0表示不喜欢)。

之后n行,每行q个数,表示喜不喜欢第i道菜。

输出格式:

最大的顾客满意数。

输入输出样例

输入样例#1:

2 2 2
1 0
1 0
1 1
1 1

输出样例#1:

1

思路:正解是最大流,但是我不会……那怎么办呢???其实,这道题亦可用二分图做。怎么个分法呢?我们发现,如果说一个人喜欢一个房间的同时也喜欢这个房间里的菜的话,那这个题就是裸的二分图最大匹配了,对吧?但是,这道题,并不是,而是如果有一个不喜欢,那就不能匹配,那我们可以建两个二分图啊,对不对?如果两个二分图都能够匹配,则\(ans\)++,但是不能呢?我们发现,我们\(dfs\)找增广路的时候,\(link\)数组是会影响到后面的状态的,所以如果一个人无法匹配一个房间,我们要把\(link\)数组还原,额外开两个数组记录就可以解决了。

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cctype>
#define maxn 207
using namespace std;
int n,p,q,link[maxn][2],num,zrj,head1[maxn],a[maxn],b[maxn],cnt,head2[maxn];
bool vis1[maxn],vis2[maxn];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,nxt;
}e1[maxn*maxn],e2[maxn*maxn];
inline void ct1(int u, int v) {
  e1[++num].v=v;
  e1[num].nxt=head1[u];
  head1[u]=num;
}
inline void ct2(int u, int v) {
  e2[++cnt].v=v;
  e2[cnt].nxt=head2[u];
  head2[u]=cnt;
}
bool dfs1(int u) {
  for(int i=head1[u];i;i=e1[i].nxt) {
    int v=e1[i].v;
    if(!vis1[v]) {
      vis1[v]=1;
      if(!link[v][0]||dfs1(link[v][0])) {
        link[v][0]=u;
        return 1;
      }
    }
  }
  return 0;
}
bool dfs2(int u) {
  for(int i=head2[u];i;i=e2[i].nxt) {
    int v=e2[i].v;
    if(!vis2[v]) {
      vis2[v]=1;
      if(!link[v][1]||dfs2(link[v][1])) {
        link[v][1]=u;
        return 1;
      }
    }
  }
  return 0;
}
int main() {
  n=qread(),p=qread(),q=qread();
  for(int i=1;i<=n;++i) {
    for(int j=1,x;j<=p;++j) {
      x=qread();
      if(x) ct1(i,j);
    } 
  }
  for(int i=1;i<=n;++i) {
    for(int j=1,x;j<=q;++j) {
      x=qread();
      if(x) ct2(i,j);
    } 
  }
  for(int i=1;i<=n;++i) {
    memset(vis1,0,sizeof(vis1));
    memset(vis2,0,sizeof(vis2));
    for(int j=1;j<=n;++j) a[j]=link[j][0],b[j]=link[j][1];
    if(dfs1(i)&&dfs2(i)) ++zrj;
    else for(int j=1;j<=n;++j) 
    link[j][0]=a[j],link[j][1]=b[j];
  }
  printf("%d\n",zrj);
  return 0;
}

5. 洛谷P2756 飞行员配对方案问题

题目背景

第二次世界大战时期..

题目描述

英国皇家空军从沦陷国征募了大量外籍飞行员。由皇家空军派出的每一架飞机都需要配备在航行技能和语言上能互相配合的2 名飞行员,其中1 名是英国飞行员,另1名是外籍飞行员。在众多的飞行员中,每一名外籍飞行员都可以与其他若干名英国飞行员很好地配合。如何选择配对飞行的飞行员才能使一次派出最多的飞机。对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。

对于给定的外籍飞行员与英国飞行员的配合情况,编程找出一个最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。

输入输出格式

输入格式:

第 1 行有 2 个正整数 m 和 n。n 是皇家空军的飞行员总数(n<100);m 是外籍飞行员数(m<=n)。外籍飞行员编号为 1~m;英国飞行员编号为 m+1~n。

接下来每行有 2 个正整数 i 和 j,表示外籍飞行员 i 可以和英国飞行员 j 配合。最后以 2个-1 结束。

输出格式:

第 1 行是最佳飞行员配对方案一次能派出的最多的飞机数 M。接下来 M 行是最佳飞行员配对方案。每行有 2个正整数 i 和 j,表示在最佳飞行员配对方案中,飞行员 i 和飞行员 j 配对。如果所求的最佳飞行员配对方案不存在,则输出‘No Solution!’。

输入输出样例

输入样例#1:

5 10
1 7
1 8
2 6
2 9
2 10
3 7
3 8
4 7
4 8
5 10
-1 -1

输出样例#1:

4
1 7
2 9
3 8
5 10

思路:一个二分图最大匹配的板子题,跟普通的没什么区别,就不必多说了吧。直接看代码。

代码:

#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#define maxn 107
using namespace std;
int n,m,head[maxn],num,link[maxn],x,y,zrj;
bool vis[maxn];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,nxt;
}e[maxn*maxn];
inline void ct(int u, int v) {
  e[++num].v=v;
  e[num].nxt=head[u];
  head[u]=num;
} 
bool dfs(int u) {
  for(int i=head[u];i;i=e[i].nxt) {
    int v=e[i].v;
    if(!vis[v]) {
      vis[v]=1;
      if(!link[v]||dfs(link[v])) {
        link[v]=u;
        return 1;
      }
    }
  }
  return 0;
}
int main() {
  n=qread(),m=qread();
  while(1) {
    x=qread(),y=qread();
    if(x==-1&&y==-1) break;
    ct(x,y);
  }
  for(int i=1;i<=n;++i) {
    memset(vis,0,sizeof(vis));
    if(dfs(i)) ++zrj;
  }
  printf("%d\n",zrj);
  for(int i=1;i<=m;++i) {
    if(link[i]) printf("%d %d\n",i,link[i]);
  }
  return 0;
}

6. SP2713 GSS4 - Can you answer these queries IV

题目大意

\(n\) 个数,和在\(10^{18}\)范围内。

也就是\(\sum~a_i~\leq~10^{18}\)

现在有两种操作

0 x y 把区间[x,y]内的每个数开方,下取整

1 x y 询问区间[x,y]的每个数的和

格式: 有多组数据,数据以EOF结束,对于每组数据,输出数据的序号,每组数据之后输出一个空行。

注意: 不保证给出的区间[x, y]有\(x <= y\),如果\(x>y\)请交换\(x\)\(y\)

感谢@Edgration 提供的翻译

输入输出格式

输入格式:

Multiple test cases, please proceed them one by one. Input terminates by EOF.

For each test case:

The first line contains an integer N. The following line contains N integers, representing the sequence A {1}1​..A {N}N​ .
The third line contains an integer M. The next M lines contain the operations in the form "i x y".i=0 denotes the modify operation, i=1 denotes the query operation.

输出格式:

For each test case:

Output the case number (counting from 1) in the first line of output. Then for each query, print an integer as the problem required.

Print an blank line after each test case.

See the sample output for more details.

输入输出样例

输入样例#1:

5
1 2 3 4 5
5
1 2 4
0 2 4
1 2 4
0 4 5
1 1 5
4
10 10 10 10
3
1 1 4
0 2 3
1 1 4

输出样例#1:

Case #1:
9
4
6

Case #2:
40
26

思路:

第一眼,一点头绪都没有,因为涉及到区间修改和查询,像线段树,但又一副不可做的样子,因为如果区间修改不加任何优化的话,常数就会大的一批,应该会超时,那么,我们就来考虑关于开方的有关性质,首先\(sqrt(1)=1\),震惊!!没错,这就是修改终点,如果一个位置的数为1,再开方是没有意义的,那么我们就可以维护一个区间最大值,如果这个区间最大值是\(1\),也就是这个区间的数都是\(1\),那么就没必要进行修改了,区间求和的话就跟普通线段树没什么区别了。还有,为什么区间修改时终点是\(l==r\)呢?难道不是\(L<=l\)&&\(r<=R\)么?因为这里的区间修改是采用了一种暴力的思想,也就是一个一个改,相当于单点修改。

自己整理的题解

如果不知道怎么写的话,可以参考以下代码:

#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cmath>
#define maxn 100007
#define ll long long
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
int n,m,tim;
ll maxx[maxn<<2],sum[maxn<<2];
inline ll qread() {
  char c=getchar();ll num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
inline void pushup(int rt) {
  sum[rt]=sum[ls]+sum[rs];
  maxx[rt]=max(maxx[ls],maxx[rs]);
}
void build(int rt, int l, int r) {
  if(l==r) {
    sum[rt]=maxx[rt]=qread();
    return;
  }
  int mid=(l+r)>>1;
  build(ls,l,mid);
  build(rs,mid+1,r);
  pushup(rt);
}
void modify(int rt, int l, int r, int L, int R) {
  if(l==r) {
    sum[rt]=sqrt(sum[rt]);
    maxx[rt]=sqrt(maxx[rt]);
    return;
  }
  int mid=(l+r)>>1;
  if(L<=mid&&maxx[ls]>1) modify(ls,l,mid,L,R);
  if(R>mid&&maxx[rs]>1) modify(rs,mid+1,r,L,R);
  pushup(rt);
}
ll csum(int rt, int l, int r, int L, int R) {
  if(L>r||R<l) return 0;
  if(L<=l&&r<=R) return sum[rt];
  int mid=(l+r)>>1;
  return csum(ls,l,mid,L,R)+csum(rs,mid+1,r,L,R);
}
int main() {
  while(scanf("%d",&n)==1) {
    printf("Case #%d:\n",++tim);
    build(1,1,n);
    m=qread();
    for(int i=1,k,x,y;i<=m;++i) {
      k=qread(),x=qread(),y=qread();
      if(x>y) swap(x,y);
      if(!k) modify(1,1,n,x,y);
      else printf("%lld\n",csum(1,1,n,x,y));  
    }
  }
  return 0;
}