闵梓轩大佬のnoip模拟题D1 总结 2017/10/26

    • 背景
    • 题目概括
    • T1
      • 题面
      • 分析
        • 90分算法
        • 满分算法
    • T2
      • 题面
      • 分析
        • 部分分算法
        • 满分算法
        • 满分代码
    • T3
      • 题面
      • 分析
      • 代码
    • 总结

背景

这道题目是去年的金牌大佬闵梓轩在一年前出的一套noip模拟题。简单的评价就是:思维分量足,数据强大!!!

题目概括

每道题目测试点10个,每个测试点10分,测试点时限1秒。
比较方式:全文比较(过滤行末空格及文末回车)。题目类型:传统。不开启O2优化。运行内存限制:128M。

T1

题面

为爱追寻 (lovefinding.pas/c/cpp) 【问题描述】
话说一年半以前,紫萱学姐展开了对杨廷学长的追求。在经历了不懈的努力之后,学姐终于成为了一名……金牌单身狗。但是这位痴情的少女并没有放弃,于是决定在保送之后继续进行这项征程,并为参加比赛的各位在役OI选手送上半熟的狗粮。
历经了半年的停课之后,学姐回到了陌生又熟悉的班里,但是她已经找不到学长的位置了。于是她决定采用一种高效率的寻找方法:瞎找法。
我们将学姐的班级视为一个二维平面,每个整数坐标对应一张桌子,学姐从班级的某个位置(x0,y0)开始瞎找,每次检查一下当前所在的这个桌子是谁的,然后进行下一次移动,直到找到学长的桌子(xt,yt),便停止移动。
给出学姐的初始坐标和每次移动的方向,请你判断在寻找的过程中学姐一共检查了多少张桌子。
【输入】 第一行五个整数n,x0,y0,xt,yt,分别代表学姐移动的次数和学长桌子的坐标。
接下来n行,第i行两个整数dx,dy,代表学姐第i次移动沿与x/y轴平行的方向移动了dx/dy个单位。如果dx/dy为负数,表示沿x/y轴的反方向移动了-dx/-dy个单位。
【输出】 输出学姐检查过的桌子总数,如果学姐进行完所有移动之后都没有找到学长的桌子,那么输出“SingleDogMZX”(不含引号)。
【输入输出样例1】
lovefinding.in 5 1 1 3 2 1 1 0 -2 0 2 1 0 0 -1
lovefinding.out 4
【数据范围】 样例中,检查了(1,1)(2,2),(2,0),(3,2)共4张桌子
对于30%的数据,学姐每次移动时不会移动到已经检查过的桌子。 对于60%的数据,任何时刻学姐的横纵坐标都为≤2500的自然数。
对于90%的数据,任何时刻学姐的横纵坐标的绝对值都为≤2500的自然数。
对于100%的数据,任何时刻学姐的横纵坐标的绝对值都为≤10^9的自然数,n≤1000000。

分析

这道题目的数据范围实在是坑啊,明明是个D1T1的送分题,但是还不给你送全分。最后面10分是有梦想的做法——Hash表。

90分算法

用一个二维数组d来表示当前位置紫萱有没有检查过。因为出现了负数的情况,所以坐标需要加上一个偏移量delta=2500。这就不多说了。
错误反思:
一看到数据有负数,马上就改了下我的老版快读。刚开始交了我的代码,本以为可以直接90分的,结果全部RE,最后一打表,原来是数组越界。数组越界的问题后来一检查就惨了,是我的快读有问题。
这是快读:

inline int read()
{
    int num=0;
    bool flag=true;
    char c;
    for(;c>'9'||c<'0';c=getchar())
    if(c=='-')
    flag=false;
    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
    return flag ? num : -num;
}

然后又一交,AC30分,WA60分。然后还贼有特色,wa的点全部多了1。最后才发现我的d数组赋初值之后才memset的(大麻瓜)。
代码在这:

#include
#define maxn 1000001 
#define delta 2500
//一个偏移量,可以避免负数导致数组越界的问题。
using namespace std;
inline int read()
{
    int num=0;
    bool flag=true;
    char c;
    for(;c>'9'||c<'0';c=getchar())
    if(c=='-')
    flag=false;
    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
    return flag ? num : -num;
}//新版的快读
int xb,yb,xt,yt,n,ans=0;
int dx[maxn],dy[maxn];
bool d[5001][5001];
int main()
{
    freopen("lovefinding.in","r",stdin);
    freopen("lovefinding.out","w",stdout);

    n=read();
    xb=read();
    yb=read();
    xt=read();
    yt=read();

    if(xb==xt&&yb==yt)
    {
        printf("%d\n",1);
        return 0;
    }//特判:如果直接就重合了,那就只需要检查一次
    for(int i=1;i<=n;i++)
    {
        dx[i]=read();
        dy[i]=read();

    }
    //读入
    ans=1;
    memset(d,false,sizeof(d));
    d[delta+xb][delta+yb]=true;

    for(int i=1;i<=n;i++)
    {
        xb+=dx[i];
        yb+=dy[i];//移动啊

        if(!d[xb+delta][yb+delta])
        {
            ans++;
            d[xb+delta][yb+delta]=true;
        }//如果没有被访问过
        if(d[xt+delta][yt+delta])
        {
            printf("%d\n",ans);
            return 0;
        }//看看终点有没有被访问
    }
    printf("SingleDogMZX\n");
    return 0; 
}

满分算法

这道题目一看数据范围那么大,二维数组肯定是炸掉的。那我们该怎么办呢?正解——Hash表!因为所有的数据都是在int范围内,我们可以使用hash表存储已经到达过的坐标,注意要选择合适的映射函数,时间复杂度为O(n)。
闵梓轩大佬满分代码:

#include
#define FILE "lovefinding"

namespace IO{
    char buf[1<<15],*fs,*ft;
    inline char gc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
    inline int read(){
        int x=0,rev=0,ch=gc();
        while(ch<'0'||ch>'9'){if(ch=='-')rev=1;ch=gc();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=gc();}
        return rev?-x:x;
    }
}using namespace IO;

struct Point{
    int x,y;
    Point(){}
    Point(int a,int b):x(a),y(b){}
};
inline Point operator+(const Point& a,const Point& b){
    return Point(a.x+b.x,a.y+b.y);
}
inline bool operator!=(const Point& a,const Point& b){
    return a.x!=b.x||a.y!=b.y;
}
inline Point getp(){
    int x=read(),y=read();
    return Point(x,y);
}


namespace Hash_Table{
    const int HASH_SIZE(23333309),step(7);
    inline int h(const Point& p){
        int ans=(1LL*(p.x+int(1e9))*int(2e9+1)+p.y+int(1e9))%HASH_SIZE;
        if(ans<0)ans+=HASH_SIZE;
        return ans;
    }
    Point ht[HASH_SIZE];bool state[HASH_SIZE];int TOT;
    int hash(const Point& p){
        int at=h(p);
        for(;state[at]&&ht[at]!=p;TOT++)
            if((at+=step)>=HASH_SIZE)
                at-=HASH_SIZE;
        if(!state[at]){
            ht[at]=p;
            state[at]=true;
            return 0;
        }
        else return 1;
    }
}using namespace Hash_Table;

#include
int main(){
    freopen(FILE".in","r",stdin);
    freopen(FILE".out","w",stdout);
    int n=read(),ans=1;Point now=getp(),t=getp();
    hash(t);
    if(!hash(now))ans++;
    else {printf("1\n");return 0;}
    bool flag=false;
    for(int i=1;i<=n;i++){
        now=now+getp();
        if(!(now!=t)){flag=true;break;}
        else if(!hash(now))
            ans++;
    }
    if(flag)printf("%d\n",ans);
    else printf("SingleDogMZX\n");
    return 0;
}

T2

题面

零食店
(snackstore.pas/c/cpp)
【问题描述】
成功找到了学长之后学姐感觉到有些饿,于是决定去附近的零食店给自己和学长买些零食。
焦作市的有n家零食店,由m条道路连接着这些零食店,每条道路都有自己的长度l,每家零食店都有自己的消费指数。
由于学姐是个穷B,所以去买零食的路上不能经过某些消费指数超过一定限度的店。
同时由于学姐体力有限,所以去买零食的过程中走的路程不能太长。
想来想去学姐决定去问学长买什么零食比较好,反正到最后都是学长吃╮(╯_╰)╭
在去问之前,学姐准备先做好准备,她把焦作市(所有零食店)的地图给了你,希望你能编出一个程序快速回答她从某个零食店出发,在上述限制下有多少家零食店可供她挑选。
【输入】
第一行三个正整数n,m,q,分别代表零食店数,道路数和询问数。
接下来一行n个正整数,第i个正整数vi代表第i家零食店的消费指数。
接下来m行,第i行三个正整数x,y,l,代表第i条道路连接编号为x和y的两个零食店,长度为l。
接下来q行第i行三个正整数s,c,d,代表第i个询问要求从s出发,所经过的零食店的消费指数不能超过c(除了起点和终点以外),且行走路程不超过d。
【输出】
一共q行,第i行一个整数代表在第i个询问的要求下有多少家零食店可供学姐挑选。
【输入输出样例1】
snackstore.in
5 5 2
1 2 3 4 5
1 2 1
1 3 4
2 3 2
1 4 3
2 5 1
1 1 3
2 1 2
snackstore.out
2
3
【数据范围】
样例中第一个询问能去编号为2/4的零食店。
第二个询问能去编号为1/3/5的零食店。
对于40%的数据,n≤10,m≤20,q=1。
对于70%的数据,m≤500,
q≤10000。
对于100%的数据,n≤100,m≤10000,q≤1000000,vi,c,d≤10^9,1≤x,y,s≤n,l≤10^6。

分析

部分分算法

40%爱怎么写怎么写,反正我是不会写。

这是闵梓轩大佬的原话。所以这里40%的数据算法不搞。

70%算法我们带一下:

70%的数据量点数边数和询问数都比较小,每次询问时去掉不符合条件的点(但仍要计算出这些点的dis值,只是不用这些点进行三角形迭代)后使用任意单源最短路算法解决即可。

满分算法

消费指数的限制对应只能经过消费指数排名前几的点,而路径长度的限制对应只能抵达最短路排名前几的点,所以我们考虑能不能预处理出g[k][i][j]代表从i出发只经过消费指数前k的点,距离i第j短的点距离为多少,这样的话每次询问只需要二分出k,然后在g[k][i]数组中二分就可以了。
预处理时我们可以当做动态加点的全源最短路,我们用动态规划的转移方程表示这个过程:用f[k][i][j]代表i出发只经过消费指数前k的点,i到j的最短路为多少。
f[k][i][j]=min{f[k-1][i][j],f[k-1][i][v[k]]+f[k-1][v[k]][j]}(其中v[k]代表消费指数排第k的点,1≤i,j≤n)
而f[0]数组很显然就是这个图的邻接矩阵。
求出f数组之后将所有f[k][i]数组排序就是上面所求的g数组了,询问的时候加两个二分即可,时间复杂度为O(n^3logn+qlognn),没有可以去卡不排序每次询问在f数组中枚举的算法,但是泥萌要知道这个做法有优化的空间。
聪明的小朋友们已经看出来了上面这个动态规划就是floyd算法。
有很多算法都是实现简单但是想要深入理解起来并不简单的,比如floyd/FFT/FWT/后缀自动机等,希望大家在学算法的时候要理解得透彻一些,不要再碰到模板题还不会写了。

满分代码

#include
#include
#include
#define FILE "snackstore"

namespace IO{
    char buf[1<<15],*fs,*ft;
    inline char gc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
    inline int read(){
        int x=0,ch=gc();
        while(ch<'0'||ch>'9')ch=gc();
        while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-'0',ch=gc();
        return x;
    }
}using namespace IO;

const int MAXN(105);
int n,m,q,pow[MAXN],dis[MAXN][MAXN][MAXN];

struct vertex{
    int x,p;
}v[MAXN];
inline bool operator<(const vertex& u,const vertex& v){
    return u.pvoid init(){
    n=read(),m=read(),q=read();
    for(int i=1;i<=n;i++)
        v[i].x=i,v[i].p=read();
    std::sort(v+1,v+n+1);
    for(int i=1;i<=n;i++)
        pow[i]=v[i].p;

    for(int k=0;k<=n+1;k++)
        for(int i=1;i<=n+1;i++)
            for(int j=1;j<=n+1;j++)
                dis[k][i][j]=int(1e9)+1;
    for(int i=1;i<=n;i++)
        dis[0][i][i]=0;

    for(int i=1;i<=m;i++){
        int x=read(),y=read(),l=read();
        if(l0][x][y])
            dis[0][x][y]=dis[0][y][x]=l;
    }
}

inline int rand32(){return ((rand()&1)<<30)|(rand()<<15)|rand();}
inline int rand(int l,int r){if(rreturn l;return l+rand32()%(r-l+1);}

void floyd(){
    for(int k=1;k<=n;k++){
        memcpy(dis[k],dis[k-1],sizeof dis[k]);
        #define d(x,y) dis[k][x][y]
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(d(i,v[k].x)+d(v[k].x,j)for(int k=0;k<=n;k++)
        for(int i=1;i<=n;i++)
            std::sort(dis[k][i]+1,dis[k][i]+n+1);
}

int binary(int* a,int k){
    int left=0,right=n;
    while(left!=right){
        int mid=left+right;
        mid=(mid&1)+(mid>>1);
        if(a[mid]>k)right=mid-1;
        else left=mid;
    }
    return left;
}

void solve(){
    for(int Case=1;Case<=q;Case++){
        int i=read(),mxp=read(),mxd=read();
        printf("%d\n",binary(dis[binary(pow,mxp)][i],mxd)-1);
    }
}

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

    init();
    floyd();
    solve();

    return 0;
}

T3

题面

昆特-冠位指定
(gwent_grandorder.pas/c/cpp)
【问题描述】
酒足饭饱之后(没有后三个字),紫萱学姐打开了手机上的一款游戏,叫做GwentGrandOrder,简称GGO,但是由于光腚总菊的要求,手机游戏中不得出现英文,所以就有了一个很low的中文名:昆特-冠位指定。
看到学姐玩这个游戏,学长也产生了浓厚的兴趣并开始了围观,学姐决定给学长展示一下自己的牌技。
这是一款卡牌游戏,每个人用自己的卡组进行对战,游戏开始时每个人从牌堆中抽取若干张牌,然后依次打出手牌,为了简化这个游戏,我们假定牌堆中只有以下三种牌:
1. 单位牌,分为近战/远程/攻城/敏捷单位四种牌,每张牌都有自己的力量值,前三种单位牌使用时将该牌置于己方战场中的对应排,敏捷单位可以置于近战或远程排,放置后不可移动且受该排的特殊牌影响。
2. 特殊牌,分为霜霰/浓雾/地形雨/史凯利杰风暴/天晴五种天气牌和领导号角,前四种天气牌的效果分别为将双方的所有近战/远程/攻城/远程和攻城单位力量降为1,天晴的效果为驱散当前所有天气牌效果。领导号角可以在最终计算力量值时将本方其中一排所有单位牌的力量翻倍。
3. 英雄牌,与单位牌使用方法相同,但是英雄牌的力量值不受任何特殊卡牌影响而增加或减少。
单位牌和英雄牌可能带有“间谍”属性,带有该属性的单位牌或英雄牌在使用时将会被置于对方战场,同时使用者将会从牌堆中抽取两张牌。
双方出牌结束或无牌可出时,计算双方场上所有牌的力量值之和,力量值大的一方获胜。
注意:如果一张单位牌同时受到负面天气和领导号角的影响,那么这张牌的力量值应该为2。同时一张力量值为0的单位牌受到负面天气影响时力量值不会变为1。
假设由于某种原因,对手已经打出m张单位牌(以总牌数和力量之和的形式表示,敌方敏捷单位以近战或远程方式直接使用,即描述敌方单位或英雄牌时不会出现敏捷属性)和特殊牌并结束出牌。紫萱学姐的卡组中有n张牌,而且可以从中抽取k张作为初始手牌,由于学长的欧气加持,紫萱学姐每次抽牌时都可以抽到自己想抽的那一张牌。但是这是一个氪金看脸游戏,每张牌都有一定的稀有度,作为一个在NOI上用尽人品从亚变非的新晋酋长,紫萱学姐希望能用尽量不稀有的卡牌战胜对手,来证明这是一个技术游戏。请你帮助紫萱学姐设计一个方案使得所使用的牌中稀有值的最大值尽可能小。由于她所使用的牌组所属阵营尼弗迦德的特性,在双方力量值相等时判定学姐胜利。
【输入】
第一行三个正整数n,m和k,意义如题目所示。
接下来m行每行描述一张牌,代表对方所出的所有牌。
接下来n行每行描述一张牌,代表紫萱学姐的牌堆。
每张牌用若干个整数表示,第一个整数代表该牌的稀有度vi,第二个整数代表该牌类型(1位单位牌,2为特殊牌,3为英雄牌)。
若该牌为单位牌或英雄牌,接下来三个自然数代表该牌的种类(1~4分别为近战/远程/攻城/敏捷)和力量值,以及该牌是否为间谍牌(0为正常单位或英雄,1为间谍牌)。
若该牌为特殊牌,接下来一个整数代表该牌的类型,0~5分别为领导号角/霜霰/浓雾/地形雨/史凯利杰风暴/天晴。若该牌为领导号角且是对方所出的牌,接下来一个正整数代表此牌所作用的位置,1~3分别对应近战/远程/攻城。
【输出】
一个正整数,为最优解中所使用的卡牌稀有值最大值,如果无论如何学姐也无法赢得这场游戏,输出“SingleDogMZX”(不含引号)。
【输入输出样例1】
gwent_grandorder.in
5 5 1
1 1 1 5 0
1 1 1 5 0
1 1 3 10 0
1 3 1 5 1
1 2 0 1
7 3 1 15 0
8 1 1 5 1
9 2 1
10 1 2 10 0
11 2 0
gwent_grandorder.out
9

【数据范围】
样例中
游戏开始时,敌方战场中有两个力量为5的近战单位和一个力量为10的攻城单位,且敌方近战排存在领导号角。我方战场有一个力量为5的近战间谍英雄。
我方手牌为力量为15的近战英雄杰日天、力量为5的近战间谍单位、霜霰、力量为10的远程单位、领导号角。
开始时抽取间谍牌(稀有度为8),并将其置于对方战场,抽取英雄牌(稀有度为7)和霜霰(稀有度为9)打出并结束回合。
由于英雄不受特殊牌影响,我方力量总和为15+5=20。
由于霜霰和领导号角的双重影响,敌方力量总和为2+2+2+10=16
我方胜利,所用牌稀有度最大值为9,为最优解。
对于测试点1/2,n,m≤10。
对于测试点3/4,n,m≤1000。
对于测试点5/6,n,m≤100000。
对于测试点1/3/5,不含间谍牌。
对于测试点1/2/3/4/5/6,不含特殊牌
对于测试点7/8,n,m≤10000。
对于测试点9/10,n,m≤100000。
对于所有测试点,vi≤10^9,所有单位的力量值≤10000。

分析

首先这题和第一题puts(“SingleDogMZX”);的同学将会爆零并祝你们都变成单身狗。
这道题从难度上来说比第二题要简单得很多,尤其是30分和60分基本就是送的。但是结果貌似和我预料到的一样,这题没多少人拿分。
首先看60分的部分,没有特殊牌,也就是双方手中的牌都可以视为力量值不变的单位牌。
在这个情况下,30分的部分没有间谍,策略就是就是俗称的按费拍怪,二分一下所使用牌稀有值的最大值,然后选取力量前k大的求和并与对方比,难度直逼第一题。
60分中存在间谍,仍然沿用二分稀有度确定自己可用牌堆的,不过此处加入了间谍牌,应该考虑如何使用。首先我们仍然先打出前k大的非间谍牌,然后假设换掉最后打出的一张单位牌改为打出一张间谍牌,抽取原来被换掉的那张和一张新的单位牌。这样就相当于每多打出一张间谍牌就能多打出一张非间谍牌。那么按间谍牌力量递增的顺序不断打出间谍牌直到牌堆中力量最小的间谍牌比力量最大的非间谍牌大就行了。
上述算法的时间复杂度为O(nlog^2n)(二分后还需排序)。
80分数据范围比较小但是出现了特殊牌,特殊牌会影响单位的,这个时候我们就要把牌的位置和属性加入考虑范围内。依然二分出可用牌堆,然后我们可以观察到我们最多只会打出八张特殊牌:五张天气牌(天晴用于驱散对方已施加的天气)和三张领导号角。只需要枚举我们所使用的特殊牌情况然后重新计算单位力量值再按60分的算法进行即可,复杂度O(2^8*nlog^2n)(二分,枚举,排序)。这个算法还有优化的空间,那就是影响单位力量值的只有每排是否有负面天气/是否有领导号角,这样可以去掉一些重复的枚举,枚举量变为O(2^6)。
100分算法数据范围比较大,但是枚举特殊牌是不可避免的,我们考虑能否减少一次排序:我们将手上的所有牌分为间谍/非间谍和近战/远程/攻城/敏捷/英雄(英雄放在哪里力量值都不受影响)一共十种,而相同种类的牌不会因为特殊牌影响而改变排序。这样一来我们就可以将排序和枚举天气变为平行的关系,然后每次从五种间谍的力量值(经过特殊牌修正)取最小值,五种非间谍取最大值比较即可,时间复杂度O(logn*(2^6*n+nlogn))。
我敢肯定好多人考完再回头看这道题,你会发现这道题的得分远低于自己的OI实力。
首先这题和第一题puts(“SingleDogMZX”);的同学将会爆零并祝你们都变成单身狗。
这道题从难度上来说比第二题要简单得很多,尤其是30分和60分基本就是送的。但是结果貌似和我预料到的一样,这题没多少人拿分。
首先看60分的部分,没有特殊牌,也就是双方手中的牌都可以视为力量值不变的单位牌。
在这个情况下,30分的部分没有间谍,策略就是就是俗称的按费拍怪,二分一下所使用牌稀有值的最大值,然后选取力量前k大的求和并与对方比,难度直逼第一题。
60分中存在间谍,仍然沿用二分稀有度确定自己可用牌堆的,不过此处加入了间谍牌,应该考虑如何使用。首先我们仍然先打出前k大的非间谍牌,然后假设换掉最后打出的一张单位牌改为打出一张间谍牌,抽取原来被换掉的那张和一张新的单位牌。这样就相当于每多打出一张间谍牌就能多打出一张非间谍牌。那么按间谍牌力量递增的顺序不断打出间谍牌直到牌堆中力量最小的间谍牌比力量最大的非间谍牌大就行了。
上述算法的时间复杂度为O(nlog^2n)(二分后还需排序)。
80分数据范围比较小但是出现了特殊牌,特殊牌会影响单位的,这个时候我们就要把牌的位置和属性加入考虑范围内。依然二分出可用牌堆,然后我们可以观察到我们最多只会打出八张特殊牌:五张天气牌(天晴用于驱散对方已施加的天气)和三张领导号角。只需要枚举我们所使用的特殊牌情况然后重新计算单位力量值再按60分的算法进行即可,复杂度O(2^8*nlog^2n)(二分,枚举,排序)。这个算法还有优化的空间,那就是影响单位力量值的只有每排是否有负面天气/是否有领导号角,这样可以去掉一些重复的枚举,枚举量变为O(2^6)。
100分算法数据范围比较大,但是枚举特殊牌是不可避免的,我们考虑能否减少一次排序:我们将手上的所有牌分为间谍/非间谍和近战/远程/攻城/敏捷/英雄(英雄放在哪里力量值都不受影响)一共十种,而相同种类的牌不会因为特殊牌影响而改变排序。这样一来我们就可以将排序和枚举天气变为平行的关系,然后每次从五种间谍的力量值(经过特殊牌修正)取最小值,五种非间谍取最大值比较即可,时间复杂度O(logn*(2^6*n+nlogn))。
我敢肯定好多人考完再回头看这道题,你会发现这道题的得分远低于自己的OI实力。

代码

#include
#include
#include
#include
#define FILE "gwent_grandorder"
namespace IO{
    char buf[1<<15],*fs,*ft;
    inline char gc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
    inline int read(){
        int x=0,rev=0,ch=gc();
        while(ch<'0'||ch>'9'){if(ch=='-')rev=1;ch=gc();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=gc();}
        return rev?-x:x;
    }
}using namespace IO;

const int MAXN(100005);

int n,m,K;

struct Card{
    int v,ty,pos;
    int pow,spy;
}c[MAXN];

inline bool operator<(const Card& a,const Card& b){
    if(a.v!=b.v)return a.velse if(a.ty!=b.ty)return a.tyelse if(a.pos!=b.pos)return a.poselse if(a.pow!=b.pow)return a.powpow;
    else return a.spystruct BattleField{
    bool weather[4];
    bool horn[2][4];
    int sum[2];
    inline int evaluate(int o,int pos,int pow){
        if(pos==4){
            int x=evaluate(o,1,pow),y=evaluate(o,2,pow);
            return !o?std::max(x,y):std::min(x,y);
        }
        else if(pos>=1&&pos<=3){
            if(weather[pos]&&pow>=1)
                pow=1;
            if(horn[o][pos])
                pow*=2;
        }
        return pow;
    }
}orgin;
int oS;

std::vector<int> e[2][6];

void init(){
    n=read(),m=read(),K=read();
    for(int i=1;i<=m;i++){
        int v=read(),ty=read(),pos=read();v++;
        if(ty==1||ty==3){
            if(ty==3)pos=5;
            int pow=read(),spy=read();
            e[spy][pos].push_back(pow);
        }
        else{
            if(pos==0)orgin.horn[1][read()]=true;
            else if(pos==4)
                orgin.weather[2]=orgin.weather[3]=true;
            else if(pos==5)
                memset(orgin.weather,0,sizeof orgin.weather);
        }
    }
    if(orgin.weather[1])
        oS|=1;
    if(orgin.weather[2])
        oS|=2;
    if(orgin.weather[3])
        oS|=4;
    for(int i=1;i<=n;i++){
        c[i].v=read();c[i].ty=read();c[i].pos=read();
        if(c[i].ty==1||c[i].ty==3){
            c[i].pow=read();
            c[i].spy=read();
        }
    }
    std::sort(c+1,c+n+1);
}

std::vector<int> u[2][6];
int cur[2][6],pw[2][6];
int skill[6];
int mns[1<<6];

int fmax(){
    int pos=0,mxp=0;
    for(int i=1;i<=5;i++)
        if(pw[0][i]>mxp)
            mxp=pw[0][i],pos=i;
    return pos;
}
int fmin(){
    int pos=0,mnp=int(2e4);
    for(int i=1;i<=5;i++)
        if(pw[0][i]1][i],pos=i;
    return pos;
}

bool Judge(int S,int cnt){
    BattleField now=orgin;
    for(int i=0;i<3;i++){
        now.weather[i+1]=S&(1<0][i+1]=S&(1<<(i+3));
    }
    for(int j=1;j<=5;j++)
        for(int i=0;i<2;i++)
            cur[i][j]=0,pw[i][j]=now.evaluate(i,j,u[i][j][0]);
    for(int k=0;k<2;k++)
        for(int i=1;i<=5;i++)
            for(int j=0,tot=int(e[k][i].size());j1]+=now.evaluate(k^1,i,e[k][i][j]);
    for(int i=1;i<=cnt;i++){
        int pos=fmax();
        if(pos==0)break;
        else{
            now.sum[0]+=pw[0][pos];
            pw[0][pos]=now.evaluate(0,pos,u[0][pos][++cur[0][pos]]);
        }
    }
    while(true){
        int pos[2];
        pos[0]=fmax();pos[1]=fmin();
        if(pos[0]==0||pos[1]==0||pw[0][pos[0]]1][pos[1]])break;
        else{
            for(int i=0;i<2;i++){
                now.sum[i]+=pw[i][pos[i]];
                pw[i][pos[i]]=now.evaluate(i,pos[i],u[i][pos[i]][++cur[i][pos[i]]]);
            }
        }
    }
    return now.sum[0]>now.sum[1];
}

void dfs(int now=0,int nS=oS,int cost=0){
    static int sS[5]={0,1,2,4,6};
    if(now==8){
        if(mns[nS]==-1||costreturn;
    }
    if(now==0&&skill[5])
        dfs(now+1,nS&(7<<3),cost+1);
    for(int i=1;i<=4;i++)
        if(now==i&&skill[i])
            dfs(now+1,nS|sS[i],cost+1);
    for(int i=5;i<=7;i++)
        if(now==i&&skill[0]){
            skill[0]--;
            dfs(now+1,nS|(1<<(i-2)),cost+1);
            skill[0]++;
        }
    dfs(now+1,nS,cost);
}
inline bool mycmp(int a,int b){
    return a>b;
}
bool Check(int x){
    for(int i=0;i<2;i++)
        for(int j=1;j<6;j++)
            u[i][j].clear();
    memset(skill,0,sizeof skill);
    memset(mns,-1,sizeof mns);
    for(int i=1;i<=x;i++){
        if(c[i].ty==2)
            skill[c[i].pos]++;
        else{
            int pos=c[i].pos;
            if(c[i].ty==3)pos=5;
            u[c[i].spy][pos].push_back(c[i].pow);
        }
    }
    for(int i=1;i<=5;i++){
        u[0][i].push_back(-1);
        std::sort(u[0][i].begin(),u[0][i].end(),mycmp);
    }
    for(int i=1;i<=5;i++){
        u[1][i].push_back(1e9);
        std::sort(u[1][i].begin(),u[1][i].end());
    }
    dfs();
    for(int S=0;S<(1<<6);S++)
        if(mns[S]!=-1&&Judge(S,K-mns[S]))
            return true;
    return false;
}

void binary(){
    int left=0,right=n+1;
    while(left!=right){
        int mid=(left+right)>>1;
        if(Check(mid))right=mid;
        else left=mid+1;
    }
    if(right==n+1)printf("SingleDogMZX\n");
    else printf("%d\n",c[left].v);
}

int main(){
    freopen(FILE".in","r",stdin);
    freopen(FILE".out","w",stdout);
    init();
    binary();
    return 0;
}

总结

这套模拟题总的来说是跟noip2015很像的难度和题目类型。就拿D1T1来说吧,都是90分贼好拿,然后剩下10分有梦想的才能拿、。。。D1明显要比D2简单,但我D1T1犯了两个错误:1、快读写错;2、memset放错了地方,直接爆零。。
我实在想不出来写什么了……溜了

你可能感兴趣的:(闵梓轩大佬のnoip模拟题D1 总结 2017/10/26)