[神秘模拟赛]

  一套不错的NOIP模拟赛,总共6道题。小C把它分两天,每天3个半小时完成,用于模拟在NOIP赛场上的情况。

 

A. A Riddle About Toys

  有n个玩具小人围成一圈,它们有的面朝圈内,有的面朝圈外。已知它们的职业和朝向。现在第1个玩具小人告诉小南一个包含m条指令的谜题, 其中第z条指令形如“左数/右数第s个玩具小人”。你需要输出依次数完这些指令后,到达的玩具小人的职业。

Input

  输入的第一行包含两个正整数n,m,表示玩具小人的个数和指令的条数。接下来 n行,每行包含一个整数和一个字符串,以逆时针为顺序给出每个玩具小人的朝向和职业。其中0表示朝向圈内,1表示朝向圈外。整数和字符串之问用一个空格隔开。字符串长度不超过10且仅由小写字母构成,字符串不为空,并且字符串两两不同。接下来 m行,其中第z行包含两个整数a,s,表示第z条指令。若a=0,表示向左数s个人;若a=1,表示向右数s个人。

Output

  输出一个字符串,表示从第一个读入的小人开始,依次数完m条指令后到达的小人的职业。

Sample Input

  7 3
  0 singer
  0 reader
  0 mengbier
  1 thinker
  1 archer
  0 writer
  1 mogician
  0 3
  1 1
  0 2

Sample Output

  writer

HINT

  1<=n,m<=100000,1<=s<n;
  字符串长度不超过10且仅由小写字母构成,字符串不为空,并且字符串两两不同。

 

Solution

  D1T1特有的简单模拟,位置通过加减法计算即可,WA了你退群吧。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MN 100005
using namespace std;
int n,m,st;
bool cx[MN];
char nam[MN][15];

inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
    while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
    return n*f;
}

int main()
{
    register int i,g,x;
    n=read(); m=read();
    for (i=1;i<=n;++i)
        cx[i]=read(),scanf("%s",nam[i]);  //0 in,1 out
    st=1;
    while (m--)
    {
        g=read(); x=read();  //0 left,1 right
        st+=((g^cx[st])*2-1)*x;
        if (st<=0) st+=n;
        if (st> n) st-=n;
    }
    puts(nam[st]);
}

 

B. Day Day Love Running

  小C决定制作一款叫做《Day Day Love Running》的游戏。
  这个游戏的地图可以看作一棵包含n个结点和n-1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到n的连续正整数。
  现在有m个玩家,第i个玩家的起点为Si,终点为Ti。每天任务开始时,所有玩家在第0秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。(由于地图是一棵树, 所以每个人的路径是唯一的)
  小C想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。在结点j的观察员会选择在第Wj秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点j。小C想知道每个观察员会观察到多少人?
  注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一段时间后再被观察员观察到。 即对于把结点j作为终点的玩家:若他在第Wj秒前到达终点,则在结点j的观察员不能观察到该玩家;若他正好在第Wj秒到达终点,则在结点j的观察员可以观察到这个玩家。

Input

  第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量,m代表玩家的数量。
  接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边。
  接下来一行n个整数,其中第j个整数为Wj,表示结点j出现观察员的时间。
  接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。

Output

  输出1行n个整数,第j个整数表示结点j的观察员可以观察到多少人。

Sample Input

  6 3
  2 3
  1 2
  1 4
  4 5
  4 6
  0 2 5 1 2 3
  1 5
  1 3
  2 6

Sample Output

  2 0 0 1 1 1

HINT

  1<=n,m<=300000,1<=Si,Ti<=n,0<=Wj<=n。

 

Solution

  出题人脑子被炮打了吧?这种题也敢放D1T2?这种题目对萌新一点也不友好啊,简直就跟NOIP2016的出题人一个德性!

  这道题的(超纲)做法依然很多,小C就说一说自己的做法。

  用链来询问树上的点明显比用树上的点来询问链好做,所以我们可以先思考对于每条树上路径,他可以被多少个点观测到。

  (小C才不会告诉你有这个想法其实是因为小C一开始看错了题目)

  我们可以把玩家的路径分为两部分,一条向上走(即从深度大的点走到深度小的点),一条向下走(从深度小到深度大的点)。

  记这条路径的起点为x,终点为y,它们的lca为z。

  对于向上走的链,如果上面有结点p能观察到该路径上的玩家,一定满足w[p]+dep[p]是一个定值;

  对于向下走的链,如果上面有结点p能观察到该路径上的玩家,一定满足w[p]-dep[p]是一个定值。

  对于每条路径,这个定值都很容易求出来。

  所以能观测到这条路径结点数量的点为:两条链上分别满足w[p]±dep[p]的结点p的数量之和,lca被算了两次,还要扣去lca。

  所以要维护的信息是二维,第一维是dfs序区间,第二维是w[p]±dep[p]。

  所以树剖+主席树?然而小C用的是更好用的括号序列。查询链上信息用到括号序列会使你的代码复杂度简单很多。

  不知道括号序列的可以看看小C的另一篇blog:http://www.cnblogs.com/ACMLCZH/p/7465161.html

  好了,回到现实,你的做法求的东西不是题目想要的。但实际上求这两种答案做法是一样的。

  每个结点都有一段dfs序区间,都有一个定值(w[p]±dep[p])。

  每条链都有一段dfs序区间,都有一个定值。

  实际上结点和链是一回事啊!

  设一条链的dfs序区间为[A,B],一个结点的dfs序区间为[L,R]。

  对一条链有贡献的点满足:A<=L<=B,R>B。

  所以对一个点有贡献的链满足:L<=B<R,A<=L。

  所以同样可以用主席树来完成,如果将链的dfs序区间排序,我们只需要用到线段树。

  然而我们发现每次是单点修改,单点查询,我们只要开个数组就可以了!!这下就不超纲啦!括号序列超纲?你在逗我?

  时间复杂度O(n+mlogm)。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MN 300005
#define MS 20
using namespace std;
struct edge{int nex,to;}e[MN<<1];
struct meg{int df,plc,ad;}b[MN<<1];
struct hp{int x,y,z,tpt;}a[MN];
int pos[MN<<1],dfbg[MN],dfed[MN],dep[MN],fa[MS][MN],hr[MN],cal[MN<<1],ans[MN],w[MN];
int dfn,din,n,m,pin;

inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
    while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
    return n*f;
}

inline void ins(int x,int y) {e[++pin]=(edge){hr[x],y}; hr[x]=pin;}

void dfs(int x,int fat,int depth)
{
    pos[dfbg[x]=++dfn]=x; dep[x]=depth; fa[0][x]=fat;
    for (register int i=hr[x];i;i=e[i].nex)
        if (e[i].to!=fat) dfs(e[i].to,x,depth+1);
    pos[dfed[x]=++dfn]=x;
}

int lca(int x,int y)
{
    register int i,k;
    if (dep[x]<dep[y]) swap(x,y);
    for (k=dep[x]-dep[y],i=0;k;k>>=1,++i) if (k&1) x=fa[i][x];
    if (x==y) return x;
    for (i=MS-1;i>=0;--i) if (fa[i][x]!=fa[i][y]) x=fa[i][x],y=fa[i][y];
    return fa[0][x];
}

bool cmp(const meg& a,const meg& b) {return a.df<b.df;}
void solve(int g1,int g2)
{
    register int i,j;
    sort(b+1,b+din+1,cmp);
    for (i=0,j=1;i<=n*2;++i)
    {
        ans[pos[i]]+=((i==dfbg[pos[i]])?-1:1)*cal[w[pos[i]]+g1*dep[pos[i]]+g2];
        for (;j<=din&&i>=b[j].df;++j) cal[b[j].plc]+=b[j].ad;
    }
}

int main()
{
    register int i,j,x,y;
    n=read(); m=read();
    for (i=1;i<n;++i)
        x=read(),y=read(),
        ins(x,y),ins(y,x);
    for (i=1;i<=n;++i) w[i]=read();
    dfs(1,0,1);
    for (i=1;i<MS;++i)
        for (j=1;j<=n;++j) fa[i][j]=fa[i-1][fa[i-1][j]];
    for (i=1;i<=m;++i)
        a[i].x=read(),a[i].y=read(),
        a[i].z=lca(a[i].x,a[i].y),a[i].tpt=dep[a[i].x]-dep[a[i].z],
        ans[a[i].z]-=(a[i].tpt==w[a[i].z]);
        
    memset(cal,0,sizeof(cal));
    for (din=0,i=1;i<=m;++i)
        ++cal[a[i].tpt+dep[a[i].z]],
        b[++din]=(meg){dfbg[a[i].z]-1,a[i].tpt+dep[a[i].z],-1},
        b[++din]=(meg){dfbg[a[i].x]  ,a[i].tpt+dep[a[i].z], 1};
    solve( 1,0);
    
    memset(cal,0,sizeof(cal));
    for (din=0,i=1;i<=m;++i)
        ++cal[a[i].tpt-dep[a[i].z]+n],
        b[++din]=(meg){dfbg[a[i].z]-1,a[i].tpt-dep[a[i].z]+n,-1},
        b[++din]=(meg){dfbg[a[i].y]  ,a[i].tpt-dep[a[i].z]+n, 1};
    solve(-1,n);
    
    printf("%d",ans[1]);
    for (i=2;i<=n;++i) printf(" %d",ans[i]);
}

 

C. Changing the Classrooms

  有2n节课程安排在n个时间段上。在第i(1≤i≤n)个时同段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室ci上课,而另一节课程在教室di进行。
在不提交任何申请的情况下,学生们需要按时间段的顺序依次完成所有的n节安排好的课程。如果学生想更换第i节课程的教室,则需要提出申请。若申请通过,学生就可以在第i个时间段去教室di上课,否则仍然在教室ci上课。
  由于更换教室的需求太多,申请不一定能获得通过。通过计算,牛牛发现申请更换第i节课程的教室时,申请被通过的概率是一个已知的实数ki,并且对于不同课程的申请,被通过的概率是互相独立的。学校规定,所有的申请只能在学期开始前一次性提交,并且每个人只能选择至多m节课程进行申请。这意味着牛牛必须一次性决定是否申请更换每节课的教室,而不能根据某些课程的申请结果来决定其他课程是否申请; 牛牛可以申请自己最希望更换教室的m门课程,也可以不用完这m个申请的机会,甚至可以一门课程都不申请。
  因为不同的课程可能会被安排在不同的教室进行,所以牛牛需要利用课间时间从一间教室赶到另一间教室。
  牛牛所在的大学有v个教室,有e条道路。每条道路连接两间教室,并且是可以双向通行的。由于道路的长度和拥堵程度不同,通过不同的道路耗费的体力可能会有所不同。当第i(1≤i≤n-1)节课结束后,牛牛就会从这节课的教室出发,选择一条耗费体力最少的路径前往下一节课的教室。
  现在牛牛想知道,申请哪几门课程可以使他在教室间移动耗费的体力值的总和的期望值最小,请你帮他求出这个最小值。

Input

  第一行四个整数n,m,v,e。n表示这个学期内的时间段的数量;m表示牛牛最多可以申请更换多少节课程的教室;v表示牛牛学校里教室的数量;e表示牛牛的学校里道路的数量。
  第二行n个正整数,第i个正整数表示ci,即第i个时间段牛牛被安排上课的教室;
  第三行n个正整数,第i(1≤i≤n)个正整数表示di,即第i个时间段另一间上同样课程的教室;
  第四行n个实数,第i(1≤i≤n)个实数表示ki,即牛牛申请在第i个时间段更换教室获得通过的概率。
  接下来e行,每行三个正整数aj,bj,wj,表示有一条双向道路连接教室aj,bj,通过这条道路需要耗费的体力值是wj;

Output

  输出一行,包含一个实数,四舍五入精确到小数点后恰好2位,表示答案。你的输出必须和标准输出完全一样才算正确。

Sample Input

  3 2 3 3
  2 1 2
  1 2 1
  0.8 0.2 0.5
  1 2 5
  1 3 3
  2 3 1

Sample Output

  2.80

HINT

  1≤n≤2000,0≤m≤2000,1≤v≤300,0≤e≤90000。
  1≤aj,bj,ci,di≤v,0≤ki≤1,1≤wj≤100。
  输入的实数最多包含3位小数。
  保证通过学校里的道路,从任何一间教室出发,都能到达其他所有的教室。
  保证四舍五入后的答案和准确答案的差的绝对值不大于4*10^-3。

 

Solution

  又臭又长的题面,有点像NOIP2016的画风,是为了契合时代吗?

  这题是这套模拟赛中唯一一道坑了小C的。

  俗话说“概率正着做,期望倒着做”,所以,这题求的是期望?

  然而注意到“一次性提交”,说明不能一边上课一边提交申请。所以题目要我们求的,其实是概率啊!

  所以注意题目中所述的“最小期望值”不是在转移的过程中取最小值,而是对于所有的固定转移方案取最小值。

  所以我们用f[i][j][0/1]表示上了前i节课,用了j次申请机会,第i节课是否有提出申请,得到的耗费体力的最小期望值。

  因为自己身处哪个教室会影响答案。转移就是枚举当前课和前一节课是否有申请,通过申请通过的概率来计算自己在那两间教室的概率。

  目标状态的花费期望=初始状态的花费期望+转移过程的花费期望。

  最短路什么之前跑个Floyd处理一下就行。

  时间复杂度O(v^3+n*m)。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x3FFFFFFF
#define MN 2005
#define MV 305
using namespace std;
int n,m,p,q;
int a[MN],b[MN],dis[MV][MV];
double w[MN],f[MN][MN][2],ans;

inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
    while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
    return n*f;
}

inline void rw(double& x,double y) {if (y<x) x=y;}

int main()
{
    register int i,j,k,x,y,z;
    n=read(); m=read(); p=read(); q=read();
    for (i=1;i<=n;++i) a[i]=read();
    for (i=1;i<=n;++i) b[i]=read();
    for (i=1;i<=n;++i) scanf("%lf",&w[i]);
    memset(dis,62,sizeof(dis));
    for (i=1;i<=q;++i)
    {
        x=read(); y=read(); z=read();
        dis[x][y]=dis[y][x]=min(dis[x][y],z);
    }
    for (i=1;i<=p;++i) dis[i][i]=0;
    for (k=1;k<=p;++k)
        for (i=1;i<=p;++i)
            for (j=1;j<=p;++j)
                if (i!=j&&i!=k&&j!=k) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    for (i=2;i<=n;++i)
        for (j=0;j<=min(m,i);++j)
        {
            f[i][j][0]=f[i][j][1]=INF;
            if (j<i)
            rw(f[i][j][0],f[i-1][j  ][0]+dis[a[i-1]][a[i]]);
            if (j==0) continue;
            rw(f[i][j][1],f[i-1][j-1][0]+dis[a[i-1]][b[i]]*   w[i  ] +
                                         dis[a[i-1]][a[i]]*(1-w[i  ]));
            if (j<i)
            rw(f[i][j][0],f[i-1][j  ][1]+dis[b[i-1]][a[i]]*   w[i-1] +
                                         dis[a[i-1]][a[i]]*(1-w[i-1]));
            if (j==1) continue;
            rw(f[i][j][1],f[i-1][j-1][1]+dis[b[i-1]][b[i]]*   w[i] *   w[i-1] +
                                         dis[b[i-1]][a[i]]*(1-w[i])*   w[i-1] +
                                         dis[a[i-1]][b[i]]*   w[i] *(1-w[i-1])+
                                         dis[a[i-1]][a[i]]*(1-w[i])*(1-w[i-1]));
        }
    ans=INF;
    for (i=0;i<=min(m,n);++i) ans=min(ans,min(f[n][i][0],f[n][i][1]));
    printf("%.2lf",ans);
}

 

D. The Problem of Combination Number

  给定n,m和k,求对于所有的0<=i<=n,0<=j<=min(i,m)有多少对(i,j)满足C(i,j)是k的倍数。(C(i,j)为i个物品中任选j个方案数)

Input

  第一行两个整数t,k,t代表该测试点总共有多少组测试数据。
  接下来t行每行两个整数n,m。

Output

  t行,每行一个整数代表答案。

Sample Input

  1 2
  3 3

Sample Output

  1

HINT

  0<=n,m<=2000,1<=k<=21,1<=T<=10000。

 

Solution

  似乎去年11月份还做过原题来着,当时还写了个质因数分解,现在想想真是太蠢了。

  明显的n*m做法,根据杨辉三角,C(i,j)=C(i-1,j)+C(i-1,j-1),可以在O(n*m)的时间内求出所有组合数模k的余数。

  之后做一个二维前缀和就好了啊。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MN 2005
using namespace std;
int t,mod;
int f[MN][MN],sum[MN][MN];

inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
    while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
    return n*f;
}

int main()
{
    register int i,j,x,y;
    t=read(); mod=read();
    memset(f,62,sizeof(f));
    for (i=0;i<MN;++i) f[i][0]=f[i][i]=1%mod;
    for (i=2;i<MN;++i)
        for (j=1;j<i;++j)
        {
            f[i][j]=f[i-1][j-1]+f[i-1][j];
            if (f[i][j]>=mod) f[i][j]-=mod;
        }
    if (mod==1) for (i=0;i<MN;++i) sum[0][i]=1,sum[i][0]=i;
    for (i=1;i<MN;++i)
        for (j=1;j<MN;++j)
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(f[i][j]==0);
    while (t--)
    {
        x=read(); y=read();
        printf("%d\n",sum[x][y]);
    }
}

 

E. Earthworm

  蛐蛐国里现在共有n只蚯蚓(n为正整数)。每只蚯蚓拥有长度,我们设第i只蚯蚓的长度为ai(i=1,2,...,n),并保证所有的长度都是非负整数(即:可能存在长度为0的蚯蚓)。
每一秒,神刀手会在所有的蚯蚓中,准确地找到最长的那一只(如有多个则任选一个)将其切成两半。神刀手切开蚯蚓的位置由常数p(是满足0<p<1的有理数)决定,设这只蚯蚓长度为x,神刀手会将其切成两只长度分别为[px]和x-[px]的蚯蚓([c]表示对c向下取整)。特殊地,如果这两个数的其中一个等于0,则这个长度为0的蚯蚓也会被保留。此外,除了刚刚产生的两只新蚯蚓,其余蚯蚓的长度都会增加q(是一个非负整常数)。蛐蛐国王希望知道m秒内的战况。
  具体来说,他希望知道:
    m秒内,每一秒被切断的蚯蚓被切断前的长度(有m个数);
    m秒后,所有蚯蚓的长度(有n+m个数)。

Input

  第一行包含六个整数n,m,q,u,v,t,你需要自己计算p=u/v,t是输出参数。
  第二行包含n个非负整数,为a1,a2,…,an,即初始时n只蚯蚓的长度。

Output

  第一行输出[m/t]个整数,按时间顺序,依次输出第t秒,第2t秒,第3t秒……被切断蚯蚓(在被切断前)的长度。
  第二行输出[(n+m)/t]个整数,输出m秒后蚯蚓的长度;需要按从大到小的顺序,依次输出排名第t,第2t,第3t……的长度。
  同一行中相邻的两个数之间,恰好用一个空格隔开。即使某一行没有任何数需要输出,你也应输出一个空行。

Sample Input

  3 7 1 1 3 1
  3 3 2

Sample Output

  3 4 4 4 5 5 6
  6 6 6 5 5 4 4 3 2 2

HINT

  1<=n<=10^5,0<m<7*10^6,0<u<v<10^9,0<=q<=200,1<t<71,0<ai<10^8。

 

Solution

  题面拥有把人吓傻的能力。

  如果是mlog(n+m)的做法的话是堆的裸题,然而这道题要求我们O(n+m)的做法。

  这个做法同样很神,开三个队列,一开始所有蚯蚓排序后塞在第一个队列里。

  每次找出三个队列队首最大的那一个(也就是所有蚯蚓中最长的那一个),切开来,

  把长的那一段放进第二个队列,把短的那一段放进第三个队列。

  这样可以保证三个队列都是单调的,简单证明一下。

  第一个队列没有新增元素,本来就是单调的。

  如果蚯蚓长度不增长的话,每次得到的最长蚯蚓的长度是递减的,

  所以放入第二、三个队列的蚯蚓长度也是递减的,即使有增长量也是同理的。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define INF 1LL<<62
#define MN 8000005
using namespace std;
int n,m,ad,tms,mxi,fz,fm;
int hd[3],tl[3];
ll q[3][MN],del,mx;

inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
    while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
    return n*f;
}

bool cmp(int x,int y) {return x>y;}

int main()
{
    register int i,j;
    ll x1,x2;
    n=read(); m=read(); ad=read(); fz=read(); fm=read(); tms=read();
    for (i=1;i<=n;++i) q[0][i]=read();
    sort(q[0]+1,q[0]+n+1,cmp);
    hd[0]=hd[1]=hd[2]=1;
    tl[0]=n; tl[1]=tl[2]=0;
    for (i=1,del=0;i<=m;++i,del+=ad)
    {
        for (mx=-INF,j=0;j<3;++j) if (hd[j]<=tl[j]&&q[j][hd[j]]>mx) mx=q[j][hd[j]],mxi=j;
        if (i==tms) printf("%lld",mx+del);
        else if (i%tms==0) printf(" %lld",mx+del);
        ++hd[mxi];
        x1=(double)(1LL*fz*(mx+del))/fm; x2=mx+del-x1;
        q[1][++tl[1]]=x1-del-ad; q[2][++tl[2]]=x2-del-ad;
    }
    puts("");
    for (i=1;i<=n+m;++i)
    {
        for (mx=-INF,j=0;j<3;++j) if (hd[j]<=tl[j]&&q[j][hd[j]]>mx) mx=q[j][hd[j]],mxi=j;
        if (i==tms) printf("%lld",mx+del);
        else if (i%tms==0) printf(" %lld",mx+del);
        ++hd[mxi];
    }
}

 

F. Angry Birds

  有一架弹弓位于(0,0)处,每次Kiana可以用它向第一象限发射一只红色的小鸟,小鸟们的飞行轨迹均为形如y=ax^2+bx的曲线,其中a,b是Kiana指定的参数,且必须满足a<0。
  当小鸟落回地面(即x轴)时,它就会瞬间消失。
  在游戏的某个关卡里,平面的第一象限中有n只绿色的小猪,其中第i只小猪所在的坐标为(xi,yi)。
  如果某只小鸟的飞行轨迹经过了(xi,yi),那么第i只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;
  如果一只小鸟的飞行轨迹没有经过(xi,yi),那么这只小鸟飞行的全过程就不会对第i只小猪产生任何影响。
  而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。
  这款神奇游戏的每个关卡对Kiana来说都很难,所以Kiana还输入了一个神秘的指令m,使得自己能更轻松地完成这个游戏。
    如果m=0,表示Kiana输入了一个没有任何作用的指令。
    如果m=1,则这个关卡将会满足:至多用⌈(n+1)/3⌉只小鸟即可消灭所有小猪。
    如果m=2,则这个关卡将会满足:一定存在一种最优解,其中有一只小鸟消灭了至少⌊n/3⌋只小猪。(⌈x⌉为上取整,⌊n/3⌋为下取整)
  假设这款游戏一共有T个关卡,现在Kiana想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。由于她不会算,所以希望由你告诉她。

Input

  第一行包含一个正整数T,表示游戏的关卡总数。
  下面依次输入这T个关卡的信息。每个关卡第一行包含两个非负整数n,m,分别表示该关卡中的小猪数量和Kiana输入的神秘指令类型。接下来的n行中,第i行包含两个正实数(xi,yi),表示第i只小猪坐标为(xi,yi)。

Output

  对每个关卡依次输出一行答案。
  输出的每一行包含一个正整数,表示相应的关卡中,消灭所有小猪最少需要的小鸟数量。

Sample Input

  6
  2 0
  1.00 3.00
  3.00 3.00
  5 2
  1.00 5.00
  2.00 8.00
  3.00 9.00
  4.00 8.00
  5.00 5.00
  2 0
  1.41 2.00
  1.73 3.00
  3 0
  1.11 1.41
  2.34 1.79
  2.98 1.49
  5 0
  2.72 2.72
  2.72 3.14
  3.14 2.72
  3.14 3.14
  5.00 5.00
  10 0
  7.16 6.28
  2.02 0.38
  8.33 7.78
  7.68 2.09
  7.46 7.86
  5.77 7.44
  8.24 6.72
  4.42 5.11
  5.42 7.79
  8.15 4.99

Sample Output

  1
  1
  2
  2
  3
  6

HINT

  1<=n<=18,0<=m<=2,0<xi,yi<10,输入中的实数均保留到小数点后两位。
  数据保证同一个关卡中不存在两只坐标完全相同的小猪。

 

Solution

  出题人还真敢出这种题目啊?这种码农题只有像NOIP这种大型比赛能够保证标程正确性的情况下才能出出来啊。有一点佩服出题人了。

  反正就是很契合NOIP每年一道玄学码农题的风格。

  暴力谁都会打,就看你打得够不够优秀。小C说说自己的处理方式。

  一开始先把所有小猪按照横坐标排序。

  对于每对i,j(i<j),预处理出经过(xi,yi),(xj,yj)能打掉的小猪集合,集合状压成一个整数。

  而且如果你仔细思考,你会发现状压DP是有转移顺序的。

  我们把所有状态分层,对于每个集合状态S,设x为S中没有被打掉的编号最小的猪,则S应被归为第x层。

  转移时逐层转移即可,时间复杂度O(T*2^n*n)。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define MM 300005
#define MN 20
#define eps 1e-12
using namespace std;
struct node{double x,y;}a[MN];
int ys[MN],dir[MN][MN],gst[MN*MN],st[MN],tl[MN],f[MM],q[MN][MM],bel[MM];
bool inq[MM],u[MN*MN];
int t,n,opt,tp;

inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
    while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
    return n*f;
}

inline void cal(double x1,double y1,double x2,double y2,double& A,double& B)
{
    if (x1==x2) {A=1; return;}
    if (fabs(x2*y1-x1*y2)<eps) {A=1; return;}
    A=(x2*y1-x1*y2)/(x1*x2*(x1-x2));
    B=(x2*x2*y1-x1*x1*y2)/(x1*x2*(x2-x1));
}
inline bool inarc(double x,double y,double A,double B) {return fabs(A*x*x+B*x-y)<eps;}

bool cmp(const node& a,const node& b) {return a.x<b.x;}

int main()
{
    register int i,j,k,l,tin,nx;
    double A,B;
    bool usd;
    t=read();
    for (ys[1]=1,i=2;i<MN;++i) ys[i]=ys[i-1]<<1;
    for (i=0;i<(1<<18);++i)
    {
        for (j=1;j<=18;++j) if (!(i&ys[j])) break;
        bel[i]=j;
    }
    while (t--)
    {
        n=read(); opt=read(); tp=0;
        memset(dir,0,sizeof(dir));
        for (i=1;i<=n;++i) scanf("%lf%lf",&a[i].x,&a[i].y);
        sort(a+1,a+n+1,cmp);
        for (i=1;i<n;++i)
            for (j=i+1;j<=n;++j)
            {
                if (dir[i][j]) continue;
                cal(a[i].x,a[i].y,a[j].x,a[j].y,A,B);
                if (A>0) continue;
                gst[++tp]=0; tin=0;
                for (k=i;k<=n;++k) if (inarc(a[k].x,a[k].y,A,B)) st[++tin]=k,gst[tp]|=ys[k];
                for (k=1;k<tin;++k)
                    for (l=k+1;l<=tin;++l) dir[st[k]][st[l]]=tp;
            }
        for (i=1;i<=n+1;++i) tl[i]=0;
        memset(f,62,sizeof(f));
        f[q[1][++tl[1]]=0]=0; inq[0]=true;
        for (i=1;i<=n;++i)
            for (j=1;j<=tl[i];inq[q[i][j++]]=false)
            {
                for (usd=false,k=i+1;k<=n;++k)
                {
                    if (!dir[i][k]||u[dir[i][k]]) continue;
                    u[dir[i][k]]=true; usd=true;
                    nx=q[i][j]|gst[dir[i][k]];
                    f[nx]=min(f[nx],f[q[i][j]]+1);
                    if (!inq[nx]) inq[q[bel[nx]][++tl[bel[nx]]]=nx]=true;
                }
                if (!usd)
                {
                    nx=q[i][j]|ys[i];
                    f[nx]=min(f[nx],f[q[i][j]]+1);
                    if (!inq[nx]) inq[q[bel[nx]][++tl[bel[nx]]]=nx]=true;
                }
                else for (k=i+1;k<=n;++k) u[dir[i][k]]=false;
            }
        printf("%d\n",f[ys[n+1]-1]); inq[ys[n+1]-1]=false;
    }
}

 

Last Word

  两天下来除了D1T3看错题目+写错变量爆了之后,其他题都1A了。

  不过说实在的,这套模拟赛确实出得不错,而且从部分分的设计都可以看出,出题人十分用心。

  不要跟小C说这套题很眼熟!小C不听!!不听!!!

posted @ 2017-10-11 14:59  ACMLCZH  阅读(318)  评论(0编辑  收藏  举报