NOIP2017模拟赛(八)总结

来源:互联网 发布:matlab智能算法 编辑:程序博客网 时间:2024/06/12 00:49

NOIP2017模拟赛(八)解题报告

T1:
路径
题目描述
在二维坐标平面里有N个整数点,Bessie要访问这N个点。刚开始Bessie在点(0,0)处。 每一步,Bessie可以走到上、下、左、右四个点。即假设Bessie当前所在点的坐标是(x,y),那么它下一步可以移动到(x,y+1), (x,y-1), (x+1,y), (x-1,y)之一。
Bessie目标是找到一个移动序列,满足下面的条件:
1、从点(0,0)出发。
2、对于给定的那N个点,每个点至少被访问一次。
3、可以选择那N个给定点中任意一个作为结束点。
现在为了增加难度,农夫规定Bessie走过的所有步数之和的奇偶性必须为wantedParity,显然wantedParity 是0或1,是题目给定的,0表示偶,1表示奇。Bessie立刻感觉到了难度的增加,如果存在满足条件的移动序列,那么输出CAN,否则输出CANNOT。
输入格式 1883.in
多组测试数据。
第一行,一个整数g,表示有g组测试数据,1 <= g <= 5。
每组测试数据格式:
第一行,N、wantedParity。 1 <= N <= 50。
接下来有N行,每行两个整数:x, y,表示第i个点的坐标值。
范围都在【-1000000,1000000】。输入的N个点不会有重叠,且不会有(0,0)点。
输出格式 1883.out
共g行,每行输出CAN或CANNOT。
输入样例 1883.in
5
4 0
-1 -1
-1 1
1 1
1 -1
3 1
-5 2
-3 0
2 3
2 1
1001 0
-4000 0
3 0
11 -20
21 42
0 7
2 1
0 10
6 -20
输出样例 1883.out
CAN
CAN
CAN
CANNOT
CANNOT
样例解释
第一组测试数据:其中一个合法序列如下:
•2 steps: (0,0) -> (-1,-1).
•2 steps: (-1,-1) -> (-1,1).
•2 steps: (-1,1) -> (1,1).
•2 steps: (1,1) -> (1,-1).
第二组测试数据:其中一个合法序列:
•7 steps: (0,0) -> (-5,2).
•4 steps: (-5,2) -> (-3,0).
•8 steps: (-3,0) -> (2,3).
第三组测试数据:其中一个合法序列:
(0,0) -> (-4000,0) -> (1001, 0)。
题目分析:拿到这题的时候,本着第一题都是水题的思想,我提出了一个大胆的结论:从(0,0)开始,每个点走一遍的路径的奇偶性,只跟这条路径的终点有关。然而我觉得还是稳一点比较好,于是我开始尝试着证明。首先我们发现,从点(a1,b1)走到点(a2,b2),再走回来,路径长度一定是偶数,因为如果(a1-a2+b1-b2)是奇/偶数,那么去回都一定要走奇/偶数步。这样从一个点出发,经过很多个点最后再回来,走的步数一定是偶数。既然这样,中间每个点经过多少次,是否重复走就不重要了。而我们现在以任意一个点(a,b)为终点,就是相当于最后不用走(a,b)->(0,0)这条路。如果这条路长度是奇/偶数,那么就可以使路径总长度为奇/偶数。于是我们扫一遍每个点的坐标判断一下即可。
CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;int g,N,wantedParity;bool odd,even;int main(){    freopen("a.in","r",stdin);    freopen("a.out","w",stdout);    scanf("%d",&g);    while (g--)    {        scanf("%d%d",&N,&wantedParity);        odd=even=false;        while (N--)        {            int x,y;            scanf("%d%d",&x,&y);            x=x+y+20000000;            x=(x&1);            if (x) odd=true;            else even=true;        }        if ( ( !wantedParity && !even ) || ( wantedParity && !odd ) ) printf("CANNOT\n");        else printf("CAN\n");    }    return 0;}

T2:
冠军
题目描述
有N个拳手参加擂台赛,这个人的编号是0至N-1。有N个位置,编号从0至N-1。每个位置分配一个拳手,显然共有N!种不同的分配方案。
对于一种具体的分配方案,站在位置0的拳手与站在位置1的拳手比赛,胜者进入下一轮,败者被淘汰。站在位置2的拳手与站在位置3的拳手比赛,胜者进入下一轮,败者被淘汰,同样道理,站在位置4的拳手与站在位置5的拳手比赛,胜者进入下一轮,败者被淘汰。。。最终会产生一个冠军拳手。
如下图所示:

已知N一定是2的若干次幂,而且不超过16,也就是说N是{2,4,8,16}之中的某一个数。
现在的问题是:有多少种不同的分配方案,使得第i个选手能最终成为冠军?不妨假设该数值是ans[i]。
你的任务就是输出:ans[0]、ans[1]、….ans[N-1]。
输入格式 1792.in
第一行,一个整数N。 N=2或者4或者8或者16。
接下来是N行N列的字符矩阵,第i行第j列的字符如果是’Y’,表示第i个拳手如果跟第j个拳手直接比赛的话,第i个拳手会赢第j个拳手,如果字符是‘N’,表示第i个拳手会输给第j个拳手。
注意:
1、第i行第i列的字符一定是’N’
2、拳手的胜负不一定满足传递性。例如:第i个拳手能赢第j个拳手,第j个拳手能赢第k个拳手,但第i个拳手可能会输给第k个拳手。
3、如果第i行第j列的字符是’Y’,那么第j行第i列的字符一定是’N’,即拳手i和拳手j比赛,有且只有一个胜者。
输出格式 1792.out
共N行,第i行是ans[i]。
输入样例 1792.in
输入样例一:
2
NN
YN
输入样例二:
4
NYNY
NNYN
YNNY
NYNN
输入样例三:
8
NYNYNYNY
NNYNYNYY
YNNNNNNN
NYYNNYNY
YNYYNYYY
NYYNNNNN
YNYYNYNN
NNYNNYYN
输出样例 1792.out
输出样例一:
0
2
输出样例二:
8
0
16
0
输出样例三:
4096
8960
0
2048
23808
0
1408
0
【样例解释】
第一样例:不管拳手1站在位置0还是站在位置1,都能战胜拳手0。
题目分析:这题应该是这次比赛唯一一道有点思维难度的题目了吧,还是挺有趣的。
如果用16!的暴搜肯定超时,于是我开始考虑记忆化搜索。假设我们要求16个人中每个人获胜的方案数,我们可以在其中选8个人跑到上半部分比赛,另外8个人跑到下半部分比赛,这就变成了两个子问题。最后我们枚举上下半部分是谁获胜,合并起来即可。
现在问题来了,这样的时间复杂度是多少呢?我们知道记忆化搜索的本质是在填充f数组。在这题中,只有当s的二进制位中有2k个1时,f[s]才会被计算到。假设N=16,当s中有16个1时,我们要枚举选哪8个1放到上半部分;当s中有8个1时,我们要枚举选哪4个1放到上半部分……。这样的总时间就是:

(C1616C816+C816C48+C48C24+C24C12)162

然而这样算出来的时间很卡,我造了个极限数据,跑了3s。
所以我们要剪枝。但左边括号里的这块肯定省不了,于是我们看看右边。162的时间在于我要枚举左半部分s1谁获胜(假设是i),右半部分s2谁获胜(假设是j),来更新f[s]。但事实上只有i在s1中,j在s2中时,枚举才是有用的。于是我们预处理出对于每一个s,有哪些人在它的集合中,分别是谁,合并的时候只枚举这些人,就可以大大提高效率。事实上,加了这个强有力的剪枝之后,我的极限数据不到0.3s就跑出来了。
CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=18;const int maxs=(1<<16)+100;typedef long long LL;struct data{    LL num[maxn];    bool vis;} f[maxs];int P[maxs];int Q[maxs][maxn];bool win[maxn][maxn];int N;void Dfs1(int );void Dfs2(int ori,int S,int s,int Left){    if (!Left)    {        Dfs1(s);        int t=ori^s;        Dfs1(t);        int mi=Q[s][0],mj=Q[t][0];        for (int i=1; i<=mi; i++)            for (int j=1; j<=mj; j++)            {                int a=Q[s][i],b=Q[t][j];                int y=a;                if (!win[a][b]) y=b;                f[ori].num[y]+=f[s].num[a]*f[t].num[b];            }        return;    }    int cnt=P[S];    while (cnt>=Left)    {        int v=S&(-S);        S^=v;        Dfs2(ori,S,s+v,Left-1);        cnt--;    }}void Dfs1(int s){    if (f[s].vis) return;    int cnt=P[s];    if (cnt==1)    {        for (int i=0; i<N; i++) if (s&(1<<i)) f[s].num[i]++;        f[s].vis=true;        return;    }    cnt>>=1;    Dfs2(s,s,0,cnt);    f[s].vis=true;}int main(){    freopen("b.in","r",stdin);    freopen("b.out","w",stdout);    scanf("%d",&N);    for (int i=0; i<N; i++)        for (int j=0; j<N; j++)        {            char c=getchar();            while ( c!='Y' && c!='N' ) c=getchar();            if (c=='Y') win[i][j]=true;            else win[i][j]=false;        }    for (int i=1; i<maxs; i++) P[i]=P[ i-(i&(-i)) ]+1;    for (int i=1; i<maxs; i++)        for (int j=0; j<16; j++)            if ( i&(1<<j) ) Q[i][ ++Q[i][0] ]=j;    int ms=(1<<N)-1;    Dfs1(ms);    for (int i=0; i<N; i++) cout<<f[ms].num[i]<<endl;    return 0;}

T3:
指纹
题目描述
随着科技的发展,当今很多企业向社会推出了越来越多的结合指纹识别技术的高科技产品。其中以需要进行身份验证或身份识别类型的产品居多,如门禁系统、手提电脑、指纹硬盘。
对任何生物识别系统来说,输入数据的质量对于系准确率有着重大的影响。为了获得较高质量的指纹图像,近年来指纹采集设备不断地被更新,各种先进的指纹采集技术也逐渐被引入到实际产品中。尽管如此,由于手指皮肤情况、空气湿度、灰尘等一些因素的影响,依旧存在着大量的质量不高的指纹图像。
通常我们可以通过编号为A,B,C,D的四个属性来评估一个指纹图像的质量的高低:
A为杂点的数量;B为折痕的数量;C为脊线断续程度;D为脊线粘连程度。这四个属性值越小表示该图像在相应方面表现越优。
由于指纹图质量评估研究的需要,我们通过对一个人的指纹进行多次采样后得到多个不同质量的指纹图像,并将其各质量属性记录在一个数据库中(不同图像的各属性值均不相同)。对于两个指纹图像,单个属性的好坏并不能说明图像质量的高低。比如图像1杂点数比图像2的少,但有可能图像1的粘连程度比图像2高得多,因此不能武断地认为图像1就比图像2好。
但是如果一个图像有不少于三个属性都优于另一个图像,那么我们有理由相信前者的质量要优于后者。对于数据库中的一个指纹图像I,如果存在另一个图像J,J有不少于三个质量属性优于图像I,那么我们认为图像I是一种“累赘”。
为了减少指纹图像数据库的大小,我们希望去除其中的累赘图像,现在实验室需要你的帮忙,找出该部分图像。为方便就算,我们已经分别按四个属性,计算出了所有图像按该属性的优劣程度排序的名次。
输入格式 1884.in
第一行,包含一个正整数N,表示有N张指纹图像,编号为1,2,3…N。1 <= N <= 100000。
接下来的N行,每行有4个正整数Ai,Bi,Ci,Di。第i行表示编号为i的图像在所有图像中,其A属性排名为Ai, B属性排名为Bi, C属性排名为Ci, D属性排名为Di。所有Ai取遍1到N这N个自然数,1表示最优,N表示最差,类似地,Bi,Ci,Di也一样。
输出格式 1884.out
第一行,一个整数M,表示累赘的指纹图像个数。接下来有M行,每行包含一个整数,表示累赘的图像的编号。标号从小到大输出。
输入样例 1884.in
6
1 1 2 6
2 3 3 4
3 4 1 3
4 2 6 5
5 6 5 1
6 5 4 2
输出样例 1884.out
4
2
4
5
6
题目分析:本次比赛我的做题顺序是1->2->3,做这题的时候我还有80min。我一开始往CDQ分治去想。但后来我发现它不是要你求对于每个i有多少个j使它成为累赘,而是问有没有j。这就是一道水题了嘛……我们在a,b,c,d中枚举三个参数(假设是a,b,c)。接下来我们将数据按a值排序,然后从左到右加进treap里,treap的key值为b,维护一个附加域c,并保存子树中c的最小值minc。这样我们做到第i个的时候,treap中的元素一定是比它的a值要小的,然后我们用log(n)的二叉查找,找所有b比它小的元素中c的最小值,看看是否小于i的c值即可。时间复杂度O(nlog(n))(话说这就是数据结构裸题啊)。
CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>#include<ctime>using namespace std;const int maxn=100100;int Min(int x,int y){    if (x<y) return x;    return y;}struct Tnode{    int valC,valB,minB,fix;    Tnode *lson,*rson;    void Up()    {        minB=valB;        if (lson) minB=Min(minB,lson->minB);        if (rson) minB=Min(minB,rson->minB);    }} tree[maxn];Tnode *Root;int cur;struct data{    int A,B,C,id;} pic[maxn];bool vis[maxn];int a[maxn];int b[maxn];int c[maxn];int d[maxn];int n;bool Comp(data x,data y){    return x.A<y.A;}int Find(Tnode *P,int v){    if (!P) return n+1;    if ( P->valC < v )        return Min( Min( Find(P->rson,v) , P->valB ) , (P->lson? P->lson->minB:n+1) );    else return Find(P->lson,v);}Tnode *New_node(int vc,int vb){    cur++;    tree[cur].valC=vc;    tree[cur].valB=tree[cur].minB=vb;    tree[cur].fix=rand();    tree[cur].lson=tree[cur].rson=NULL;    return tree+cur;}void Right_turn(Tnode *&P){    Tnode *W=P->lson;    P->lson=W->rson;    W->rson=P;    P=W;    P->rson->Up();    P->Up();}void Left_turn(Tnode *&P){    Tnode *W=P->rson;    P->rson=W->lson;    W->lson=P;    P=W;    P->lson->Up();    P->Up();}void Insert(Tnode *&P,int vc,int vb){    if (!P) P=New_node(vc,vb);    else        if ( vc < P->valC )        {            Insert(P->lson,vc,vb);            if ( P->lson->fix < P->fix ) Right_turn(P);            else P->Up();        }        else        {            Insert(P->rson,vc,vb);            if ( P->rson->fix < P->fix ) Left_turn(P);            else P->Up();        }}void Solve(){    sort(pic+1,pic+n+1,Comp);    Root=NULL;    cur=-1;    for (int i=1; i<=n; i++)    {        int x=Find(Root,pic[i].C);        if (x<pic[i].B) vis[ pic[i].id ]=true;        Insert(Root,pic[i].C,pic[i].B);    }}int main(){    freopen("c.in","r",stdin);    freopen("c.out","w",stdout);    srand( time(0) );    rand(); rand();    scanf("%d",&n);    for (int i=1; i<=n; i++)        scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);    for (int i=1; i<=n; i++)        pic[i].A=a[i],pic[i].B=b[i],pic[i].C=c[i],pic[i].id=i;    Solve();    for (int i=1; i<=n; i++)        pic[i].A=a[i],pic[i].B=b[i],pic[i].C=d[i],pic[i].id=i;    Solve();    for (int i=1; i<=n; i++)        pic[i].A=a[i],pic[i].B=c[i],pic[i].C=d[i],pic[i].id=i;    Solve();    for (int i=1; i<=n; i++)        pic[i].A=b[i],pic[i].B=c[i],pic[i].C=d[i],pic[i].id=i;    Solve();    int num=0;    for (int i=1; i<=n; i++) if (vis[i]) num++;    printf("%d\n",num);    for (int i=1; i<=n; i++) if (vis[i]) printf("%d\n",i);    return 0;}

总结:AK了,没什么值得高兴的。第一题裸题,第三题模板题,第二水题。我反而觉得我写数据生成器的能力要加强。第三题对拍的时候,我的方法是先令a[i]=b[i]=i,c[i]=d[i]=Ni+1,然后随机交换a,b,c,d中的两个元素N/2次,作为数据。但这样生成的数据非常弱,比如N=5000时,答案基本等于4998这个值。这或许就是随机化的弊端吧,又或者是我这种生成数据的方法不优……还是要继续探索一下别的方法。(好像从剩下的数中随机选一个数作为a[i]的这种方法也不优啊)

原创粉丝点击