NOIP2015题解

NOIP2015题解

Day1

神奇的幻方 magic

模拟裸题。我在NOIP切掉的第一道题

#include
#include
using namespace std;
int n,a[50][50],x,y;
int main()
{
    scanf("%d",&n);
    x=1;y=(n+1)>>1;
    for(int i=1;i<=n*n;++i)
    {
        a[x][y]=i;x-=1;y+=1;
        if(x==0)
        {
            if(y>n)x=2,y=n;
            else x=n;
        }
        else if(y>n)y=1;
        else if(a[x][y])x+=2,y-=1;
        
    }
    for(int i=1;i<=n;++i,puts(""))
        for(int j=1;j<=n;++j)
            printf("%d ",a[i][j]);
    return 0;
}

信息传递 message

不难发现就是让你求基环森林的最小环,直接\(Tarjan\)了。

#include
#include
using namespace std;
#define MAX 200200
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 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 n,ans=1e9;
int dfn[MAX],low[MAX],tim,S[MAX],top;
bool ins[MAX];
void Tarjan(int u)
{
    dfn[u]=low[u]=++tim;S[++top]=u;ins[u]=true;
    for(int i=h[u];i;i=e[i].next)
    {
        int v=e[i].v;
        if(!dfn[v])Tarjan(v),low[u]=min(low[u],low[v]);
        else if(ins[v])low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        int sz=0,v;
        do{v=S[top--];ins[v]=false;++sz;}while(u!=v);
        if(sz>1)ans=min(ans,sz);
    }
}
int main()
{
    n=read();
    for(int i=1;i<=n;++i)Add(i,read());
    for(int i=1;i<=n;++i)if(!dfn[i])Tarjan(i);
    printf("%d\n",ans);
    return 0;
}

斗地主 landlords

很妙的题。而且我的这里写的是可以被\(Hack\)的。

发现出牌有两种关系,一种与点数无关,一种与点数相关。那么当对于点数无关的时候,我们贪心求一次解,点数相关的时候我们爆搜顺子。

这样子可以过这题,但是有问题。因为贪心是假的,假的原因是你要考虑一些牌是可以拆开打的,比如说你有三个炸弹,理论上说是出\(3\)次。事实上你可以把其中一个拆成两对,这样子\(2\)次就打出去了。所以把贪心换成\(dp\)就没有问题了。然而数据随机,贪心基本就行了。

#include
#include
#include
#include
#include
#include
#include
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,ans;
int a[20],b[20];
void dfs(int st)
{
    for(int i=0;i<=14;++i)b[a[i]]+=1;
    for(int i=1;i<=14;++i)
        if(a[i]==4)
        {
            if(b[1]>=2)b[1]-=2;
            else if(b[2]>=2)b[2]-=2;
            else if(b[2])b[2]-=1;
        }
    for(int i=1;i<=14;++i)
        if(a[i]==3)
        {
            if(b[1])b[1]-=1;
            else if(b[2])b[2]-=1;
        }
    ans=min(ans,st+b[1]+b[2]+b[3]+b[4]);
    b[0]=b[1]=b[2]=b[3]=b[4]=0;
    for(int i=3;i<=10;++i)
        for(int j=i;j<=15;++j)
        {
            if(!a[j]){for(int k=i;k=5)dfs(st+1);
        }
    for(int i=3;i<=12;++i)
        for(int j=i;j<=15;++j)
        {
            if(a[j]<2){for(int k=i;k=3)dfs(st+1);
        }
    for(int i=3;i<=13;++i)
        for(int j=i;j<=15;++j)
        {
            if(a[j]<3){for(int k=i;k=2)dfs(st+1);
        }
}
int main()
{
    freopen("landlords.in","r",stdin);
    freopen("landlords.out","w",stdout);
    int T=read();n=read();
    while(T--)
    {
        for(int i=0;i<=14;++i)a[i]=0;
        for(int i=1;i<=n;++i)a[read()]+=1,read();
        a[14]=a[1];a[1]=0;ans=n;dfs(0);
        printf("%d\n",ans);
    }
    return 0;
}

Day2

跳石头 stone

又想起我当年连这种题都不会做(雾

二分答案,直接扫一遍就好了。

#include
#include
using namespace std;
#define MAX 50050
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 L,n,m,S[MAX],top,d[MAX];
bool check(int s)
{
    S[top=0]=0;int mov=0;
    for(int i=1;i<=n;++i)
    {
        if(d[i]>1;
        if(check(mid))l=mid+1,ret=mid;
        else r=mid-1;
    }
    printf("%d\n",ret);return 0;
}

子串 substring

\(dp\)题。

\(f[i][j][k][0/1]\)表示当前考虑\(A\)串的第\(i\)位,\(B\)串匹配了第\(j\)个字符,当前已经分了\(k\)段,当前\(i\)位置的字符是否在最后一段中的方案数。

转移不难,看看代码就知道了。

#include
#include
#include
using namespace std;
#define MOD 1000000007
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
char A[1010],B[220];
int f[2][220][220][2];
int n,m,K;
int main()
{
    scanf("%d%d%d",&n,&m,&K);
    scanf("%s",A+1);scanf("%s",B+1);
    f[0][0][0][0]=1;
    for(int i=1,nw=1,pw=0;i<=n;++i,nw^=1,pw^=1)
    {
        memset(f[nw],0,sizeof(f[nw]));
        for(int j=0;j<=m;++j)
            for(int k=0;k<=K;++k)
            {
                add(f[nw][j][k][0],f[pw][j][k][0]);
                add(f[nw][j][k][0],f[pw][j][k][1]);
                if(A[i]==B[j])
                {
                    add(f[nw][j][k][1],f[pw][j-1][k][1]);
                    if(k)
                    {
                        add(f[nw][j][k][1],f[pw][j-1][k-1][0]);
                        add(f[nw][j][k][1],f[pw][j-1][k-1][1]);
                    }
                }
            }
    }   
    printf("%d\n",(f[n&1][m][K][0]+f[n&1][m][K][1])%MOD);
    return 0;
}

运输计划 transport

现在看是真的简单题。。。

二分答案,显然时间本来就小于二分值的就不用管,只考虑时间大于二分值的。那么显然它要改变路径上的一条边时间才会变小,那么树上差分找到可以改变所有计划的一条最长边。然后用最大的时间减去最长边边长和二分值比较判定就做完了。

#include
#include
#include
using namespace std;
#define MAX 300300
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 Line{int v,next,w;}e[MAX<<1];
int h[MAX],cnt=1,df[MAX];
inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
int n,m,mx;
namespace LCA
{
    int dep[MAX],fa[MAX],top[MAX],size[MAX],hson[MAX],dis[MAX];
    void dfs1(int u,int ff)
    {
        size[u]=1;fa[u]=ff;dep[u]=dep[ff]+1;
        for(int i=h[u];i;i=e[i].next)
        {
            int v=e[i].v;if(v==ff)continue;
            dis[v]=dis[u]+e[i].w;df[v]=e[i].w;
            dfs1(v,u);size[u]+=size[v];
            if(size[hson[u]]t)
            ++tot,a[p[i].u]+=1,a[p[i].v]+=1,a[p[i].lca]-=2;
    dfs(1,0);
    int mxt=0;
    for(int i=2;i<=n;++i)
        if(a[i]==tot)
            mxt=max(mxt,df[i]);
    return mx-mxt<=t;       
}
int main()
{
    n=read();m=read();
    for(int i=1;i>1;
        if(check(mid))r=mid-1,ret=mid;
        else l=mid+1;
    }
    printf("%d\n",ret);
    return 0;
}

转载于:https://www.cnblogs.com/cjyyb/p/9931796.html

你可能感兴趣的:(NOIP2015题解)