1. 程式人生 > >NOIP2013題解

NOIP2013題解

NOIP2013題解

Day1

轉圈遊戲 circle

快速冪模板題。

#include<iostream>
using namespace std;
int n,m,k,x;
int fpow(int a,int b){int s=1;while(b){if(b&1)s=1ll*s*a%n;a=1ll*a*a%n;b>>=1;}return s;}
int main()
{
    cin>>n>>m>>k>>x;
    m=1ll*m*fpow(10,k)%n;
    cout<<(x+m)%n<<endl;
    return 0;
}

火柴排隊 match

比較不錯的題目。

不難發現顯然上下的排名一樣的時候是最優解,對於上方重編號,轉為逆序對問題。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAX 100100
#define MOD 99999997
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
struct Node{int a,b;}p[MAX];
int n,S[MAX],top,a[MAX],ans;
int c[MAX];
int lb(int x){return x&(-x);}
void add(int x,int w){while(x<=n)c[x]+=w,x+=lb(x);}
int getsum(int x){int ret=0;while(x)ret+=c[x],x-=lb(x);return ret;}
int main()
{
    n=read();
    for(int i=1;i<=n;++i)p[i].a=read();
    for(int i=1;i<=n;++i)p[i].b=read();
    top=0;
    for(int i=1;i<=n;++i)S[++top]=p[i].a;
    sort(&S[1],&S[n+1]);top=unique(&S[1],&S[top+1])-S-1;
    for(int i=1;i<=n;++i)p[i].a=lower_bound(&S[1],&S[top+1],p[i].a)-S;
    top=0;
    for(int i=1;i<=n;++i)S[++top]=p[i].b;
    sort(&S[1],&S[n+1]);top=unique(&S[1],&S[top+1])-S-1;
    for(int i=1;i<=n;++i)p[i].b=lower_bound(&S[1],&S[top+1],p[i].b)-S;
    for(int i=1;i<=n;++i)a[p[i].a]=i;
    for(int i=1;i<=n;++i)p[i].b=a[p[i].b];
    for(int i=1;i<=n;++i)a[i]=p[i].b;
    for(int i=n;i;--i)(ans+=getsum(a[i]))%=MOD,add(a[i],1);
    printf("%d\n",ans);
    return 0;
}

貨車運輸 truck

不錯的題目,做過一次你就會做了。

不難發現答案一定在最大生成樹上,所以構建最大生成樹之後,答案就是兩點間的路徑最大值了。

可以倍增,可以樹鏈剖分+線段樹。也可以直接克魯斯卡爾重構樹。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 20200
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,m;
struct Edge{int u,v,w;}E[MAX*5];
bool operator<(Edge a,Edge b){return a.w>b.w;}
int f[MAX],tot,W[MAX];
int getf(int x){return x==f[x]?x:f[x]=getf(f[x]);}
struct Line{int v,next;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int p[16][MAX],dep[MAX];
void dfs(int u,int ff)
{
    p[0][u]=ff;dep[u]=dep[ff]+1;
    for(int i=1;i<16;++i)p[i][u]=p[i-1][p[i-1][u]];
    for(int i=h[u];i;i=e[i].next)
        dfs(e[i].v,u);
}
int LCA(int u,int v)
{
    if(dep[u]<dep[v])swap(u,v);
    for(int i=15;~i;--i)
        if(dep[p[i][u]]>=dep[v])u=p[i][u];
    if(u==v)return u;
    for(int i=15;~i;--i)
        if(p[i][u]^p[i][v])
            u=p[i][u],v=p[i][v];
    return p[0][u];
}
int main()
{
    n=read();m=read();tot=n;
    for(int i=1;i<=m;++i)E[i].u=read(),E[i].v=read(),E[i].w=read();
    sort(&E[1],&E[m+1]);
    for(int i=1;i<n+n;++i)f[i]=i;
    for(int i=1;i<=m;++i)
    {
        int u=getf(E[i].u),v=getf(E[i].v);
        if(u==v)continue;
        f[u]=f[v]=++tot;W[tot]=E[i].w;
        Add(tot,u);Add(tot,v);
    }
    for(int i=tot;i;--i)
        if(!dep[i])dfs(i,0);
    int Q=read();
    while(Q--)
    {
        int u=read(),v=read();
        if(getf(u)!=getf(v))puts("-1");
        else printf("%d\n",W[LCA(u,v)]);
    }
    return 0;
}

Day2

積木大賽 block

略有思維的題目。

我們從第一個開始,顯然無論如何我們都要以第一個為左端點執行第一個的高度次操作。那麼我們不限定右端點,讓這個操作的區間自然向右延伸。而每個積木的高度則限定了可以向右延伸的次數,那麼直接算一遍就好了。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,h[100100];
ll ans=0,nw=0;
int main()
{
    n=read();
    for(int i=1;i<=n;++i)h[i]=read();
    for(int i=1;i<=n;nw=h[i++])
        if(h[i]>=nw)ans+=h[i]-nw;
    printf("%lld\n",ans);
    return 0;
}

花匠 flower

發現要求的就是一個最長波動序列。考慮一種\(dp\)做法,設\(f[i][0/1]\)表示當前第\(i\)個位置,它是峰開始谷,轉移的時候分類討論一下。如果\(h_i>h_{i+1}\),那麼\(f[i][1]=f[i-1][0]+1\),因為可以把當前位置當做峰。然後\(f[i][1]=f[i-1][1]\),因為當前位置代替一個更低的位置當做峰一定更優。反過來的轉移類似。

有了這個\(dp\)不難發現貪心就行了。

#include<iostream>
#include<cstdio>
using namespace std;
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int ans=1,n,h[100100],nw=-1;
int main()
{
    n=read();
    for(int i=1;i<=n;++i)h[i]=read();
    for(int i=2;i<=n;++i)
    {
        if(h[i]>h[i-1]&&nw!=1)++ans,nw=1;
        if(h[i]<h[i-1]&&nw!=0)++ans,nw=0;
    }
    printf("%d\n",ans);
    return 0;
}

華容道 puzzle

可以說是\(NOIP\)中最優秀的題目之一。

然而做過一次就再也忘不了了。所以這回一下就寫完了。

發現如果空格如果在當前的棋子周圍,那麼問題轉化為了空格從當前棋子旁邊的一個方向移動到另外一個方向上的問題。預處理\(dis[x][y][0..4][0..4]\)表示當前位置\((x,y)\),空格在某個方向上,空格要移動到另外一個方向上、在不經過\((x,y)\)位置的情況下的最小步數。只需要對於每個點\(bfs\)一次就好了。

然後對於每次詢問,先讓空格到達起點旁邊,再直接跑一遍最短路就好了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define MAX 35
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int d[4][2]={1,0,-1,0,0,1,0,-1};
bool vis[MAX][MAX];
int dis[MAX][MAX];
int n,m,Q,a[MAX][MAX];
int mov[MAX][MAX][4][4];
void bfs(int x,int y,int X,int Y)
{
    queue<int> Qx,Qy;Qx.push(x),Qy.push(y);
    memset(dis,63,sizeof(dis));dis[x][y]=0;
    memset(vis,0,sizeof(vis));vis[x][y]=vis[X][Y]=true;
    while(!Qx.empty())
    {
        int x=Qx.front(),y=Qy.front();Qx.pop();Qy.pop();
        for(int i=0;i<4;++i)
        {
            int xx=x+d[i][0],yy=y+d[i][1];
            if(xx<1||yy<1||xx>n||yy>m||a[xx][yy]==0)continue;
            if(vis[xx][yy])continue;vis[xx][yy]=true;
            Qx.push(xx);Qy.push(yy);
            dis[xx][yy]=dis[x][y]+1;
        }
    }
}
int Dis[MAX][MAX][4];
bool Vis[MAX][MAX][4];
int SPFA(int Ex,int Ey,int Sx,int Sy,int Tx,int Ty)
{
    if(Sx==Tx&&Sy==Ty)return 0;
    int ans=2e9;bfs(Ex,Ey,Sx,Sy);
    memset(Dis,63,sizeof(Dis));
    queue<int> Qx,Qy,Qd;
    for(int i=0;i<4;++i)
    {
        int x=Sx+d[i][0],y=Sy+d[i][1];
        if(x<1||y<1||x>n||y>m||!a[x][y])continue;
        if(dis[x][y]>1e9)continue;
        Dis[Sx][Sy][i]=dis[x][y];Vis[Sx][Sy][i]=true;
        Qx.push(Sx);Qy.push(Sy);Qd.push(i);
    }
    while(!Qx.empty())
    {
        int x=Qx.front(),y=Qy.front(),D=Qd.front();
        Qx.pop();Qy.pop();Qd.pop();
        for(int i=0;i<4;++i)
        {
            int xx=x+d[i][0],yy=y+d[i][1];
            if(xx<1||yy<1||xx>n||yy>m||!a[xx][yy])continue;
            int w=mov[x][y][D][i]+Dis[x][y][D]+1;
            if(Dis[xx][yy][i^1]>w)
            {
                Dis[xx][yy][i^1]=w;
                if(!Vis[xx][yy][i^1])
                    Vis[xx][yy][i^1]=true,Qx.push(xx),Qy.push(yy),Qd.push(i^1);
            }
        }
        Vis[x][y][D]=false;
    }
    for(int i=0;i<4;++i)ans=min(ans,Dis[Tx][Ty][i]);
    if(ans>1e9)ans=-1;
    return ans;
}
int main()
{
    n=read();m=read();Q=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            a[i][j]=read();
    memset(mov,63,sizeof(mov));
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(a[i][j])
                for(int k=0;k<4;++k)
                {
                    int x=i+d[k][0],y=j+d[k][1];
                    if(x<1||y<1||x>n||y>m)continue;
                    if(!a[x][y])continue;
                    bfs(x,y,i,j);
                    for(int l=0;l<4;++l)
                    {
                        int xx=i+d[l][0],yy=j+d[l][1];
                        mov[i][j][k][l]=dis[xx][yy];
                    }
                }
    while(Q--)
    {
        int Ex=read(),Ey=read(),Sx=read(),Sy=read(),Tx=read(),Ty=read();
        printf("%d\n",SPFA(Ex,Ey,Sx,Sy,Tx,Ty));
    }
    return 0;
}