NOIP2017模拟赛(6) 总结

前言:sb环套树写错了,然后就送命了。。


a 巧克力铁丝

题目描述

在一个二维平面里,有n块巧克力,每块巧克力都是长方形(正方形也可以认为是长方形),每块巧克力的四条边都平行于X轴或平行于Y轴。我们用(X1, Y1, X2, Y2)来描述一块巧克力的所在位置,其中(X1, Y1)表示这块巧克力左下角的坐标,(X2,Y2) 表示这块巧克力右上角的坐标。注意:题目给出的n块巧克力之间可能有重叠的地方。奶牛bessie手头上有一个a × b的长方形铁丝框. Bessie想知道它应该把铁丝框放在哪个位置,才能使得可以拿走的巧克力的个数最多?农夫FJ规定:bessie铁丝框放的位置也必须要平行X轴和Y轴,而且还规定,bessie只能拿走在铁丝框里面的巧克力,bessie最多能拿走多少块巧克力?bessie只能放一次铁丝框。解释:如果某块巧克力的位置是:(1,1, 2, 2), 而铁丝框的位置是(-1,1,2,100), 那么这块巧克力也是在铁丝框里面,可以被bessie拿走。也就是说,如果某块巧克力任何部分都没有超出铁丝框, 就可以认为是在铁丝框里面。


输入格式

第一行:一个整数n. 0 <= n <= 50。
第2至n+1行,每行四个整数: X1, Y1, X2, Y2, 描述巧克力的位置. -10^9 <= X1, Y1, X2, Y2 <= 10^9.
最后一行:两个整数: a 、b. 且1 <= a , b <= 10^9.


输出格式

一个整数,bessie最多能拿走多少块巧克力?


输入样例

样例1输入:

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

样例2输入:

2
0 1 2 3
3 0 4 2
4 3


输出样例

样例1输出:

2
样例解释:如果bessie把铁丝框放在(1,1,3,3)处, 那么它可以拿走第1和第2块巧克力;如果把铁丝框放在(2,2,4,4)那么它可以拿走第2和第3块巧克力。

样例2输出:

2


解题思路(直接做)

这题没有什么算法。直接按题目要求的做就可以了。
首先要明白每个框最好的放法肯定是贴着某(些)巧克力两条的边。
然后我们只需将所有巧克力的边收集起来然后枚举矩形框的位置,判断巧克力是否入框就行了。
值得注意的,框可以翻来覆去放。
时间 O(n3)


代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#define N 60

using namespace std;

int n, A, B, px[N<<1], py[N<<1], ans;
struct Data{
    int x1, y1, x2, y2;
    Data() {}
    Data(int x1, int y1, int x2, int y2):x1(x1), y1(y1), x2(x2), y2(y2) {}
}rec[N];

bool Judge(Data P, Data Q){
    return P.x1 >= Q.x1 && P.y1 >= Q.y1 && P.x2 <= Q.x2 && P.y2 <= Q.y2;
}

int main(){


    freopen("a.in", "r", stdin);
    freopen("a.out", "w", stdout);

    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
      scanf("%d%d%d%d", &rec[i].x1, &rec[i].y1, &rec[i].x2, &rec[i].y2);
      px[++px[0]] = rec[i].x1;
      px[++px[0]] = rec[i].x2;
      py[++py[0]] = rec[i].y1;
      py[++py[0]] = rec[i].y2;
    }

    scanf("%d%d", &A, &B);

    for(int i = 1; i <= px[0]; i++)
     for(int j = 1; j <= py[0]; j++){

       Data Fe1 = Data(px[i], py[j], px[i]+A, py[j]+B);
       Data Fe2 = Data(px[i], py[j], px[i]+B, py[j]+A);

       int temp1 = 0, temp2 = 0;
       for(int k = 1; k <= n; k++){
         if(Judge(rec[k], Fe1))  temp1 ++;
         if(Judge(rec[k], Fe2))  temp2 ++;
       }

       ans = max(ans, max(temp1, temp2));
     }

    printf("%d\n", ans);

    return 0;
} 

b 旅行

题目描述

在一段时间之后,网络公司终于有了一定的知名度,也开始收到一些订单,其中最大的一宗来自B市。Blue Mary决定亲自去签下这份订单。
为了节省旅行经费,他的某个金融顾问建议只购买U航空公司的机票。U航空公司的所有航班每天都只有一班,并且都是上午出发当天下午到达的,所以他们每人每天只能坐一班飞机。经过调查,他们得到了U航空公司经营的所有航班的详细信息,这包括每一航班的出发地,目的地以及最多能买到的某一天出发的票数。(注意: 对于一个确定的航班,无论是哪一天,他们最多能买到的那一天出发的票数都是相同的。)
Blue Mary注意到他们一定可以只乘坐U航空公司的航班就从A市到达B市,但是,由于每一航班能买到的票的数量的限制,他们所有人可能不能在同一天到达B市。所以现在Blue Mary需要你的帮助,设计一个旅行方案使得最后到达B市的人的到达时间最早。


输入格式

第一行包含3个正整数N,M和T。题目中会出现的所有城市分别编号为1,2,…,N,其中城市A编号一定为1,城市B编号一定为N. U公司一共有M条(单向)航班。而连Blue Mary在内,公司一共有T个人要从A市前往B市。
以下M行,每行包含3个正整数X,Y,Z, 表示U公司的每一条航班的出发地,目的地以及Blue Mary最多能够买到的这一航班某一天出发的票数。(即:无论是哪一天,Blue Mary最多只能买到Z张U航空公司的从城市X出发到城市Y的机票。)
输入保证从一个城市到另一个城市的单向航班最多只有一个。


输出格式

仅有一行,包含一个正整数,表示最后到达B市的人的最早到达时间。假设他们第一次乘飞机的那一天是第一天。


输入样例

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


输出样例

6

本题数据范围

2 <= N <= 50
1 <= M <= 2450
1 <= T <= 50
1 <= X,Y <= N
X != Y
1 <= Z <= 50


解题思路(二分答案+网络流)

首先,这题我做得很差。一看到要使最晚到达的时间最早,立刻如醍醐灌顶、膝跳反射般想到二分答案。我想到了,这题我花了很久(另外两题都比较简单),想二分后如何检验,贪心?应该不太可能。dp?退了好久,发现不会。网络流?我真想到过,但naive的我认为票数作为原图的边在网络上要拆,难道要写一个能拆边的网络流?(这里全是一个sb的想法)怎么办?写不了,弃!
然后我就写了一个类似于sp(b)fa的dp边宽搜边搞,最后发现有后效性。搞不定了。

其实网络流的构图和原图没有关系,原图上的一些限制在网络上用容量限制表示出来就行了,两者是互不影响的。
这题并不需要神么拆边,拆点才是解决网络流问题的常见手段。
这题如果熟悉网络流的技巧其实并不算难,但在考场上有且仅有网络流大神tututu做对了此题。据说玄学贪心也能过很多点。我写完sp(b)fa后就对这题不报太大希望了,谁知最后发现第三题写炸了,真是后悔莫及啊。。

现在讲正解。正解是动态流,要边做边构图。(tututu唬我们的)。其实只用将原图上的一个点拆成Limit个点,Limit是二分出来的时间限制,然后就开始按题目要求连边,建一个s向第一天的第一个点连边,容量为总人数T,建一个t由最后一天的最后一个点连过去,容量为T。中间有航班的就对应着时间和线路来连,容量为票数,可以在中途停留,所以也要向同一地点的明天连边,容量为oo。然后跑一遍最大流,如果流量恰好为T,则可行,否则不可行。

正确性的话,这是显然的,但在考场上想到,好像不是那么容易呢。
时间复杂度就是网络流的时间复杂度(玄学)。

讲一个笑话,我一开始这样写Dinic超时了,然后将二分的上界改到最小(T*2+1)后还是超。然后改成多路增广(以前我都是写单路),还是超。无奈之下请tututu帮忙看。他帮我把点数改成10万,边数改成100万,再交,居然过了!!定睛一看,果然是点数开小了导致超时。tututu的教诲:凡是网络流是正解的题,点开10万,边开100万肯定不会错,空间不会炸,而且多过这个限制的网络流做不了。果真强,不像蒟蒻我每次都去算该开多大。。%%%

然后这题就到这里了,要多学习总结网络流的建模,像二分+重新构图这种常见套路一定要熟记。抽空补一下网络流24题。


代码

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#define nn 60
#define N 5050
#define oo 0x7fffffff

using namespace std;

int n, m, T, cur, s, t;
int q[N], head_p[N], iter[N], level[N];
struct Data{int from, to, tic;} fli[N];
struct Ab{int next, obj, cap;} Edg[(N*nn+nn*nn+nn*2)*4];

void Insert(int a, int b, int c){
    cur ++;
    Edg[cur].next = head_p[a];
    Edg[cur].obj = b;
    Edg[cur].cap = c;
    head_p[a] = cur;
}

bool bfs(){
    int head, tail;
    memset(level, -1, sizeof(level));
    level[s] = 0;
    q[head = tail = 0] = s;
    while(head <= tail){
      int now = q[head++];
      for(int i = head_p[now]; ~ i; i = Edg[i].next){
        int v = Edg[i].obj, c = Edg[i].cap;
        if(level[v] == -1 && c){
          level[v] = level[now] + 1;
          q[++tail] = v;
        }
      }
    }
    return level[t] != -1;
}

int Dinic(int now, int f){
    if(now == t || !f)  return f;
    int ret = 0;
    for(int &i = iter[now]; ~ i; i = Edg[i].next){
      int v = Edg[i].obj, c = Edg[i].cap;
      if(level[v] > level[now] && c){
        int d = Dinic(v, min(c, f));
        f -= d;
        Edg[i].cap -= d;
        Edg[i^1].cap += d;
        ret += d;
        if(!f)  break;
      }
    }
    return ret;
}

int Max_flow(){
    int flow = 0;
    while(bfs()){
      for(int i = s; i <= t; i++)  iter[i] = head_p[i];
      flow += Dinic(s, oo); 
    }
    return flow;
}

bool Judge(int limit){

    s = 1;  t = s + limit * n + 1;

    cur = -1;
    for(int i = s; i <= t; i++)  head_p[i] = -1;

    Insert(s, s+1, T);
    Insert(s+1, s, 0);

    Insert(t-1, t, T);
    Insert(t, t-1, 0);

    for(int i = 1; i <= m; i++)
      for(int j = 1; j < limit; j++){
        Insert(s+(j-1)*n+fli[i].from, s+j*n+fli[i].to, fli[i].tic);
        Insert(s+j*n+fli[i].to, s+(j-1)*n+fli[i].from, 0);
      }

    for(int i = 1; i <= n; i++)
      for(int j = 1; j < limit; j++){
        Insert(s+(j-1)*n+i, s+j*n+i, oo);
        Insert(s+j*n+i, s+j*n+i, 0);
      }

    return Max_flow() == T;
}

int main(){

    freopen("b.in", "r", stdin);
    freopen("b.out", "w", stdout);

    scanf("%d%d%d", &n, &m, &T);

    for(int i = 1; i <= m; i++)  scanf("%d%d%d", &fli[i].from, &fli[i].to, &fli[i].tic);

    int L = 0, R = T * 2 + 1;

    while(L + 1 < R){
      int mid = (L + R) >> 1;
      if(Judge(mid))  R = mid;
      else  L = mid;
    }

    printf("%d\n", R - 1);

    return 0;
}

c 交通违法

题目描述

禅城区有N条双向道路和N个路口,路口的编号从1至N。每条道路连接两个路口。两个路口之间不会有重边。
保证任意两个路口都是相互到达的。现在觉得在一些路口装上摄像头,检测路面的违法情况。
装在路口的摄像头可以监测到所有连接到这个路口的道路。现在的问题是:最少需要在多少个路口安装摄像头才能监测所有的道路?


输入格式

输入文件第1行包括一个数字N(1 <= N <= 100000)
接下来N行,第i+1行的第一个数字Ai表示有Ai条道路与路口i相连,后面紧跟着Ai个数字,表示与路口i直接相连的路口。


输出格式

最少的摄像头数量。


输入样例

3
2 2 3
2 1 3
2 1 2


输出样例

2


解题思路(环套树dp)

这题一看就是个环套树。
n点n边,求最小点覆盖。如果是二分图直接求最大匹配。一般图的话也不会考带花树什么鬼的吧,还好这题就是个环套树。
考场上犯了个傻叉无比的错误,导致信心满满的一题就此爆零。
不说废话,正解的话,直接记 f[root] 为选此点的, g[root] 为不选的。如果父亲选了,儿子可选可不选;父亲没选,儿子必选。然后就在图上找环断开做树形dp就行了。dfs时直接初始化就好了(跟我的sb错误有关)。最后断开的点一定至少有一个要选,答案就包含在 f[u] f[v] 中,取 min 就是答案。
虽然简单,但这题还有其他作法。由于最小点覆盖与最大独立集互为补集。我们就直接按某骑士那题一样写求最大独立集的模版,再用 n 一减就是答案。其实类比二者记录的状态及转移知其二者是“互补”的。

然而由于点数较大,直接dfs写在win下会爆栈,如果硬不服气,就可以像KsCla那样将环套树拆成先树形dp后再做一遍环形dp,这样可以用bfs代替dfs,但除外这是没什么必要的。
另,环形dp也是要做的,如果在环上贪心明显是错的,因为环上还有外向树套在那里的。
再另,找到环后最后再做dp是好的,如果在dfs中做dp就会做两遍(一次在v找到 u 时,另一次在回溯后u再搜到 v 时)。其实没什么影响但这样就会莫名爆栈(绝望),解决方法就是先找环最后再做dp,如果打标记让其只搜一次的话,应该也可以把。。
时间O(n).


代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 100010

using namespace std;

int n, cur = -1, head_p[N], ban1, ban2, ans;
int f[N], g[N];

struct Tadj{int next, obj;} Edg[N<<1];
bool vis[N];

void Insert(int a, int b){
    cur ++;
    Edg[cur].next = head_p[a];
    Edg[cur].obj = b;
    head_p[a] = cur;
}

void dp(int root, int fa){
    f[root] = 1;
    g[root] = 0;
    for(int i = head_p[root]; ~ i; i = Edg[i].next){
      int v = Edg[i].obj;
      if((root == ban1 && v == ban2) || (root == ban2 && v == ban1) || v == fa)  continue;
      dp(v, root);
      g[root] += f[v];
      f[root] += min(f[v], g[v]);
    }
}

int Work(int u, int v){
    int temp;
    dp(u, 0);
    temp = f[u];
    dp(v, 0);
    temp = min(temp, f[v]);
    return temp;
}

void dfs(int root, int fa){
    vis[root] = true;
    for(int i = head_p[root]; ~ i; i = Edg[i].next){
      int v = Edg[i].obj;
      if(v == fa)  continue;
      if(vis[v]){
        ban1 = root;
        ban2 = v;
      }
      else  dfs(v, root);
    }
}

int main(){

    freopen("c.in", "r", stdin);
    freopen("c.out", "w", stdout);

    scanf("%d", &n);
    for(int i = 1; i <= n; i++)  head_p[i] = -1;

    int num, x;
    for(int i = 1; i <= n; i++){
      scanf("%d", &num);
      for(int j = 1; j <= num; j++){
        scanf("%d", &x);
        Insert(i, x);
      }
    }

    dfs(1, 0);
    ans = Work(ban1, ban2);

    printf("%d\n", ans);

    return 0;
}

总结

但愿sb题不要再写错,不然标准分就天差地别,rank也天差地别了。加油吧,坚持下去。


这里写图片描述

美人如诗,草木如织。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
NOI(全国青少年信息学奥林匹克竞模拟的测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟的测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值