NOIP 2017.10.23 总结+心得

NOIP 2017.10.23 总结+心得_第1张图片
世界真的很大
完了再这么考下去NOIP怕是要跪
讲道理今天三道水题考了100分也是可以的
第一题卡常卡了20分,第二题玄学错误只剩20分,本来想着前两道题稳了就没看第三题,补一会儿觉,结果第三题也是一道水题
哎哎哎
AK这种flag果然不能乱立233
明天要好好调整状态了
讲道理虽然题很水但是不可否认命题的质量还是很高,没有白做
但感觉我可能考试时候真的白做了233


看题先:

1.

NOIP 2017.10.23 总结+心得_第2张图片
这道题也是一个神奇
考试的时候一开始真没有什么思路
想了一会儿,突然发现这个兔子图和斐波拉契好像没有关系?
但是题目就是斐波拉契啊?
于是乎往斐波拉契上想,发现每一只兔子的父亲就是其编号减去离他最近的一个斐波拉契数
这样就是一棵树了,10^12建树实在是不大可能,但是不建树的话倍增LCA就处理不出来
莫不是只能一个一个往上跳?
打了个表发现斐波拉契最多只到59,就是说最多跳59层,嗯~~
那不是可做吗?
跳的时候用一个map来记录中途访问了哪些点
复杂度 O(m* 59 * log)有点卡但是好像可做,听说测评开O2?
然后成功被卡常20分

正解大概思路就是这样,只不过省下来了map的复杂度
就像普通lca那样交换着往上跳,用数的大小来表示层数

完整代码:

#include
#include
#include
#include
using namespace std;
typedef long long dnt;

map int> mp;

int m,n;
dnt f[100];

void init()
{
    f[0]=1,f[1]=1,f[2]=2;
    n=2;
    while(n<=62)
    {
        n++;
        f[n]=f[n-1]+f[n-2];
    }
}

int main()
{
    freopen("fibonacci.in","r",stdin);
    freopen("fibonacci.out","w",stdout);
    scanf("%d",&m);
    init();
    for(int i=1;i<=m;i++)
    {
        dnt u,v;
        int flag=1;
        scanf("%I64d%I64d",&u,&v);
        while(u!=v)
        {
            if(uint k=lower_bound(f+1,f+n+1,u)-(f+1);
            u-=f[k];
        }
        printf("%I64d\n",u);
    }
    return 0;
}
/*
5
2 3
*/

讲道理map的常数怕是有点太大了哦
果然以后还是不要寄希望于测评机,老老实实分析复杂度,如果有点卡就要思索其他办法或者优化


2。

NOIP 2017.10.23 总结+心得_第3张图片
这道题考试的时候真的是秒想啊
看到区间查询值域什么的想到主席树但是要修改?
差点就写树套树了233
发现修改其实只是交换相邻两项而已?
主席树每棵树记录的其实是前缀和,交换a,a+1两个值只会对第a颗树的前缀造成影响
一开始想的就是直接修改,但是对拍出错了
发现虽然看似只改了第a颗树,对a+1及之后的树没有影响,其实主席树这个东西在建树的时候为了省空间就是“依靠”在上一棵树上建的
换句话说两棵树共用了很大一部分公共节点
纵然只修改了上一颗树,但是由于两棵树的节点共用,对于后面的查询也会造成影响
为了避免这个问题,只得每次修改重新建一棵树,这样空间复杂度就是O(mlogn)的,也还在可接受范围内
这便是一种正解了
然而20分。。。
我的主席树的值域是按照ai的最值来开的,如果ai最大是20,那我买的值域就只有20,然而
他问我L,R有没有200!200!200! 提莫的!
WA掉,改了立马AC
我* * * * *(语无伦次)

我写的只是正解的一种
由于每次交换不会改变同种颜色的相对位置,所以我们就只需要用一个vector来保存每种颜色的依次出现位置,每次改就二分改一下位置就好,每次查的时候就lowerbound L,R 就好

完整代码:

#include
#include
using namespace std;

struct node
{
    int sum;
    node *ls,*rs;
}pool[16000010],*tail=pool,*root[400010],*null;

int n,m,maxn=0;
int a[400010];

node *newnode()
{
    node* nd=++tail;
    nd->sum=0;
    nd->ls=nd->rs=null;
    return nd;
}

void insert(node *ne,node * &nd,int lf,int rg,int pos)
{
    nd=newnode();
    nd->sum=ne->sum+1;
    nd->ls=ne->ls,nd->rs=ne->rs;
    if(lf==rg) return ;
    int mid=(lf+rg)>>1;
    if(pos<=mid) insert(ne->ls,nd->ls,lf,mid,pos);
    else insert(ne->rs,nd->rs,mid+1,rg,pos);
}

int query(node *ne,node *nd,int lf,int rg,int pos)
{
    if(lf==rg) return nd->sum-ne->sum;
    int mid=(lf+rg)>>1;
    if(pos<=mid) return query(ne->ls,nd->ls,lf,mid,pos);
    else return query(ne->rs,nd->rs,mid+1,rg,pos);
}

int main()
{
    freopen("color.in","r",stdin);
    freopen("color.out","w",stdout);
    null=++tail;
    null->ls=null->rs=null;
    null->sum=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    maxn=400000;
    root[0]=newnode();
    for(int i=1;i<=n;i++) insert(root[i-1],root[i],1,maxn,a[i]);
    while(m--)
    {
        int opt,L,R,C;
        scanf("%d",&opt);
        if(opt==1)
        {
            scanf("%d%d%d",&L,&R,&C);
            printf("%d\n",query(root[L-1],root[R],1,maxn,C));
        }
        else
        {
            scanf("%d",&L);
            insert(root[L-1],root[L],1,maxn,a[L+1]);
            swap(a[L],a[L+1]);
        }
    }
    return 0;
}

讲道理要是我这道题不去脑残的想什么办法取个ai的最值优化空间我直接就A了,对拍的时候也没有考虑这点,出的全是合法数据orz
以后数据构造什么的我真的不能想当然地出了,题目没明说的情况一定不能当成已知条件。
一句话:尽可能的怀疑题目


3。

NOIP 2017.10.23 总结+心得_第4张图片
这道题总结什么的没什么好说的,考试的时候直接放过,原因是觉得前两道题我觉得稳了,毕竟拍了30分钟没出问题orz,因为我出的全是合法数据,再加上困~~~
考完一听题解(。。。)就是水题
然后下午听完课晚上果不其然20分钟调出来。。
考试的时候我在干嘛?

这道题的正解是什么,不好意思不知道
我的做法如下:
分类讨论,
对于K == 1的情况,选字典序最小的方案其实就是在保证组数尽量少的前提下,使得分的区间尽量靠前,这个看上去的确很难把握
那就转化成从后往前选使得区间尽量靠后,即能不分就不分,非要分才分,贪心走一遍即可

对于K == 2的情况,大概思路和上面一样,但是允许同一组中可以分成两个子集使得各个子集中没有互相矛盾的元素。
即能分成一堆的判定条件是可以把一堆数分成两个子集使得子集之内没有矛盾,如果出现不能满足的情况,就必须分组
想到什么?
并查集啊,所谓“敌人的敌人就是朋友”
详见:
BZOJ 1370
NOIP 2010 关押罪犯
用并查集来判断是不是必须分组了

有一点细节问题

完整代码:

#include
#include
using namespace std;

const int N=131073;

vector <int> va;

int n,K,a[1000010],vis[1000010],fa[1000010];

void sov1()
{
    int tot=1;
    for(int i=n;i>=1;i--)
    {
        int flag=0;
        for(int j=1;j*j<=2*N;j++)
            if(j*j>a[i] && vis[j*j-a[i]]==tot) flag=1;
        if(flag) tot++,va.push_back(i);
        vis[a[i]]=tot;
    }
    printf("%d\n",va.size()+1);
    for(int i=va.size()-1;i>=0;i--) printf("%d ",va[i]);
}

int getfather(int x)
{
    if(x==fa[x]) return x;
    return fa[x]=getfather(fa[x]);
}

bool check(int u,int v)
{
    int x=getfather(u),y=getfather(v);
    if(x!=y)
    {
        fa[x]=getfather(v+n);
        fa[y]=getfather(u+n);
        return false ;
    }
    return true ;
}

void sov2()
{
    int st=n,tot=0;
    for(int i=1;i<=2*n;i++) fa[i]=i;
    for(int i=1;i*i<=4*N;i++) vis[i*i]=1;
    for(int i=n;i>=1;i--)
    {
        int flag=0;
        for(int j=st;j>i;j--)
            if(vis[a[i]+a[j]] && check(i,j)) flag=1;
        if(flag) fa[i]=i,va.push_back(i),st=i;
    }
    printf("%d\n",va.size()+1);
    for(int i=va.size()-1;i>=0;i--) printf("%d ",va[i]);
}

int main()
{
    freopen("division.in","r",stdin);
    freopen("division.out","w",stdout);
    scanf("%d%d",&n,&K);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    if(K==1) sov1();
    else sov2();
    return 0;
}
/*
2 2
131072 131072 

5 2
1 3 15 10 6
*/

并查集是一个很厉害的东西,其拓展的性质非常之丰富啊,好好掌握
对于一道题,当正向很难入手的时候,尝试从反面入手,解决反问题或者由反面最劣推导正面最优


这次考得真的是。。。太难过了
希望明天有所改善吧
不然可能NOIP完了真的要退役了233

嗯,就是这样

你可能感兴趣的:(NOIP)