ZOJ Monthly, March 2013 总结

A.A Simple Tree Problem

  刚开始没想好怎么转化,其实,用vector记录下来每个父节点的直接孩子,来个深度优先遍历进行编号,就可以把树形的结构转化成一维的线性结构,然后就是一般的线段树了!

ZOJ 3686
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
vector<int>son[100010];
int l[100010],r[100010];//记录每个节点所对应的区间的左右端点
int sum[400010],flag[400010];
int num;

void dfs(int x)
{
    l[x]=num++;
    int size=son[x].size();
    for(int i=0;i<size;i++)
    {
        dfs(son[x][i]);
    }
    r[x]=num-1;
}

void pushup(int rt)
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}

void pushdown(int rt,int len,int llen)
{
    flag[rt]=0;
    flag[rt<<1]^=1;
    flag[rt<<1|1]^=1;
    sum[rt<<1]=llen-sum[rt<<1];
    sum[rt<<1|1]=len-llen-sum[rt<<1|1];
    
}

void update(int s,int e,int l,int r,int rt)
{
    if(s<=l&&e>=r)
    {
        flag[rt]^=1;
        sum[rt]=r-l+1-sum[rt];
        return;
    }
    int m=(l+r)>>1;
    if(flag[rt])
        pushdown(rt,r-l+1,m-l+1);
    if(m>=s)
        update(s,e,lson);
    if(m<e)
        update(s,e,rson);
    pushup(rt);
}

int query(int s,int e,int l,int r,int rt)
{
    if(s<=l&&e>=r)
        return sum[rt];
    int ret=0;
    int m=(l+r)>>1;
    if(flag[rt])
        pushdown(rt,r-l+1,m-l+1);
    if(m>=s)
        ret+=query(s,e,lson);
    if(m<e)
        ret+=query(s,e,rson);
    pushup(rt);
    return ret;
}

int main()
{
    int n,m,temp,i;
    char ch[10];
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(i=1;i<=n;i++)
        {
            son[i].clear();
        }
        for(i=2;i<=n;i++)
        {
            scanf("%d",&temp);
            son[temp].push_back(i);
        }
        num=1;
        dfs(1);
        memset(sum,0,sizeof(sum));
        memset(flag,0,sizeof(flag));
        while(m--)
        {
            scanf("%s %d",ch,&temp);
            if(ch[0]=='o')
            {
                update(l[temp],r[temp],1,n,1);
            }
            else
            {
                printf("%d\n",query(l[temp],r[temp],1,n,1));
            }
        }
        printf("\n");
    }
    return 0;
}

B.The Review Plan I

  其实从推导来说,这道题比C要水多了...甚B至根本就不用推导,直接用容斥就行了,比赛的时候一直想着,把每个位置不能放的点用vector存下来,但是这样大大增加了容斥原理实现的难度,当我们求有两个位置放错的情况数时,如果这两个位置都不能放X号章节,那么情况数求起来就会很麻烦,直到最后都没得出一个可用的公式..

  正确的思路是,记录m个条件,直接对这些条件进行容斥原理,但是有一个额外的条件,就是不能同时选对同一个位置的限制或者对同一个章节的限制,这里我加了两个check数组来记录...然后剩下的就是最裸的容斥了。

  还有很坑爹的一点!!!限制是可能重复的,要加判重!!

ZOJ 3687
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html
#include<iostream>
#include<stdio.h>
#include<string.h>

using namespace std;

#define MOD 55566677

int check[55],check2[55];//标记天数和章节
int n,m,mark[55][55];
long long fa[100],ans;
struct node
{
    int a,b;
}va[30];

void dfs(int pos,int num)
{
    if(pos==(m+1))
    {
        if(num&1)
            ans=(ans-fa[n-num])%MOD;
        else
            ans=(ans+fa[n-num])%MOD;
    }
    else
    {
        dfs(pos+1,num);
        if(!check[va[pos].a]&&!check2[va[pos].b])
        {
            check[va[pos].a]=1;
            check2[va[pos].b]=1;
            dfs(pos+1,num+1);
            check[va[pos].a]=0;
            check2[va[pos].b]=0;
        }
    }
}
int main()
{
    int i;
    fa[0]=1;
    for(i=1;i<=60;i++)
    {
        fa[i]=(fa[i-1]*i)%MOD;
    }
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(mark,0,sizeof(mark));
        for(i=1;i<=m;i++)
        {
            scanf("%d%d",&va[i].a,&va[i].b);
            if(!mark[va[i].a][va[i].b])
            {
                mark[va[i].a][va[i].b]=1;
            }
            else
            {
                i--;
                m--;
            }
        }
        ans=0;
        dfs(1,0);//(当前条件的编号,选中的条件个数)
        printf("%lld\n",(ans%MOD+MOD)%MOD);
    }
    return 0;
}

C.The Review Plan II

  容斥原理,推起来比较难,研究了好久才弄出来,假设有1个章节所在的天数是错误的情况数是W(1),则每个错误的章节i可以放在i或i+1天,剩下的章节方法数时(n-1)!

所以w(1)=C(n,1)*2*(n-1)!,这一步很容易想到,有点坑的是2个以上的情况,假设有k个章节所在的天数是错误的情况数是W(K),总共n个章节分别记为1,2,...n,它们可放的天数可以表示为(1,2)(2,3)(3,4)...(n,1)现在我们把括号去掉即得到一个数列1,2,2,3,3,4,....n,1,现在我们从里面取出K个数,分别表示k个章节所在的天数,只要满足(1).取出的任何2数不相邻 (即不能取自同一个括号,且不会取到相同的数) (2)两个1不同时被取到。那么,剩下的元素的摆放方法就是(n-k)!,现在只要求出满足这两个条件的取法数就可以了。

  把问题单独拿出来,现在我们求一个子问题,1,2,3,....n中能够取出多少个长为k的递增子序列a1,a2...ak,使得ai+1-ai>1

  设已经取出了满足上述条件的子序列,我们构造一个新的序列,b1,b2....bk,其中bi=ai-(i-1);

  则bi+1-bi=ai+1-ai-1>0

  从而1<=a1=b1<b2<.....<bk<=n-k+1

  确定b序列只需要从1,2...n-k+1中取出k个数就行了,然后b序列又可以转化成对应的a序列,所以方法数是C(n-k-1,k)

  回到我们本来的问题,满足第一个条件的取法数是C(2n-k+1,k),满足第一个条件,但不满足第二个条件的取法数是C(2n-4-(k-2)+1,k-2)

  所以W(K)={C(2n-k+1,k)-C(2n-4-(k-2)+1,k-2)}*(n-k)!

  W(K)确定以后就是最裸的容斥原理了~

ZOJ 3688
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html
#include<stdio.h>
#define MOD 1000000007
long long fa[200010],invfa[200010];


long long quickpow(long long m,long long n,long long p)
{
    long long ans=1;
    while(n)
    {
        if(n&1)
            ans=(ans*m)%p;
        n=n>>1;
        m=(m*m)%p;
    }
     return ans;
}
 
long long Inv(long long x,long long p)
{
    return quickpow(x,p-2,p);
}

void init()
{
    fa[0]=1;
    for(int i=1;i<=200000;i++)
    {
        fa[i]=(fa[i-1]*i)%MOD;
        invfa[i]=Inv(fa[i],MOD);
    }
    invfa[0]=Inv(fa[0],MOD);
}

long long W(int n,int k)
{
    long long a=fa[2*n-k+1];
    a=(a*invfa[k])%MOD;
    a=(a*invfa[2*n-k-k+1])%MOD;
    long long b=fa[2*n-k-1];
    b=(b*invfa[k-2])%MOD;
    b=(b*invfa[2*n-2*k+1])%MOD;
    return ((fa[n-k]*(a-b))%MOD+MOD)%MOD;
}

int main()
{
    init();
    int n,i;
    while(scanf("%d",&n)!=EOF)
    {
        if(n==1)
        {
            printf("0\n");
            continue;
        }
        long long ans=fa[n];
        for(i=1;i<=n;i++)
        {
            if(i&1)
                ans=(ans-W(n,i))%MOD;
            else
                ans=(ans+W(n,i))%MOD;
        }
        printf("%lld\n",(ans+MOD)%MOD);
    }
    return 0;
}

D.Digging

  比赛的时候不知道为什么一直没看过这道题,直到很多人过了,我们才去看,刚开始也没有思路,觉得很像背包,但是又不像背包,后来ry直接想到了交叉排序...目测这次比赛基本全靠ry= =,我是多能打酱油...

  假设要做任务1和2,现在的时间是T,如果先做1,得到的回报是V1=T*s1+(T-t1)*s2,如果先做2得到的回报是V2=T*s2+(T-t2)*s1,V1-V2=t2*s1-t1*s2,因此只要每两个元素都交叉比较来排序就可以得到最优的顺序,考虑到最后快结束的时候,不一定连续取就最优,所以在这个顺序下再用一次01背包就可以了。

  

ZOJ 3689
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int dp[10100];

struct node
{
    int s,t;
}va[3010];

int cmp(const void *a,const void *b)
{
    return (*(node *)a).s*(*(node *)b).t-(*(node *)b).s*(*(node *)a).t;
}

int MAX(int x,int y)
{
    return x>y?x:y;
}

int main()
{
    int n,t,i,j;
    while(scanf("%d%d",&n,&t)!=EOF)
    {
        for(i=0;i<n;i++)
        {
            scanf("%d%d",&va[i].t,&va[i].s);
        }
        qsort(va,n,sizeof(va[0]),cmp);
        memset(dp,0,sizeof(dp));
        for(i=0;i<n;i++)
        {
            for(j=t;j>=va[i].t;j--)
            {
                dp[j]=MAX(dp[j],dp[j-va[i].t]+j*va[i].s);
            }
        }
        printf("%d\n",dp[t]);
    }
    return 0;
}

E.Choosing number

  这道题也勉强算是数学题吧,居然没一下想到矩阵...好失败ToT

  正确的思路是,对于每一个方法数ans[n],分成2部分,一种是ans[n][0],代表n个人排成的,以号码数小于等于K的人结尾的方法数,ans[n][1]代表n个人排成的,以号码数大于K的人结尾的方法数所以:

  ans[n+1][0]=ans[n][0]*(k-1)+ans[n][1]*k

  ans[n+1][1]=(ans[n][0]+ans[n][1])*(m-k)

这两个式子刚好可以构造一个2X2的矩阵,剩下的只要用矩阵的快速幂就OK了

ZOJ 3690
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html
#include<stdio.h>
#define MOD 1000000007
#define maxn 6
long long ret[maxn][maxn];
long long init[maxn][maxn];
long long buf[maxn][maxn];
void matrixMul(long long a[][maxn] , long long b[][maxn] , long long n,long long mod)
{
    long long i,j,k;
    for(i=0;i<n;i++)
    {
        for(j=0;j<n;j++)
        {
            buf[i][j]=0;
        }
    }
    for(i=0;i<n;i++)
    {
        for(k=0;k<n;k++)
        {
            if(a[i][k]==0)
                continue;
            for(j=0;j<n;j++)
            {
                if(b[k][j]==0)
                    continue;
                buf[i][j]+=a[i][k]*b[k][j];
                if (buf[i][j]>=mod||buf[i][j]<=-mod)
                {
                    buf[i][j]%=mod;
                }
            }
        }
    }
    for(i=0;i<n;i++)
    {
        for(j=0;j<n;j++)
        {
            a[i][j]=buf[i][j];
        }
    }
}

void matrixMul(long long n,long long m,long long mod)
{
    long long i,j;
    for(i=0;i<n;i++)
    {
        for(j=0;j<n;j++)
        {
            ret[i][j]=(i==j);
        }
    }
    for(;m;m>>=1)
    {
        if(m&1)
        {
            matrixMul(ret,init,n,mod);
        }
        matrixMul(init,init,n,mod);
    }
}

int main()
{
    int n,m,k;
    while(scanf("%d%d%d",&n,&m,&k)!=EOF)
    {
        init[0][0]=k-1;
        init[0][1]=m-k;
        init[1][0]=k;
        init[1][1]=m-k;
        matrixMul(2,n-1,MOD);
        long long ans=(k*ret[0][0]+(m-k)*ret[1][0])%MOD;
        ans=(ans+k*ret[0][1]+(m-k)*ret[1][1])%MOD;
        printf("%lld\n",ans);
    }
    return 0;
}

H.Happy Great BG

  其实这题也不能说是精度问题...主要两点易错的是

  1.教练俩人也是人(这个直接看出来了...因为样例都过不了)

  2.考虑到现实情况,不能直接保留2位小数,只要分以下不是0就要进位,所以后面要加个0.00499!确实很好地结合了实际= =

ZOJ 3693
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html
#include<stdio.h>
int main()
{
    int n,k;
    double w;
    while(scanf("%d%lf%d",&n,&w,&k)!=EOF)
    {
        n+=2;
        int temp=n/k;
        n-=temp;
        printf("%.2lf\n",1.0*n*w/2+0.00499);
    }
    return 0;
}

  

  

原文地址:https://www.cnblogs.com/SolarWings/p/2994548.html