NOIP2017提高组模拟赛 8(总结)

NOIP2017提高组模拟赛 8(总结)

第一题 路径

  一个定理,假如点i到点j有一条路径是偶数,那么其他路径也一定是偶数(最短距离需要的上下左右是固定的,多余的上和下抵消,多余的左和右抵消),反之亦然。
  简单的证明,假如需要的是偶数,那么只需判断是否存在一个点到(0,0)的距离是偶数就行了(作为终点),其它点可以从(0,0)出发到(xi,yi)再回到(0,0),这样肯定为偶数步数。需要的是奇数的也类似。

#include<cstdio>
#include<algorithm>
#include<cmath>

#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))

typedef long long ll;

using namespace std;

int ng,n,ne,a,b,s[5];

int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d",&ng);
    while(ng--)
    {
        scanf("%d%d",&n,&ne);
        s[0]=s[1]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&a,&b);
            int c=abs(a)+abs(b);
            s[c&1]++;
        }
        if(ne==0)
        {
            if(s[0]>0) printf("CAN\n"); else printf("CANNOT\n");
        } else
        if(ne==1)
        {
            if(s[1]>0) printf("CAN\n"); else printf("CANNOT\n");
        }
    }
    return 0;
}

第二题 冠军

  已知N一定是2的若干次幂,而且不超过16,也就是说N是{2,4,8,16}之中的某一个数。
  现在的问题是:有多少种不同的分配方案,使得第i个选手能最终成为冠军?不妨假设该数值是ans[i]。
  你的任务就是输出:ans[0]、ans[1]、….ans[N-1]。
  一道状压DP题,不过要注意优化,过多的无用状态没过滤掉会被卡。
  若beat[i][j]==’Y’  F[S][i]+=F[S1][i]*F[S-S1][j]
  若beat[i][j]==’N’  F[S][j]+=F[S1][i]*F[S-S1][j]
  F[S][i]表示选了S这几个拳手(2进制,0为不选,1为选),最终胜利的拳手为i的方案数。
  结果会爆INT,要用LONG LONG来存。
  (PS:一开始,码了搜索去找状态,结果WA了,机子单步不了,又找不出哪里错。后来比赛结束重新码了程序才过。)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))
#define pd(S,i) ((S&(1<<(i-1)))>0)

typedef long long ll;

using namespace std;

const int MP=100500;
int n,hn,nn,B[20],C[20];
char st[20];
bool ff[20][20];
int Go[6][MP],Set[6][MP],one[MP],A[20];
ll f[MP][20];

int main()
{
    freopen("b.in","r",stdin);
    freopen("b.out","w",stdout);
    scanf("%d",&n); hn=n>>1;
    nn=(1<<n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",st);
        for(int j=1;j<=n;j++) ff[i][j]=(st[j-1]=='Y');
    }
    one[0]=0;
    for(int i=1;i<nn;i++) one[i]=one[i>>1]+(i&1);
    for(int i=1;i<nn;i++)
    {
        if(one[i]==1) { Set[0][++Set[0][0]]=i; if(i<(1<<2)) Go[0][++Go[0][0]]=i; } else
        if(one[i]==2) { Set[1][++Set[1][0]]=i; if(i<(1<<4)) Go[1][++Go[1][0]]=i; } else
        if(one[i]==4) { Set[2][++Set[2][0]]=i; if(i<(1<<8)) Go[2][++Go[2][0]]=i; } else
        if(one[i]==8) { Set[3][++Set[3][0]]=i; if(i<(1<<16)) Go[3][++Go[3][0]]=i; }
    }
    for(int i=1;i<=Set[0][0];i++)
    {
        int S=Set[0][i];
        for(int j=1;j<=n;j++)
        if(pd(S,j)) f[S][j]=1;
    }
    Set[4][0]=1; Set[4][1]=(1<<16)-1;
    int go=0;
    if(n==16) go=4; else
    if(n==8) go=3; else
    if(n==4) go=2; else go=1;
    for(int ho=1;ho<=go;ho++)
    {
        for(int i=1;i<=Set[ho][0];i++)
        {
            int S=Set[ho][i],S1; A[0]=0;
            for(int j=1;j<=n;j++)
            if(pd(S,j)) A[++A[0]]=j;
            for(int h=1;h<=Go[ho-1][0];h++)
            {
                int ss=Go[ho-1][h]; S1=0;
                B[0]=0; C[0]=0;
                for(int k=1;k<=(1<<ho);k++)
                {
                    if(pd(ss,k))
                    {
                        S1|=(1<<(A[k]-1));
                        B[++B[0]]=A[k];
                    } else C[++C[0]]=A[k];
                }
                for(int fa=1;fa<=B[0];fa++)
                for(int fb=1;fb<=C[0];fb++)
                if(ff[B[fa]][C[fb]]) f[S][B[fa]]+=f[S1][B[fa]]*f[S-S1][C[fb]];
                else f[S][C[fb]]+=f[S1][B[fa]]*f[S-S1][C[fb]];
            }
        }
    }
    for(int i=1;i<=n;i++) printf("%I64d\n",f[nn-1][i]);
    return 0;
}

第三题 指纹

  这题比前一题好打多了,不过一直在调第二题,这题便没有做……
  每一个指纹有四个参数ABCD
  先考虑ABC,因为假设i的D很小,但ABC很大,那么它也会被刷掉。
  按A排序,用B作为下标,存C。用线段树维护一段区间的最小值。
  A从小到大排序,对于i来说,比它的A小的都已经加入了线段树中,若findmin(1,B)比C小,证明一定存在x的ABC均小于i,那么i也没有存在的意义了(打del标记)。
  再考虑ABD,ACD,BCD,一共四种情况,逐一筛掉无用的指纹就行了。

#include<cstdio>
#include<algorithm>
#include<cmath>

#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))

typedef long long ll;

using namespace std;

const int oo=1e9;
const int N=1e5;
struct data { int a,b,c,d,id; } d[N+100],dg[N+100];
int n;
bool ffg[N+100];
int tree[N<<2];

bool cmp(data A,data B) { return (A.a<B.a); }

void build(int ro,int L,int R)
{
    if(L==R) { tree[ro]=oo; return; }
    int Mid=(L+R)>>1;
    build(ro<<1,L,Mid);  build(ro<<1|1,Mid+1,R);
    tree[ro]=imin(tree[ro<<1],tree[ro<<1|1]);
}

void pre(int ho)
{
    for(int i=1;i<=n;i++) dg[i]=d[i];
    if(ho==1) for(int i=1;i<=n;i++) swap(dg[i].c,dg[i].d); else
    if(ho==2) for(int i=1;i<=n;i++) swap(dg[i].b,dg[i].d); else
    if(ho==3) for(int i=1;i<=n;i++) swap(dg[i].a,dg[i].d);
    sort(dg+1,dg+1+n,cmp);
    build(1,1,n);
}

void updata(int ro,int L,int R,int x,int val)
{
    if(L>x || R<x) return;
    if(L==x && x==R)
    {
        tree[ro]=val; return;
    }
    int Mid=(L+R)>>1;
    updata(ro<<1,L,Mid,x,val); updata(ro<<1|1,Mid+1,R,x,val);
    tree[ro]=imin(tree[ro<<1],tree[ro<<1|1]);
}

int query(int ro,int L,int R,int li,int ri)
{
    if(L>ri || R<li) return oo;
    if(li<=L && R<=ri) return tree[ro];
    int Mid=(L+R)>>1;
    int x1=query(ro<<1,L,Mid,li,ri),x2=query(ro<<1|1,Mid+1,R,li,ri);
    return (imin(x1,x2));
}

int main()
{
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d%d%d",&d[i].a,&d[i].b,&d[i].c,&d[i].d),d[i].id=i;
    for(int ho=0;ho<4;ho++)
    {
        pre(ho);
        for(int i=1;i<=n;i++)
        {
            int temp=query(1,1,n,1,dg[i].b);
            if(temp<dg[i].c) ffg[dg[i].id]=1;
            updata(1,1,n,dg[i].b,dg[i].c);
        }
    }
    int dell=0;
    for(int i=1;i<=n;i++) dell+=ffg[i];
    printf("%d\n",dell);
    for(int i=1;i<=n;i++)
    if(ffg[i]) printf("%d\n",i);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值