Topcoder SRM 601 div1题解

日常TC计划~

Easy(250pts):

题目大意:有n个篮子,每个篮子有若干个苹果和橘子,先任取一个正整数x,然后从每个篮子中选出x个水果,把nx个水果放在一起,输出一共有多少种不同的组成方案。其中n<=50,每个篮子中每种水果的个数<=1000000,可能有篮子不存在水果。

首先考虑x的大小,由于每个篮子中都要取出x个水果,那么apple[i]+orange[i]>=x,于是我们先将所有的篮子扫一遍,得到x的上届。

接下来我们枚举x,考虑每一个篮子中的情况,

由于最后要求的是方案数,所以我们只需要关心从每个篮子取出多少个苹果和多少个橘子就可以了。

考虑第i个篮子,那么从这个篮子中最多取出min(apple[i],x)个苹果,最少取出x-min(orange[i],x)个苹果。(因为最多取出min(orange[i],x)个橘子)

于是我们将上届与下届的差全部加在一起,然后直接统计答案就可以了。

时间复杂度O(n*w),代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 class WinterAndPresents
 4 {
 5     public:
 6     long long getNumber(vector<int> apple,vector<int> orange)
 7     {
 8         int n=apple.size();
 9         int mn=2000000;
10         for (int i=0;i<n;i++) mn=min(mn,apple[i]+orange[i]);
11         long long ans=0;
12         for (int k=1;k<=mn;k++)
13         {
14             int cnt1=0,cnt2=0;
15 //cnt1 means the least of apples should be taken
16 //cnt2 means the most of apples should be taken
17             for (int i=0;i<n;i++)
18             {
19                 cnt1+=k-min(k,orange[i]);
20                 cnt2+=min(k,apple[i]);
21             }
22             ans+=cnt2-cnt1+1;
23         }
24         return ans;
25     }
26 };

Medium(500pts):

题目大意:给定两个正整数N,M,现在有两个集合{1,2...N}{1,2...M},对于这两个集合分别选择一个子集(可以为空),使得这两个子集没有交集并且前一个子集所有数XOR的值小于后一个子集所有数XOR的值,其中N,M<=2000。

一眼就知道这应该是一个数位DP吧~~~

我们考虑f[i][j][k]表示当前考虑是否选择数字i,前一个子集的xor值为j,后一个子集的xor值为k,

于是我们得到f[i][j][k]=f[i+1][j^i][k]+f[i+1][j][k^i]+f[i+1][j][k],

这样做的话时间复杂度O(n^3),然而n范围有2000,显然会超时。

我们考虑两个数X和Y,如果X小于Y,那么X一定在某一个二进制位小于Y,而之前几位全部相同。

于是我们发现X XOR Y前几位都是0,某一位为1,

这样我们只需要统计一下X哪一个二进制位比Y小,然后在转移的时候纪录一下A的那一个二进制位就可以了。

设f[i][j][k]表示当前考虑是否选择数字i,目前两个子集的XOR值为j,第一个子集的第i为为k。

于是我们得到f[i][j][k]=f[i+1][j][k]+f[i+1][j^(i>>pos)][k^((i>>pos)&1)]+f[i+1][j^(i>>pos)][k],

时间复杂度O(n^2logn),

感觉时间限制还是很紧,可以选择记忆化搜索,听说是严格O(n^2)的然而我不会证明。

代码如下:

 1 #include <bits/stdc++.h>
 2 #define modp 1000000007
 3 #define Maxn 2057
 4 int f[Maxn][Maxn][2];
 5 bool flag[Maxn][Maxn][2];
 6 using namespace std;
 7 int n,m;
 8 class WinterAndSnowmen
 9 {
10     int tryit(int pos,int Min_num,int now1,int now2)
11     {
12 //Min_num means the min number you can choose
13 //now1 means the xor of the numbers in the set A xor with the xor of the numbers in the set B
14 //now2 means the ith position of the xor of the numbers in the set A
15 //pos means i
16         if (flag[Min_num][now1][now2]) return f[Min_num][now1][now2];
17         flag[Min_num][now1][now2]=true;
18         if (Min_num==0)
19         {
20             if (now1==1&&now2==0) f[Min_num][now1][now2]=1; else f[Min_num][now1][now2]=0;
21             return f[Min_num][now1][now2];
22         }
23         f[Min_num][now1][now2]=tryit(pos,Min_num-1,now1,now2);
24         if (Min_num<=n) f[Min_num][now1][now2]=(f[Min_num][now1][now2]+tryit(pos,Min_num-1,now1^(Min_num>>pos),now2^((Min_num>>pos)%2==1)))%modp;
25         if (Min_num<=m) f[Min_num][now1][now2]=(f[Min_num][now1][now2]+tryit(pos,Min_num-1,now1^(Min_num>>pos),now2))%modp;
26         return f[Min_num][now1][now2];
27     }
28     public:
29     int getNumber (int N,int M)
30     {
31         n=N,m=M;
32         int ans=0;
33         for (int i=0;i<=12;i++)
34         {
35             memset(f,0,sizeof(f));
36             memset(flag,false,sizeof(flag));
37             ans=(ans+tryit(i,max(n,m),0,0))%modp;
38         }
39         return ans;
40     }
41 };

Hard(950pts):

题目大意:给你n个商店信息,每个店告诉你开门时间,红绿蓝的小球个数,每个商店每天只能卖一个小球,数据爆炸同一天最多只有两个商店开门,如果有两个商店,那么这两个商店必须卖同样颜色的小球,求总的方案数。其中n<=50,一个点一种颜色的小球<=100,任何的时间<=500。

也挺显然的是一道DP吧~~~

我们考虑f[i][x1][x2][y1][y2]分别表示出时间i的时候两个商店的红绿小球个数分别为x1,x2,y1,y2,

于是我们可以得出两个商店的蓝球个数,

这样就可以直接大力转移了,

时间复杂度O(T*w^4),怎么算都超时吧。。。

我们来考虑如何用更少的数字来描述一个状态,从题目中我们可以发现一个性质,每一个商店开店的时间一定是连续的一段。

我们可以继续考虑记忆化搜索,我们用三个数字来表示一个状态f[i][x1][x2],

其中i表示时间,x1表示当前的商店红球被删除的个数,x2表示当前的商店绿球被删除的个数。

我们来考虑一下对于当前的商店的定义,

如果当前的时刻t,没有商店开门,那么当前商店定义为空,x1和x2都是0。(其实好像定义成多少都没关系)

如果当前的时刻t,恰好有一个商店开门,那么当前的商店定义为这个商店。

如果当前的时刻t,恰好有两个商店开门,那么定义当前的商店为开门较早的那个商店。

于是我们来考虑一下这个函数,我们写成记忆化搜索的形式,

如果当前没有商店开门,直接将时间从t跳到t+1,将x1和x2变成0;

如果当前恰好有一个商店开门,直接将时间从t跳到当前的商店关门的时间,将x1和x2变成0;

如果当前恰好有两个商店开门,

(1)如果当前的两个商店同时关门,那么这两个商店剩余的红绿蓝球个数应该完全一致,直接乘上三者的组合数,然后将时间跳到当前商店关门时间,将x1和x2变成0;

(2)如果当前的两个商店,开门较早的关门较早,那么开门较早的商店剩余的红蓝绿球个数应该完全小于等于另一个商店的个数,直接乘上三者的组合数,然后将时间跳到当前商店关门时间,并且将当前的商店改变成另一个商店,并将x1和x2变成另一个商店的数据;

(3)如果当前的两个商店,开门较早的关门较晚,那么另一个商店的红蓝绿球个数完全小于等于当前商店剩下来的个数,那么直接处理掉另一个商店,乘上三者的组合数,把时间跳到另一个商店的关门时间,处理x1和x2。(分别减掉另一个商店的个数就可以了)

最后来考虑三者的组合数,我们有a个红球,b个蓝球,c个绿球,不同的排列方式一共有C(a+b+c,a)*C(b+c,b),

于是我们可以先预处理出C数组和时间轴,然后dp记忆化搜索就可以了。

时间复杂度O(Tm^2),代码如下:

  1 #include <bits/stdc++.h>
  2 #define Maxn 57
  3 #define Maxa 107
  4 #define Maxt 507
  5 #define modp 1000000007
  6 using namespace std;
  7 struct data {int l,r,x,y,z;} a[Maxn];
  8 int cover[Maxt][2],combination[3*Maxa+1][3*Maxa+1];
  9 int f[Maxt+1][Maxa][Maxa];
 10 bool flag[Maxt][Maxa][Maxa];
 11 //cover is the data of the two shops in the same day
 12 int n;
 13 class WinterAndShopping
 14 {
 15     int calc(int x,int y,int z)
 16     {
 17         int ans=(1LL*combination[x+y+z][x]*combination[y+z][y])%modp;
 18         return ans;
 19     }
 20     int tryit(int t,int x,int y)
 21     {
 22 //t means the present time
 23 //x means how many red balls have been taken from the shop
 24 //y means how many green balls have been taken from the shop
 25 //if there is only one shop
 26 //we can calculate how many blue balls
 27 //if there is two shops at the same time
 28 //we can also calculate it and skip the time to the next shop
 29         if (flag[t][x][y]) return f[t][x][y];
 30         flag[t][x][y]=true;
 31         int p=cover[t][0],q=cover[t][1];
 32         if (p<0)
 33         {
 34 //there is no shop open at the moment
 35             f[t][x][y]=tryit(t+1,0,0);
 36             return f[t][x][y];
 37         }
 38         int x1=a[p].x-x,y1=a[p].y-y,z1=a[p].z-t+a[p].l+x+y;
 39 //x1 means how many red balls there are in the first shop
 40 //y1 means how many green balls there are in the first shop
 41 //z1 means how many blue balls there are in the first shop
 42         if (q<0)
 43         {
 44 //there is exactly one shop open at the moment
 45             if (t==a[p].r)
 46             {
 47 //we should skip the time directly to the next shop
 48                 f[t][x][y]=tryit(t+1,0,0);
 49                 return f[t][x][y];
 50             } else
 51             {
 52                 if (x1) f[t][x][y]=tryit(t+1,x+1,y);
 53                 if (y1) f[t][x][y]=(f[t][x][y]+tryit(t+1,x,y+1))%modp;
 54                 if (z1) f[t][x][y]=(f[t][x][y]+tryit(t+1,x,y))%modp;
 55                 return f[t][x][y];
 56             }
 57         }
 58 //there is exactly two shops open at the moment
 59         if (a[p].r==a[q].r)
 60         {
 61 //the two shops will be closed at the same time
 62 //then the three kinds of balls should be the same
 63             if (x1==a[q].x&&y1==a[q].y&&z1==a[q].z)
 64             {
 65                 f[t][x][y]=(1LL*tryit(a[p].r+1,0,0)*calc(x1,y1,z1))%modp;
 66             }
 67             return f[t][x][y];
 68         } else if (a[p].r<a[q].r)
 69         {
 70 //the first shop will be closed earlier than the second one
 71 //then the three kinds of balls in the first shop should not be more then the second one
 72             if (x1<=a[q].x&&y1<=a[q].y&&z1<=a[q].z)
 73             {
 74                 f[t][x][y]=(1LL*tryit(a[p].r+1,x1,y1)*calc(x1,y1,z1))%modp;
 75             }
 76             return f[t][x][y];
 77         } else if (a[p].r>a[q].r)
 78         {
 79 //the first shop will be closed later than the second one
 80 //then the three kinds of balls i the first shop should not be less than the second one
 81             if (x1>=a[q].x&&y1>=a[q].y&&z1>=a[q].z)
 82             {
 83                 f[t][x][y]=(1LL*tryit(a[q].r+1,x+a[q].x,y+a[q].y)*calc(a[q].x,a[q].y,a[q].z))%modp;
 84             }
 85             return f[t][x][y];
 86         }
 87     }    
 88     public:
 89     int getNumber(vector<int> first, vector <int> red, vector <int> green, vector <int> blue)
 90     {
 91         n=first.size();
 92 //deal with the data of the combination number
 93         memset(combination,0,sizeof(combination));
 94         combination[0][0]=1;
 95         for (int i=1;i<=3*Maxa;i++)
 96         {
 97             combination[i][0]=1;
 98             for (int j=1;j<=i;j++)
 99                 combination[i][j]=(combination[i-1][j]+combination[i-1][j-1])%modp;
100         }
101 //deal with the data of the time schedule
102         for (int i=0;i<Maxt;i++) cover[i][0]=cover[i][1]=-1;
103         for (int i=0;i<n;i++)
104         {
105             a[i].l=first[i],a[i].x=red[i],a[i].y=green[i],a[i].z=blue[i];
106             a[i].r=a[i].l+a[i].x+a[i].y+a[i].z-1;
107             for (int j=a[i].l;j<=a[i].r;j++)
108             {
109                 if (cover[j][0]==-1) cover[j][0]=i; else
110                 {
111                     cover[j][1]=i;
112                     if (a[i].l<a[cover[j][0]].l) swap(cover[j][0],cover[j][1]);
113                 }
114             }
115         }
116         memset(f,0,sizeof(f));
117         memset(flag,false,sizeof(flag));
118         f[Maxt][0][0]=1,flag[Maxt][0][0]=true;
119         return tryit(1,0,0);
120     }
121 };

打卡完成!

 

转载于:https://www.cnblogs.com/Tommyr7/p/6801390.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值