NOIP2017题解

前言

趁着现在还没有出成绩,我还可以在机房浪一波……
以下6题的代码都只是通过了洛谷的民间数据,如果官方数据出了,没有AC也是很正常的


Day1T1:小凯的疑惑

题目分析:考试的时候花了10min推出来,后来得知出题人也是这么推的。

我们考虑答案模a等于几,很明显不可能等于0。那么假设答案模a等于1,必然存在一个最小的正整数y使得yb%a=1,很明显y与a互质。于是所有%a=1的数中,最大的不能被表示的数就是yb-a。而所有%a=2的数中,最大的不能被表示数是(2y%a)b-a;所有%a=3的数中,最大的不能被表示数是(3y%a)b-a……以此类推。由小学奥数可知(其实是某XJB定理),当正整数y小于a且与a互质时,y,2y%a,3y%a……(a-1)y%a的最大值为a-1。于是答案为(a-1)b-a。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

typedef long long LL;

int a,b;
LL ans;

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

    scanf("%d%d",&a,&b);
    ans=(long long)a*(long long)b-(long long)a-(long long)b;
    printf("%lld\n",ans);

    return 0;
}

Day1T2:时间复杂度

题目分析:硬模拟即可,感觉就是pjT2的难度。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=110;

struct data
{
    int id,lo,hi,ed;
} work[maxn];

bool vis[30];
int sak[maxn];
int tail=0;

char s[maxn];
int Max,now;
int t,l,w;

int Read(int y)
{
    int x=0;
    while ( '0'<=s[y] && s[y]<='9' ) x=x*10+(s[y]-'0'),y++;
    return x;
}

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

    scanf("%d",&t);
    while (t--)
    {
        scanf("%d",&l);
        getchar();
        scanf("%s",&s);
        if (s[2]=='1') w=0;
        else w=Read(4);

        for (int i=1; i<=l; i++)
        {
            scanf("%s",&s);
            while ( s[0]!='E' && s[0]!='F' ) scanf("%s",&s);
            if (s[0]=='E') work[i].id=-1;
            else
            {
                scanf("%s",&s);
                work[i].id=s[0]-'a';
                scanf("%s",&s);
                if (s[0]=='n') work[i].lo=102;
                else work[i].lo=Read(0);
                scanf("%s",&s);
                if (s[0]=='n') work[i].hi=102;
                else work[i].hi=Read(0);
            }
        }

        //printf("%d\n",w);
        //for (int i=1; i<=l; i++) printf("%d %d %d\n",work[i].id,work[i].lo,work[i].hi);
        //printf("\n");

        memset(vis,false,sizeof(vis));
        bool sol=true;
        tail=0;
        for (int i=1; i<=l; i++)
        {
            if (work[i].id==-1)
                if (!tail)
                {
                    sol=false;
                    break;
                }
                else
                {
                    int x=sak[tail];
                    tail--;
                    vis[ work[x].id ]=false;
                    work[x].ed=i;
                }
            else
            {
                tail++;
                sak[tail]=i;
                if (!vis[ work[i].id ]) vis[ work[i].id ]=true;
                else
                {
                    sol=false;
                    break;
                }
            }
        }
        if (tail) sol=false;

        if (!sol)
        {
            printf("ERR\n");
            continue;
        }

        //printf("\n");

        now=0,Max=0;
        int Time=0;
        while (Time<l)
        {
            Time++;
            if (work[Time].id==-1)
            {
                int x=sak[tail];
                tail--;
                if ( work[x].lo<work[x].hi && work[x].hi==102 ) now--;
            }
            else
                if (work[Time].lo>work[Time].hi) Time=work[Time].ed;
                else
                {
                    sak[++tail]=Time;
                    if ( work[Time].lo<work[Time].hi && work[Time].hi==102 )
                        now++,Max=max(Max,now);
                }
        }

        if (Max==w) printf("Yes\n");
        else printf("No\n");
    }

    return 0;
}

Day1T3:逛公园

题目分析:我们可以先算出从1到每个点的最短路f,以及每个点到n的最短路g。然后记cnt[i][j]表示到了点i,已走长度为f[i]+j的方案数,很明显j不应超过题目给定的K。由于f[u]+cost(u,v)>=f[v],所以cnt数组是可以宽搜算出来的。0环的话一开始Dfs一遍就可以找出来了。有一个很坑的地方是:如果出现了0环,还要判断0环是否能真的走到n号点,所以Dfs的时候,遇到一个不能走到n的状态要退出。时间复杂度 O((+mK)T)

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int maxm=200100;
const int maxk=55;
const int oo=1000000000;

struct edge
{
    int obj,len;
    edge *Next;
} e[maxm<<1];
edge *head1[maxn];
edge *head2[maxn];
int cur=-1;

int Heap[maxn];
int id[maxn];
int tail;

int f[maxn];
int g[maxn];

int cnt[maxn][maxk];
int vis[maxn][maxk];
int num[maxn][maxk];

int qx[maxn*maxk];
int qy[maxn*maxk];
int he,ta;

int t,n,m,k,p;

void Add(edge **head,int x,int y,int z)
{
    cur++;
    e[cur].obj=y;
    e[cur].len=z;
    e[cur].Next=head[x];
    head[x]=e+cur;
}

int Delete(int *dis)
{
    int temp=Heap[1];
    Heap[1]=Heap[tail];
    id[ Heap[1] ]=1;
    tail--;

    int x=1;
    while (1)
    {
        int y=x,Left=x<<1,Right=Left|1;
        if ( Left<=tail && dis[ Heap[Left] ]<dis[ Heap[y] ] ) y=Left;
        if ( Right<=tail && dis[ Heap[Right] ]<dis[ Heap[y] ] ) y=Right;
        if (y==x) break;
        swap(Heap[x],Heap[y]);
        swap(id[ Heap[x] ],id[ Heap[y] ]);
        x=y;
    }

    return temp;
}

void Update(int *dis,int x)
{
    while (x>1)
    {
        int y=x>>1;
        if (dis[ Heap[y] ]<dis[ Heap[x] ]) break;
        swap(Heap[x],Heap[y]);
        swap(id[ Heap[x] ],id[ Heap[y] ]);
        x=y;
    }
}

void Dijkstra(int s,edge **head,int *dis)
{
    for (int i=1; i<=n; i++) dis[i]=oo;
    dis[s]=0;

    tail=1;
    Heap[1]=s;
    id[s]=1;
    for (int i=1; i<=n; i++) if (i!=s)
        Heap[++tail]=i,id[i]=tail;

    for (int i=1; i<n; i++)
    {
        int node=Delete(dis);
        for (edge *p=head[node]; p; p=p->Next)
        {
            int to=p->obj;
            if (dis[to]>dis[node]+p->len)
                dis[to]=dis[node]+p->len,Update(dis,id[to]);
        }
    }
}

bool Find(int node,int i)
{
    if (f[node]+i+g[node]>f[n]+k) return true;
    if (vis[node][i]==2) return true;
    if (vis[node][i]==1) return false;

    vis[node][i]=1;
    for (edge *p=head1[node]; p; p=p->Next)
    {
        int to=p->obj;
        int j=f[node]+i+p->len-f[to];
        if (j>k) continue;
        if ( !Find(to,j) ) return false;
    }
    vis[node][i]=2;
    return true;
}

int Mod(int x)
{
    if (x>=p) x-=p;
    return x;
}

void Bfs()
{
    for (int i=1; i<=n; i++)
        for (int j=0; j<=k; j++) cnt[i][j]=0;
    cnt[1][0]=1%p;

    for (int i=1; i<=n; i++)
        for (int j=0; j<=k; j++)
            if (vis[i][j])
                for (edge *p=head1[i]; p; p=p->Next)
                {
                    int to=p->obj;
                    int x=f[i]+j+p->len-f[to];
                    if ( x<=k && vis[to][x] ) num[to][x]++;
                }

    he=0,ta=1;
    qx[1]=1;
    qy[1]=0;
    while (he<ta)
    {
        he++;
        int node=qx[he];
        int i=qy[he];

        for (edge *p=head1[node]; p; p=p->Next)
        {
            int to=p->obj;
            int j=f[node]+i+p->len-f[to];
            if ( j>k || !vis[to][j] ) continue;

            cnt[to][j]=Mod(cnt[to][j]+cnt[node][i]);
            num[to][j]--;
            if (!num[to][j])
            {
                ta++;
                qx[ta]=to;
                qy[ta]=j;
            }
        }
    }
}

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

    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d%d%d",&n,&m,&k,&p);
        cur=-1;
        for (int i=1; i<=n; i++) head1[i]=head2[i]=NULL;
        for (int i=1; i<=m; i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            Add(head1,a,b,c);
            Add(head2,b,a,c);
        }

        Dijkstra(1,head1,f);
        Dijkstra(n,head2,g);

        for (int i=1; i<=n; i++)
            for (int j=0; j<=k; j++)
                vis[i][j]=num[i][j];
        bool sol=Find(1,0);

        if (!sol) printf("-1\n");
        else
        {
            Bfs();
            int ans=0;
            for (int i=0; i<=k; i++) ans=Mod(ans+cnt[n][i]);
            printf("%d\n",ans);
        }
    }

    return 0;
}

Day2T1:奶酪

题目分析: n2 连边,然后宽搜即可。按理来说两个圆心的 (δx)2+(δy)2+(δz)2 会爆long long,要用unsigned long long,但听说出题人没有卡。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1010;
typedef long long LL;

struct edge
{
    int obj;
    edge *Next;
} e[maxn*maxn];
edge *head[maxn];
int cur;

int level[maxn];
int que[maxn];
int he,ta;

int X[maxn];
int Y[maxn];
int Z[maxn];

int t,n,h,r;
LL temp;

bool Judge(LL x,LL y,LL z)
{
    x*=x;
    y*=y;
    z*=z;
    LL dis=x+y+z;
    return (dis<=temp);
}

void Add(int x,int y)
{
    cur++;
    e[cur].obj=y;
    e[cur].Next=head[x];
    head[x]=e+cur;
}

bool Bfs()
{
    level[0]=1;
    for (int i=1; i<=n+1; i++) level[i]=0;
    he=0,ta=1;
    que[1]=0;
    while (he<ta)
    {
        int node=que[++he];
        for (edge *p=head[node]; p; p=p->Next)
        {
            int to=p->obj;
            if (!level[to]) level[to]=level[node]+1,que[++ta]=to;
        }
    }
    return level[n+1];
}

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

    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d%d",&n,&h,&r);
        for (int i=1; i<=n; i++) scanf("%d%d%d",&X[i],&Y[i],&Z[i]);

        cur=-1;
        temp=4LL*(long long)r*(long long)r;
        for (int i=0; i<=n+1; i++) head[i]=NULL;
        for (register int i=1; i<n; ++i)
            for (register int j=i+1; j<=n; ++j)
                if ( Judge(X[i]-X[j],Y[i]-Y[j],Z[i]-Z[j]) )
                    Add(i,j),Add(j,i);

        for (int i=1; i<=n; i++) if (Z[i]-r<=0) Add(0,i);
        for (int i=1; i<=n; i++) if (Z[i]+r>=h) Add(i,n+1);
        bool sol=Bfs();
        if (sol) printf("Yes\n");
        else printf("No\n");
    }

    return 0;
}

Day2T2:宝藏

题目分析:主要思路是将生成树分层状压,然后子集转移。先预处理一个点到一个集合的最小费用,然后算出一个集合到一个集合的最小费用g[s][t]。记f[i][s]表示到第i层为止,已经选了s集合的最小花费,很明显f[i][s]=f[i-1][t]+g[s][t]*(i-1)。虽然实际上每一条边的花费并不一定都要乘上i-1,但可以发现最优答案一定会被计算到。时间复杂度 O(3nn)

注意将某些数组初始化为无限大时,乘起来可能会爆int。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=15;
const int maxs=(1<<12)+100;
const int oo=1000000000;

int e[maxn][maxn];

int h[maxn][maxs];
int g[maxs][maxs];
int f[maxn][maxs];

int n,m;

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

    scanf("%d%d",&n,&m);
    for (int i=1; i<=n; i++)
        for (int j=1; j<=n; j++) e[i][j]=oo;
    for (int i=1; i<=m; i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        e[a][b]=min(e[a][b],c);
        e[b][a]=min(e[b][a],c);
    }

    int ms=1<<n;
    for (int i=1; i<=n; i++)
        for (int s=1; s<ms; s++)
        {
            h[i][s]=oo;
            if ( !(s&(1<<(i-1))) )
                for (int j=1; j<=n; j++)
                    if ( s&(1<<(j-1)) )
                        h[i][s]=min(h[i][s],e[i][j]);
        }

    for (int s=1; s<ms; s++)
    {
        int t=s&(s-1);
        while (t)
        {
            int x=s^t;
            for (int i=1; i<=n; i++)
                if ( x&(1<<(i-1)) )
                {
                    g[s][t]+=h[i][t];
                    if (g[s][t]>oo) g[s][t]=oo;
                }
            t=s&(t-1);
        }
    }

    for (int i=1; i<=n; i++)
        for (int s=0; s<ms; s++) f[i][s]=oo;
    for (int i=1; i<=n; i++) f[1][1<<(i-1)]=0;
    for (int i=2; i<=n; i++)
        for (int s=1; s<ms; s++)
        {
            int t=s&(s-1);
            while (t)
            {
                int temp;
                if (g[s][t]<oo) temp=g[s][t]*(i-1);
                else temp=oo;
                if (f[i-1][t]<oo) f[i][s]=min(f[i][s],f[i-1][t]+temp);
                t=s&(t-1);
            }
        }

    int ans=oo;
    for (int i=1; i<=n; i++) ans=min(ans,f[i][ms-1]);
    printf("%d\n",ans);

    return 0;
}

Day2T3:列队

题目分析:后来分析了一下,发现用线段树+Treap就可以做了。

在某一次操作后,矩阵一定是这个样子的:

其中红色部分表示从第m列来到左边某一行的数,蓝色代表某一行原有的数,绿色代表第m列。用一棵Treap维护绿色部分,用n棵Treap维护红色部分,再用n棵动态开节点的线duang树维护每一行蓝色部分里没有的数即可。找答案的时候根据实际情况在Treap上找数或者在线段树上二叉查找。时空复杂度均为 O(nlog(n))

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=300100;
const int Lg=22;

const long long M1=998244353;
const long long M2=1000000007;
const long long M3=1998585857;
typedef long long LL;

struct Seg
{
    int sum;
    Seg *Lson,*Rson;
} tree[maxn*Lg];
Seg *SRoot[maxn];
int cur=-1;

struct Tnode
{
    LL val;
    int Size,fix;
    Tnode *lson,*rson;
    int Lsize() { return (lson? lson->Size:0); }
} Treap[maxn*3];
Tnode *TRoot[maxn];
int Tcur=-1;
LL seed;

int num[maxn];
int n,m,q;

Seg *New_Seg()
{
    cur++;
    tree[cur].sum=0;
    tree[cur].Lson=tree[cur].Rson=tree;
    return tree+cur;
}

int Rand()
{
    seed=(seed*M1+M2)%M3;
    return seed;
}

Tnode *New_Tnode(LL v)
{
    Tcur++;
    Treap[Tcur].val=v;
    Treap[Tcur].Size=1;
    Treap[Tcur].fix=Rand();
    Treap[Tcur].lson=Treap[Tcur].rson=NULL;
    return Treap+Tcur;
}

void Recount(Tnode *P)
{
    P->Size=P->Lsize()+(P->rson? P->rson->Size:0)+1;
}

void Left_turn(Tnode *&P)
{
    Tnode *W=P->rson;
    P->rson=W->lson;
    W->lson=P;
    P=W;
    Recount(P->lson);
    Recount(P);
}

void Insert(Tnode *&P,LL v)
{
    if (!P) P=New_Tnode(v);
    else
    {
        Insert(P->rson,v);
        if ( P->rson->fix < P->fix ) Left_turn(P);
        else Recount(P);
    }
}

void Right_turn(Tnode *&P)
{
    Tnode *W=P->lson;
    P->lson=W->rson;
    W->rson=P;
    P=W;
    Recount(P->rson);
    Recount(P);
}

LL Delete(Tnode *&P,int Rank)
{
    int Ls=P->Lsize()+1;
    LL temp;     //!!!
    if (Ls==Rank)
        if (P->lson)
            if (P->rson)
                if ( P->lson->fix < P->rson->fix )
                {
                    Right_turn(P);
                    temp=Delete(P->rson, P->rson->Lsize()+1 );
                    Recount(P);
                }
                else
                {
                    Left_turn(P);
                    temp=Delete(P->lson, P->lson->Lsize()+1 );
                    Recount(P);
                }
            else temp=P->val,P=P->lson;
        else temp=P->val,P=P->rson;
    else
    {
        if (Rank<Ls) temp=Delete(P->lson,Rank);
        else temp=Delete(P->rson,Rank-Ls);
        Recount(P);
    }
    return temp;
}

int Query(Seg *root,int L,int R,int Rank)
{
    if (L==R) return L;
    int mid=(L+R)>>1,Ls=root->Lson->sum;
    if (Rank<=mid-Ls) return Query(root->Lson,L,mid,Rank);
    else return Query(root->Rson,mid+1,R,Rank+Ls);//没考虑清楚,+号打成了-号!!!
}

void Update(Seg *&root,int L,int R,int x)
{
    if (root==tree) root=New_Seg();
    if (L==R) root->sum++;
    else
    {
        int mid=(L+R)>>1;
        if (x<=mid) Update(root->Lson,L,mid,x);
        else Update(root->Rson,mid+1,R,x);
        root->sum=root->Lson->sum+root->Rson->sum;
    }
}

/*void Print(Tnode *P)
{
    if (!P) return;
    Print(P->lson);
    printf("%d ",P->val);
    Print(P->rson);
}*/

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

    scanf("%d%d%d",&n,&m,&q);

    seed=q;
    for (int i=0; i<=n+1; i++) TRoot[i]=NULL;
    New_Seg();
    for (int i=1; i<=n; i++) SRoot[i]=tree,num[i]=0;

    for (int i=1; i<=n; i++) Insert(TRoot[n+1],(long long)i*(long long)m);
    m--;
    for (int i=1; i<=q; i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if (y>m)
        {
            LL temp=Delete(TRoot[n+1],x);
            printf("%lld\n",temp);     //!!!
            Insert(TRoot[n+1],temp);
        }
        else
        {
            LL ans;
            if (y>m-num[x]) ans=Delete(TRoot[x],y+num[x]-m);
            else
            {
                ans=Query(SRoot[x],1,m,y);
                Update(SRoot[x],1,m,ans);
                ans+=( (long long)(x-1)*(long long)(m+1) );
                num[x]++;
            }
            Insert(TRoot[x], Delete(TRoot[n+1],x) );
            Insert(TRoot[n+1],ans);
            printf("%lld\n",ans);
        }
        //if (i==4) Print(TRoot[n+1]),printf("\n");
    }

    return 0;
}

//记得开long long!!!

后记:

屏幕在深夜微微发亮
思想在那虚树路径上彷徨
平面的向量交错生长
织成 忧伤的网

剪枝剪去我们的疯狂
SPFA 告诉我前途在何方
01 背包装下了忧伤
笑颜 洋溢脸庞

键盘微凉 鼠标微凉
指尖流淌 代码千行
凸包周长 直径多长
一进考场 全都忘光
你在 OJ 上提交了千百遍
却依然不能卡进那时限
双手敲尽代码也敲尽岁月
只有我一人
写的题解
凋零在 OJ 里面

Tarjan 陪伴强连通分量
生成树完成后思路才闪光
欧拉跑过的七桥古塘
让你 心驰神往

队列进出图上的方向
线段树区间修改求出总量
可持久化留下的迹象
我们 伏身欣赏

数论算法 图论算法
高斯费马 树上开花
线性规划 动态规划
时间爆炸 如何优化
我在 OI 中辗转了千百天
却不让我看 AK 最后一眼
我用空间换回超限的时间
随重新编译
测完样例
才发现漏洞满篇

原来 CE 是因选错语言
其实爆零 只因忘写文件
如果标算太难请坚定信念
不如回头再看一眼题面
以那暴力模拟向正解吊唁
蒟蒻的蜕变
神犇出现
终将与 Au 擦肩

屏幕在深夜微微发亮
我心在考场

——膜你抄 WC2017

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值