NOIP2017模拟赛8

前言

为什么总结只有1和8,没有2、3、4、5、6、7呢?因为只有这两次是AK才有时间写啊!

a路径

题目很长,简单一点来说就是一个二维平面坐标系中有N个整数点,主人公Bessie,每一步可以走上下左右四个点,要求你从(0,0)出发,走过所有给定的点,且结束点是任意一个给定的点,问是否有一个方案所有步数之和的奇偶性为P。

分析

这题作为一道签到题,想一下就会发现这道题唯一的条件“步数之和”只与结束点有关,与其他都是没有关系的,所以对于每一个点,将它的坐标x和y加起来判断奇偶即可。

程序

就不贴了吧。

b冠军

有N个拳手参加比赛,

分析

这题什么乱七八糟的方法(其实大部分都是在N!的方法上改进的)我都想过,最后发现除了状压加搜索优化以外,没有什么更好的方法。
f[i][j][P]表示到了第i轮比赛,胜者是第j个拳手,P表示哪几位拳手参加了比赛。
搜索dfs(k,s,w,p)表示搜索到前k个拳手已经搜索完,一共有s个拳手被选中,其中胜利的是第w个拳手,这s个拳手是这p个。
其中第w个拳手一定要输给第j个拳手,且第j个拳手不能在这s个拳手中。
为了节省时间,一开始我还把有效的P找出来,但现在分析一想,并没有优化什么太多时间复杂度。
这题如果时间开1s确实很卡。。

#include <iostream> 
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <cstring>
#include <vector>
#include <time.h>

using namespace std;
long long f[6][20][70000];
int N,w,P,n,m,sl[20],b[20][70000],a[20][20],tep[20];
char st[20];

void dfs(int k,int s,int p,int lw) {
    if ((s<<1)==(1<<N)) {
        if (lw==-1) return;
        f[N][w][P]+=f[N-1][lw][p]*f[N-1][w][P-p]*2;
        return ;
    }
    for (int i=k;i<=(1<<N);i++)
    {
        if (lw==-1&&a[w][tep[i]]) dfs(i+1,s+1,p|(1<<tep[i]),tep[i]);
        if (tep[i]!=w) dfs(i+1,s+1,p|(1<<tep[i]),lw);
    }
}

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

    scanf("%d",&n);
    for (int i=0;i<n;i++){
        scanf("%s",&st);
        for (int j=0;j<n;j++)
        if (st[j]=='N') a[i][j]=0;else a[i][j]=1;
    }

    int l=1<<n;
    for (register int i=0;i<l;i++) {
        int i1=i,s=0;
        while (i1>0) {++s;i1-=i1&-i1;}
        if (s==2) s=1; else
        if (s==4) s=2; else
        if (s==8) s=3; else
        if (s==16) s=4; else
        s=0;
        if (s!=0){
            sl[s]++;b[s][sl[s]]=i;
        }
    }

    for (int i=0;i<n;i++)
    for (int j=0;j<n;j++)
    if (a[i][j]) f[1][i][(1<<i)+(1<<j)]=2;

    if (n==2) m=1;if (n==4) m=2;if (n==8) m=3;if (n==16) m=4;
    for (int i=2;i<=m;i++)
    for (register int j=1;j<=sl[i];j++) {
        int cnt=0;
        for (int k=0;k<n;k++)
        if (b[i][j]&(1<<k)) tep[++cnt]=k;
        P=b[i][j];N=i;
        for (int k=1;k<=cnt;k++){
            w=tep[k];
            dfs(1,0,0,-1);
        }
    }

    for (int i=0;i<n;i++)
    printf("%lld\n",f[m][i][(1<<n)-1]);

    return 0;
}

中间的if (n==2) m=1;if (n==4) m=2;if (n==8) m=3;if (n==16) m=4;为什么用log(n)/log(2)算不出来,求大神解答。

小结一波

这道题的确卡了我非常多时间,首先一开始我认为状态转移需要很多时间,粗略一点就是 216 ,就算是运用组合数减少转移状态的时间也不会好到哪里去。我没有算C,导致我以为 C816 很大,不比 216 少多少,算出来也证明只比 216 少6倍,但在这种卡常数的题中,少6倍就足够了。

C指纹

给你n组四元组{a,b,c,d},其中如果每一个四元组A中的元素,有至少3个比另一个四元组B中的元素要小,则认为四元组B是“累赘”的。问有哪几组四元组是累赘的。

分析

由于第二题花了比较多的时间,所以这一题我想得并不是很深入。
首先n很大,就已经卡掉了很多譬如网络流之类的乱七八糟的算法。
对于本题而言, nn nlog2n 的时间复杂度都是可以接受的,很容易想去数据结构和分块方面。其中分块我没想过,也不好想,所以我直接想数据结构方面了。(毕竟现在的oier数据结构都学得很好,当然蒟蒻我只会线段树)
一共有四维数据。首先肯定要先对第一维排序,这样子就可以减少一维数据的干扰。
然后问题来了,对于每一组四元组,你可以有两种方法进行处理,一种是判断这个四元组是不是“累赘”,另一种是用这个四元组来判断前面的四元组是不是“累赘”。由于第二种方法具有不确定性,相对来说第一种方法看上去简单很多。(对于暴力来说,两种方法并没有什么区别)
然后想如何不用排序的方式来判第二维的大小,然后就想到以数组下标为基础的桶排,然后用数据结构询问比第二维小的有没有就可以了。
然后考虑第三维,看着这个数据结构,我很惊奇地发现,这颗线段树里空空如也,我们就可以把第三维记进去。
既然这样,那也顺便把第四维也记进线段树里面。
总的来说,就是以第一维排序,那么先加入数据结构里面的a一定比当前a要小。然后在在线段树中1~b中找最小的c和d,最后判断即可。
这种方法还有一个缺陷,就是某一个四元组是“累赘”的,但a比较小,你没有把它视为“累赘”,所以这样做完一次以后,将c、d和a、b交换再做同样的操作即可。

程序

#include <iostream> 
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <cstring>
#include <vector>
#include <time.h>

using namespace std;

struct node{
    int a,b,c,d,e;
}a[100010];
int T[2][400010],n;
bool ans[100010];

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

void update(int p,int ro,int L,int R,int x,int v) {
    if (L==R&&R==x) {T[p][ro]=v; return;}
    if (L>x||R<x) return;
    int mid=(L+R)>>1,zuo=ro<<1,you=zuo+1;
    update(p,zuo,L,mid,x,v);
    update(p,you,mid+1,R,x,v);
    T[p][ro]=min(T[p][zuo],T[p][you]);
}

int query(int p,int ro,int L,int R,int le,int ri) {
    if (le<=L&&R<=ri) return T[p][ro];
    if (R<le||L>ri) return n+1;
    int mid=(L+R)>>1,zuo=ro<<1,you=zuo+1;
    int A=query(p,zuo,L,mid,le,ri),
    B=query(p,you,mid+1,R,le,ri);
    return min(A,B);
}

void work(){
    sort(a+1,a+1+n,cmp);
    for (int i=0;i<=4*n;i++) T[0][i]=T[1][i]=n+1;
    update(0,1,1,n,a[1].b,a[1].c);
    update(1,1,1,n,a[1].b,a[1].d);
    for (int i=2;i<=n;i++) {
        int A=query(0,1,1,n,1,a[i].b),
        B=query(1,1,1,n,1,a[i].b);
        if (A<a[i].c||B<a[i].d) ans[a[i].e]=false;
        update(0,1,1,n,a[i].b,a[i].c);
        update(1,1,1,n,a[i].b,a[i].d);
    }
}

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

    scanf("%d",&n);
    for (int i=1;i<=n;i++) ans[i]=true;
    for (int i=1;i<=n;i++) {
        scanf("%d%d%d%d",&a[i].a,&a[i].b,&a[i].c,&a[i].d);
        a[i].e=i;
    }
    work();
    for (int i=1;i<=n;i++) 
    {
        swap(a[i].a,a[i].c);
        swap(a[i].b,a[i].d);
    }
    work();int cnt=0;
    for (int i=1;i<=n;i++)
    if (!ans[i]) cnt++;
    printf("%d\n",cnt);
    for (int i=1;i<=n;i++)
    if (!ans[i]) printf("%d\n",i);

    return 0;
}

小结

这题我联想起gdoi2016的某一天的第二题,做暴力并用抽屉原理来优化,但好像本题不是用这个方法。

——-zero镇楼
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值