SRM520 DIVI-500pts

题目链接如下:

http://community.topcoder.com/stat?c=problem_statement&pm=11496&rd=14545

这个题的状态很好写出,假设dp[i][j]表示:到第i个人其得到j分时的情况个数,那么可以写出状态:

dp[i][j]=sum(dp[i-1][p],p>j)*S[i][j],其中S[i][j]表示第i个人得到j分的方法数。不难看出sum(dp[i-1][p],p>j)可以在计算的过程中预处理得出,如果S[i][j]也可以预处理的话,那么状态转移的时间复杂度可以优化到O(nm),n为人数,m为最大分值,可行!

现在我们来处理S[][],对于只做出一题和两题的情况,其方法数很好算,对于做出三题的情况,我是先计算将j得分分配给后面两题有多少中情况,然后加上最前面一题,其实方法有些挫的,还有更好的方法,我做的时候没有想到。一开始想的时候,很容易联想到将n个相同的球分给3个不同盒子的问题,初中问题啊,用插板法(一开始还忘记了。。。),其实解这个题的思路并不是插板法,但是就当温习啦。

PS:一开始实现的时候是用二维数组的,然后Test的时候就会有Kill信号,最后都改为一维数组了。另外,发现自己的代码能力越来越差,写的时候很多小Bug,然后有cha不出来。

代码如下:

#define MOD (1000000007)
#define MAXN 200000
#define _min(a,b) ((a)<(b)?(a):(b))
#define _max(a,b) ((a)>(b)?(a):(b))

int pnum[22];
int toppoint[22];

long long S[MAXN+5];
long long dp[22][MAXN+5];
long long pre[MAXN+5];
long long Stemp[MAXN];
long long psum[MAXN];

void calcS2(int ind,vector<int>& points,int a,int b){
     int i;
     for(i=2;i<=toppoint[ind];i++){
         int P=i;
         P--;
         if(P>points[a]){
            S[i]=_min(points[a],points[b]-P+points[a]);
         }
         else{
            S[i]=P;
         }
     }
}

void calcS3(int ind,vector<int>& points){
     int i,P;
     for(i=3;i<=toppoint[ind];i++){
         int l=_max(2,i-points[0]);
         int r=_min(i-1,points[1]+points[2]);
         S[i]=(Stemp[r]-Stemp[l-1]+MOD)%MOD;
     }
}

void calcS(vector<int>& points,vector<string>& des,int i){
      int j;
       memset(S,0,sizeof(S));
       if(pnum[i]==0) S[0]=1;
       else if(pnum[i]==1){
          for(j=1;j<=toppoint[i];j++)
             S[j]=1;
       }
       else if(pnum[i]==2){
            if(des[i][0]=='Y'&&des[i][1]=='Y'){
               calcS2(i,points,0,1);
            }
            else if(des[i][0]=='Y'&&des[i][2]=='Y'){
               calcS2(i,points,0,2);
            }
            else{
               calcS2(i,points,1,2);
            }
       }
       else{
            calcS3(i,points);
       }
}


class SRMIntermissionPhase
{
public:
	int countWays(vector <int> points, vector <string> description)
	{
		int n=description.size();
		int i,j,P;
		int sum=0;
		for(i=0;i<n;i++){
            pnum[i]=0;
            sum=0;
            for(j=0;j<3;j++)
              if(description[i][j]=='Y'){pnum[i]++;sum+=points[j];}
            toppoint[i]=sum;
        }
        memset(S,0,sizeof(S));
        memset(Stemp,0,sizeof(Stemp));
        
        //calc S[][]
        for(i=2;i<=points[1]+points[2];i++){
           P=i;
           P--;
           if(P>points[1]){
               Stemp[i]=_min(points[1],points[2]-P+points[1]);
           }
           else{
               Stemp[i]=P;
           }
        }
        for(i=3;i<=points[1]+points[2];i++)
           Stemp[i]=(Stemp[i-1]+Stemp[i])%MOD;
                 
        //calc dp[][]
        memset(dp,0,sizeof(dp));
        memset(pre,0,sizeof(pre));
         calcS(points,description,0);
        for(j=toppoint[0];j>=pnum[0];j--){
          
           dp[0][j]=S[j];
        }
        for(i=1;i<n;i++){
           
           calcS(points,description,i);
           
           pre[MAXN+1]=0;
           for(j=MAXN;j>=0;j--)
              pre[j]=(pre[j+1]+dp[i-1][j])%MOD;
              
           for(j=toppoint[i];j>=pnum[i];j--){
              dp[i][j]=(pre[j+1]*S[j])%MOD;
           }
        }
        long long res=0;
        for(j=toppoint[n-1];j>=pnum[n-1];j--)
           res=(res+dp[n-1][j])%MOD;
        return (int)res;
	}
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值