BestCoder Round #80

A题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5665

官方题解:

因为每个数可以用很多次,所以只要这个集合中有1,那么就可以加出所有的数来.

注意最后要判定一下是否有0.

之前版本的题目描述是用的自然数,后来因为有歧义换成了非负整数.

(本来是打算留个自然数当Trick的..sad story..)

我的理解:没考虑到0 悲伤的故事

#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
    int i,n,a,T,flag;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        flag=0;
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a);
            if(a==1)
            {
                if(flag==0)flag=1;
                else if(flag==2)flag=3;
            }
            if(a==0)
            {
                if(flag==0)flag=2;
                else if(flag==1)flag=3;
            }
        }
        if(flag==3)printf("YES\n");else printf("NO\n");
    }
    return 0;
}

B题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5666

官方题解:

考虑一条以(0,0)(0,0)为起点,(x,y)(x,y)为终点的线段上格点的个数(不包含端点时),一定是gcd(x,y)-1gcd(x,y)1,这个很显然吧.

然后整个网格图范围内的格点数目是\frac {q*(q-1)} 22q(q1).

所以答案就是\frac {q*(q-1)} 2 -2q(q1) 所有线段上的格点的个数.

因为gcd(a,b)=gcd(a,b-a)\ (b>a)gcd(a,b)=gcd(a,ba) (b>a),所以gcd(x,y)=gcd(x,p-x)=gcd(x,p)gcd(x,y)=gcd(x,px)=gcd(x,p),p是质数,所以gcd(x,y)=1gcd(x,y)=1,所以线段上都没有格点,所以答案就是\frac {q*(q-1)} 22q(q1).

我的理解:java大数或快速乘

import java.io.*;  
import java.math.BigInteger;  
import java.util.*;  
  
public class Main  
{  
    public static void main(String args[])  
    {  
        Scanner cin = new Scanner(System.in);     
        int t=cin.nextInt();
        for(int i=1;i<=t;i++)
        {
        BigInteger n=cin.nextBigInteger();
        BigInteger m=cin.nextBigInteger();
        BigInteger a=n.subtract(BigInteger.valueOf(1));
        BigInteger b=n.subtract(BigInteger.valueOf(2));
        BigInteger ans=a.multiply(b);
        ans=ans.divide(BigInteger.valueOf(2));
        ans=ans.remainder(m);
        System.out.println(ans);}
    }  
}  

#include<cstdio>
#include<cstring>
#define ll __int64
using namespace std;
ll quickji(ll a,ll b,ll p)
{
    ll sum=0;
    while(b)
    {
        if(b&1)
        {
            sum+=a;
            sum%=p;
        }
        a=a*2%p;
        b/=2;
    }
    return sum;
}
int main()
{
    ll p,q;
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d",&q,&p);
        ll a=q-1;
        ll b=q-2;
        if(a%2==0)a/=2;
        if(b%2==0)b/=2;
        printf("%I64d\n",quickji(a,b,p));
    }
    return 0;
}

C题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5667

官方题解:

观察递推式我们可以发现,所有的f_ifi都是aa的幂次,所以我们可以对f_ifi取一个以aa为底的loglog,即g_i=log_a\ f_igi=loga fi

那么递推式变成g_i=b+c*g_{i-1}+g_{i-2}gi=b+cgi1+gi2,这个式子可以矩阵乘法

这题有一个小trick,注意a\ mod\ p=0a mod p=0的情况.

我的理解:矩阵快速幂+费马小定理

#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
struct matrix
{
    ll a[3][3];
};
ll n,a,b,c,p;
matrix A;
void init(ll x)
{
    A.a[0][0]=x;
    A.a[0][1]=1;
    A.a[0][2]=1;
    A.a[1][0]=1;
    A.a[1][1]=A.a[1][2]=A.a[2][0]=A.a[2][1]=0;
    A.a[2][2]=1;
}
matrix mul(matrix x,matrix y)
{
    matrix ans;
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            ans.a[i][j]=0;
            for(int k=0;k<3;k++)
            {
                (ans.a[i][j]+=((x.a[i][k]*y.a[k][j])%(p-1)))%=(p-1);
            }
        }
    }
    return ans;
}
ll fast_run(matrix temp,ll num)
{
    matrix s,base;
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            s.a[i][j]=0;
            base.a[i][j]=temp.a[i][j];
        }
    }
    s.a[0][0]=s.a[1][1]=s.a[2][2]=1;
    while(num)
    {
        if(num&1)
        {
            s=mul(s,base);
        }
        base=mul(base,base);
        num/=2;
    }
    return (s.a[0][0]+s.a[0][2])%(p-1);
}
ll quickmod(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1)
        {
            ans*=a;
            ans%=p;
        }
        (a*=a)%=p;
        b/=2;
    }
    return ans;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld%lld%lld%lld",&n,&a,&b,&c,&p);
        init(c);
        if(n==1)printf("1\n");
        else if(n==2)printf("%lld\n",quickmod(a,b));
        else
        {
            if(a%p==0)printf("0\n");
            else
            {
                ll gg=fast_run(A,n-2)*b%(p-1);
                printf("%lld\n",quickmod(a,gg));
            }
        }
    }
    return 0;
}

D题:http://acm.hdu.edu.cn/showproblem.php?pid=5668

官方题解:考虑对给定的出圈序列进行一次模拟,对于出圈的人我们显然可以由位置,编号等关系得到一个同余方程 一圈做下来我们就得到了n个同余方程 对每个方程用扩展欧几里得求解,最后找到最小可行解就是答案. 当然不要忘了判无解的情况. 有非常多选手似乎都是一眼标算然后写挂了,对此表示很遗憾,但是此题确实是比较容易写挂的...

我的理解:中国剩余定理 我们根据所给条件列出同余方程组,设答案为x 那么x=k(n-i+1)+p,p为上一个出局的位置到现在这个所需要经过的人数这样我们能列出n个方程,其中k为任意非负整数

#include<cstdio>
#include<cstring>
#define LL __int64
using namespace std;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    LL gcd=exgcd(b,a%b,x,y);
    LL tmp=x;
    x=y;
    y=tmp-a/b*x;
    return gcd;
}
int a[25],b[25],t[25];
bool vis[25];
int n,m;
int main()
{
    int T,tmp;
    scanf("%d",&T);
    while(T--)
    {
        LL a1,a2,b1,b2,x,y;
        scanf("%d",&n);
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n;i++)
        {
            a[i]=n-i+1;
            scanf("%d",&tmp);
            t[tmp]=i;
        }
        int now=1;
        for(int i=1;i<=n;i++)
        {
            int cnt=0,flag=0;
            int aim=t[i];
            for(int j=now;j<=n;j++)
            {
                if(!vis[j])
                {
                    cnt++;
                }
                if(j==aim)
                {
                    flag=1;
                    break;
                }
            }
            if(!flag)
            {
                for(int j=1;j<now;j++)
                {
                    if(!vis[j])cnt++;
                    if(j==aim)break;
                }
            }
            vis[aim]=1;
            b[i]=cnt;
            now=aim;
        }
        int flag=0;
        a1=a[1];b1=b[1];
        for(int i=2;i<=n;i++)
        {
            a2=a[i];b2=b[i];
            LL gcd=exgcd(a1,a2,x,y);
            if((b2-b1)%gcd)
            {
                flag=1;
                break;
            }
            LL t=a2/gcd;
            x=(x*(b2-b1))/gcd;
            x=(x%t+t)%t;
            b1=a1*x+b1;
            a1=(a1*a2)/gcd;
            b1=(b1%a1+a1)%a1;
        }
        if(flag)
            printf("Creation August is a SB!\n");
        else
            printf("%I64d\n",(a1+b1)%a1?(a1+b1)%a1:a1);
    }
    return 0;
}

E题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5669

官方链接:

首先考虑暴力,显然可以直接每次O(n^2)O(n2)的连边,最后跑一次分层图最短路就行了.

然后我们考虑优化一下这个连边的过程 ,因为都是区间上的操作,所以能够很明显的想到利用线段树来维护整个图,连边时候找到对应区间,把线段树的节点之间连边.这样可以大大缩减边的规模,然后再跑分层图最短路就可以了.

但是这样建图,每一次加边都要在O(logn)个线段树节点上加边,虽然跑的非常快,但是复杂度仍然是不科学的.

为了解决边的规模的问题,开两棵线段树,连边时候可以新建一个中间节点,在对应区间的节点和中间节点之间连边

进一步缩减了边的规模,强行下降一个数量级

最后跑一下分层图最短路就行了

复杂度O(mlog^2n)O(mlog2n)

什么你会线段树但是不会分层图最短路?安利JLOI2011飞行路线.

因为边的数目还是相对比较多的,所以不能使用SPFA,而要使用Heap-Dijkstra来做最短路,但是不排除某些厉害的选手有特殊的SPFA姿势可以做或者普通 SPFA写的比较优美就不小心跑过去了...

我的理解:线段树+分层图最短路

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
#define lson rt<<1
#define rson rt<<1|1
#define N 800010
struct node
{
    int v,w,next;
}edge[N<<2];
int ec,n,m,posl,posr,cnt,k,id1[N],id2[N],head[N],dist[N][12];
queue<int>qx,qy;
vector<int>p1,p2;
bool vis[N][12];
void add_edge(int u,int v,int w)//线段树加边
{
    edge[ec].v=v;
    edge[ec].w=w;
    edge[ec].next=head[u];
    head[u]=ec++;
}
void build1(int l,int r,int rt)
{
    cnt=max(cnt,rt);//cnt记录点的数目
    if(l==r){id1[l]=rt;return;}//id1记录点的对应的线段树的位置
    int mid=(l+r)>>1;
    build1(l,mid,lson);build1(mid+1,r,rson);
    add_edge(rson,rt,0);
    add_edge(lson,rt,0);
}
void build2(int l,int r,int rt)
{
    if(l==r){id2[l]=cnt+rt;add_edge(id2[l],id1[l],0);return;}//id2记录点的对应的线段树的位置
    int mid=(l+r)>>1;
    build2(l,mid,lson);build2(mid+1,r,rson);
    add_edge(cnt+rt,cnt+(lson),0);
    add_edge(cnt+rt,cnt+(rson),0);
}
void query1(int L,int R,int l,int r,int rt)//query的作用是将线段树上的在所取范围内的坐标所分成的一条条线段所形成的点加入队列
{
    if(L<=l&&r<=R){p1.push_back(rt);return;}
    int mid=(l+r)>>1;
    if(L<=mid)query1(L,R,l,mid,lson);
    if(R>mid) query1(L,R,mid+1,r,rson);
}
void query2(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R){p2.push_back(rt+cnt);return;}
    int mid=(l+r)>>1;
    if(L<=mid) query2(L,R,l,mid,lson);
    if(R>mid)  query2(L,R,mid+1,r,rson);
}
int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    scanf("%d%d%d",&n,&m,&k);
    build1(1,n,1),++cnt,build2(1,n,1);//建立线段树
    int tot=cnt*2+10;
    int ans=0x7ffffff;
    
    //构图过程
    for(int i=1,a,b,c,d,w;i<=m;i++)
    {
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&w);
        p1.clear();p2.clear();
        posl=a,posr=b,query1(posl,posr,1,n,1);//取出初始线段树里面的点集
        posl=c,posr=d,query2(posl,posr,1,n,1);//取出
        ++tot;
        for(int j=0;j<p1.size();j++) add_edge(p1[j],tot,w);//这语句是为了构造一个中间点来连边,这样能将连边数量下降一个数量级
        for(int j=0;j<p2.size();j++) add_edge(tot,p2[j],0);
        p1.clear();p2.clear();
        posl=c,posr=d,query1(posl,posr,1,n,1);//连反向边
        posl=a,posr=b,query2(posl,posr,1,n,1);
        ++tot;
        for(int j=0;j<p1.size();j++) add_edge(p1[j],tot,w);//同上
        for(int j=0;j<p2.size();j++) add_edge(tot,p2[j],0);
    }
    
    //分层最短路 构图完成 和上面可以说没什么关系了
    memset(dist,0x7f,sizeof(dist));
    qx.push(id1[1]);qy.push(0);dist[id1[1]][0]=0;
    while(!qx.empty())
    {
        int x=qx.front(),y=qy.front();vis[x][y]=0;
        qx.pop(),qy.pop();
        if(x==id2[n]) ans=min(ans,dist[x][y]);
        for(int i=head[x];i!=-1;i=edge[i].next)
        {
            if(dist[edge[i].v][y]>dist[x][y]+edge[i].w)
            {
                dist[edge[i].v][y]=dist[x][y]+edge[i].w;
                if(!vis[edge[i].v][y])vis[edge[i].v][y]=1,qx.push(edge[i].v),qy.push(y);
            }
            if(y<k)
            {
                if(dist[edge[i].v][y+1]>dist[x][y])
                {
                    dist[edge[i].v][y+1]=dist[x][y];
                    if(!vis[edge[i].v][y+1])vis[edge[i].v][y+1]=1,qx.push(edge[i].v),qy.push(y+1);
                }
            }
        }
    }
    if(ans==0x7ffffff)printf("CreationAugust is a sb!\n");
    else printf("%d\n",ans);
    return 0;
}


你可能感兴趣的:(BestCoder Round #80)