BestCoder Round #76

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


官方题解:

因为数据规模很小,所以直接用O(n^2)O(n2)时间求出有多少对(i,j)(i,j)满足a_i<a_jai<aj,然后再除以n(n-1)/2n(n1)/2即可。

当然也有O(n\log n)O(nlogn)的做法,也很简单。

时间复杂度O(n^2)O(n2)


我的思考:简单递推,先sort,然后如果a[i]==a[i-1] f[i]=f[i-1] else f[i]=i 然后遍历数组求下和就行

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
    int T,n,i;
    int a[500],f[500];
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(i=0;i<n;i++)
            scanf("%d",&a[i]);
        if(n==1)
        {
            printf("0.000000\n");
            continue;
        }
        sort(a,a+n);
        f[0]=0;
        for(i=1;i<=n-1;i++)
        {
            if(a[i]==a[i-1]) f[i]=f[i-1];
            else f[i]=i;
        }
        double x=0;
        for(i=0;i<n;i++)
        {
            x+=1.0/n*f[i]/(n-1);
        }
        printf("%.6lf\n",x);
    }
}


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


官方题解:

sum(a,k) = a + (a+1) + \cdots + (a+k-1)sum(a,k)=a+(a+1)++(a+k1)

首先,有解的充要条件是sum(1,k) \le nsum(1,k)n(如果没取到等号的话把最后一个kk扩大就能得到合法解)。

然后观察最优解的性质,它一定是一段连续数字,或者两段连续数字中间只间隔1个数。这是因为1\le a<=b-21a<=b2时有ab<(a+1)(b-1)ab<(a+1)(b1),如果没有满足上述条件的话,我们总可以把最左边那段的最右一个数字作为aa,最右边那段的最左一个数字作为bb,调整使得乘积更大。

可以发现这个条件能够唯一确定nn的划分,只要用除法算出唯一的aa使得sum(a,k)\le n < sum(a+1,k)sum(a,k)n<sum(a+1,k)就可以得到首项了。

时间复杂度O(\sqrt {n} )O(n),这是暴力把每项乘起来的复杂度。


我的思考:先求出一个数f(x)=x+x+1+……x+k 使f(x)<=sum<=f(x+1)然后将sum-f(x)分配给后sum-f(x)个数,这样求出来的值就可以最大,算是贪心的思想了。

#include<cstdio>
#include<cstring>
#define LL __int64
using namespace std;
const LL MOD=1e9+7;
int main()
{
    int T,i;
    LL ans,sum,a,b,n,k;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d",&n,&k);
        sum=(1+k)*k/2;
        if(sum>n)
        {
            printf("-1\n");
            continue;
        }
        a=n-sum;
        b=a/k+1;
        a%=k;
        ans=1;
        for(i=b;i<=b+k-a-1;i++)
            ans=(LL)ans*i%MOD;
        for(i=b+k-a+1;i<=b+k;i++)
            ans=(LL)(ans*i)%MOD;
        printf("%I64d\n",ans);
    }
    return 0;
}

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

官方题解:

对于每个结点ii,统计出f[i]f[i]表示包含ii的连通集有多少个,那么容易看出答案就是所有f[i]f[i]的和。

要计算f[i]f[i]是经典的树形DP问题。令g[i]g[i]表示以ii为根的连通集的数目,那么g[i]=\prod_j (g[j]+1)g[i]=j(g[j]+1)jjii的儿子,按这个从下往上DP一遍。

然后再用同样的式子从上往下DP一遍就好了。这一步骤写的时候要注意一下不要写错。

时间复杂度O(n)O(n)


我的思考:数状dp,第一次接触,先稍微了解了一下再来看着道题,但还是看了很久 

f[i]表示包含以i为根节点的联通块集合中所有元素个数之和

g[i]表示以i为根节点的联通块集合的个数

每当找到i节点的一个新son之后 进行更新

f[i]=f[i]+f[i]*g[son]+g[i]*f[son];

g[i]=g[i]+g[i]*g[son];

#include<cstdio>
#include<cstring>
#include<vector>
#define LL __int64
using namespace std;
const int N=200005;
const int mod=1e9+7;
vector<int>son[N];
LL g[N],f[N];
void dfs(int n,int fa)
{
    for(int i=0;i<son[n].size();i++)
    {
        if(son[n][i]==fa)
            continue;
        dfs(son[n][i],n);
        f[n]=f[n]*(g[son[n][i]]+1)%mod+g[n]*f[son[n][i]]%mod;
        f[n]%=mod;
        g[n]=g[n]*(g[son[n][i]]+1)%mod;
    }
}
int main()
{
    int T,n;
    scanf("%d",&T);
    while(T--)
    {
        int temp;
        scanf("%d",&n);
        for(int i=0;i<=n;i++)
        {
            son[i].clear();
            g[i]=f[i]=1;
        }
        for(int i=2;i<=n;i++)
        {
            scanf("%d",&temp);
            son[temp].push_back(i);
            son[i].push_back(temp);
        }
        LL sum=0;
        dfs(1,-1);
        for(int i=1;i<=n;i++)
        {
            sum+=f[i];
            sum%=mod;
        }
        printf("%I64d\n",sum);
    }
    return 0;
}

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

官方题解:

l=\log_2 \max{n,m}l=log2max{n,m}

枚举i \text{ AND } ji AND ji\text{ OR }ji OR j的值分别是多少,因为是有包含关系的,所以枚举量是3^l3l的,用枚举子集的方法。

然后要计算有多少组i,ji,j满足i \text{ AND } j=ai AND j=ai\text{ OR }j=bi OR j=b, 且1\le i \le n, 1\le j\le m1in,1jmiijj肯定都包含了aa,剩下b-aba的那些bit要分配给iijj,你可以算出最大分配多少才能使ii不超过nnjj也是同理,于是就确定了一个区间,区间内的都是合法的方案。

所以总的复杂度就是O(l \times 3^l) = O(n^{1.59} \log n)O(l×3l)=O(n1.59logn)

claris老司机提供了一个O(3^l)O(3l)的做法。考虑数位dp,状态是f[i][and][or][2][2]f[i][and][or][2][2],这样的状态数是1+3+\cdots + 3^l=O(3^l)1+3++3l=O(3l)的,然后转移也可以O(1)O(1)。求gcd也不需要log,可以用一个O(n)O(n)预处理支持O(1)O(1)询问的结构,见http://mimuw.edu.pl/~kociumaka/files/stacs2013_slides.pdf 。

这题用奇妙的打表或者其他更加厉害的做法也是有可能AC的。

我的理解:

1.有人是打表做的

2.暴力求 a=i|j b=i&j 开一个超大的hash数组记录gcd(a,b) 映射方法为 a*(a+1)/2+b

#include<cstdio>
using namespace std;
__int16 q[136000000];
int main()
{
    int T,n,m;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        __int64 ans=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                int a=i|j;
                int b=i&j;
                int tmp=a*(a+1)/2+b;
                if(q[tmp]!=0)
                    ans+=q[tmp];
                else
                {
                    while(b)
                    {
                        int r=a%b;
                        a=b;
                        b=r;
                    }
                    ans+=a;
                    q[tmp]=a;
                }
            }
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

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

官方题解:

这是一道良心的基础数据结构题。

我们二分a[k]a[k]的值,假设当前是midmid,然后把大于midmid的数字标为11,不大于midmid的数字标为00。然后对所有操作做完以后检查一下a[k]a[k]位置上是00还是11

因为只有两种值,所以操作还是不难做的。只要用一个线段树,支持区间求和、区间赋值即可。这样要排序一个区间时只要查询一下里面有几个11和几个00,然后把前半段赋值为00,后半段赋值为11即可(降序的话就是反过来)。

复杂度是O(m\log ^2 n)O(mlog2n)的。

这题用其他玄学做法或者用更加厉害的平衡树做法也是有可能AC的。

我的思考:如官方题解所说,蒟蒻在线段树下颤栗

#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e5+5;
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
int n,pos,Q;
int op[N][3];
int S[N<<2],col[N <<2],A[N];
void push_up(int rt)
{
    S[rt]=S[rt<<1]+S[rt<<1|1];
}
void push_down(int rt,int m,int len)
{
    if(col[rt]!=-1)
    {
        int szr=len>>1,szl=len-szr;
        col[rt<<1]=col[rt<<1|1]=col[rt];
        S[rt<<1]=szl*col[rt];S[rt<<1|1]=szr*col[rt];
        col[rt]=-1;
    }
}
void build(int x,int l,int r,int rt)
{
    col[rt]=-1;
    if(l==r)
    {
        S[rt]=A[l]<=x?0:1;
        return;
    }
    int m=(l+r)>>1;
    build(x,lson);
    build(x,rson);
    push_up(rt);
}
int query(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R) return S[rt];
    int ret=0,m=(l+r)>>1;
    push_down(rt,m,r-l+1);
    if(L<=m) ret+=query(L,R,lson);
    if(R>m) ret+=query(L,R,rson);
    return ret;
}
void update(int L,int R,int x,int l,int r,int rt)
{
    if(L>R) return;
    if(L<=l&&r<=R)
    {
        col[rt]=x;
        S[rt]=(r-l+1)*x;
        return;
    }
    int m=(l+r)>>1;
    push_down(rt,m,r-l+1);
    if(L<=m) update(L,R,x,lson);
    if(R>m)  update(L,R,x,rson);
    push_up(rt);
}
bool check(int x)
{
    build(x,1,n,1);
    for(int i=1;i<=Q;i++)
    {
        int sum=query(op[i][1],op[i][2],1,n,1);
        if(op[i][0]==0)
        {
            update(op[i][1],op[i][2]-sum,0,1,n,1);
            update(op[i][2]-sum+1,op[i][2],1,1,n,1);
        }
        else
        {
            update(op[i][1],op[i][1]+sum-1,1,1,n,1);
            update(op[i][1]+sum,op[i][2],0,1,n,1);
        }
    }
    int w=query(pos,pos,1,n,1);
    return w==0;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&Q);
        for(int i=1;i<=n;i++)
            scanf("%d",&A[i]);
        for(int i=1;i<=Q;i++)
        {
            for(int j=0;j<3;j++)
                scanf("%d",&op[i][j]);
        }
        scanf("%d",&pos);
        int l=1,r=n,m;
        while(l<=r)
        {
            m=(l+r)>>1;
            if(check(m)) r=m-1;
            else l=m+1;
        }
        printf("%d\n",r+1);
    }
    return 0;
}


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