纪念神九发射作业(2)
昨天在rabbithu的帮助下又做了两道题……不过这debug的过程可真是心酸……
B题:JXOI2018 游戏
这道题它看上去似乎是一道序列问题但它实际上是一道赤裸裸的数学题。
我们 把问题简化一下,就是要求在l~r的所有全排列中,对于任意一个排列进行线性筛,每筛一个数的同时筛掉其所有的倍数,记筛掉一个排列中所有数要用的时间为ti,结果求ti总和mod 1e9+7.
首先思考最关键的问题,如何统计对于任意一个排列,把所有数筛完要多长时间?
在这个区间之内,每筛去一个数都会筛去它的倍数,而在这个区间内没有因子的数字就必定不会被其他数筛去,也就是说,对于给定的区间l~r,必然存在固定的s个数必须要被筛去一次。我们将这s个数称为关键数,那么对于一个排列,最后一个关键数出现的位置即为所要筛完这个区间所用的时间。
如何确定关键数呢?考虑一下线性筛的性质。欧拉筛法每次都用一个数的最大因子和最小质因子筛去它,那么我们发现,除了所有的质数是关键数,任意一个数,如果其最大因子不在这个序列范围内,那么它也是一个关键数。(因为无法被其他数筛去)
这里要特别注意一点,那就是如果区间包括1的话,那么就只剩下1这一个关键数了。(当时没发现WA的我泪流满面,感谢rabbithu debug……)这样我们线性筛一遍就可以确定关键数了。
之后就转化为了一个组合数问题!
我们可以枚举最后一个关键数出现的位置。(当然区间长度小于s可以跳过……)这样的话,如果在最后一个关键数出现之前,区间长为t,那么后面长为n-t的区间就可以任意放入n-s个数,就是(排列符号不会打orz……)A(n-s,n-t)。
但是还没结束,我们在考虑前面区间,这里不能简单的认为再乘以一个t!就可以了,因为这样无法保证最后一个关键数的位置放了一个关键数。那么我们把那一位单独提取,有s中可能,前面t-1位所对应的自然就是(t-1)!了。
把这三个数乘起来再做累加即可,记得要多取模(反正不卡常)。阶乘和组合数率先用逆元处理即可。
上代码。
// luogu-judger-enable-o2 #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<queue> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') using namespace std; const int M = 1e7+5; const int MOD = 1e9+7; typedef long long ll; ll read() { ll ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } ll l,r,n,p[M],ind[M],sum[M],inv[M],ans,s; bool np[M]; void euler() { np[1] = 1;ind[1] = 1; rep(i,2,r)//线性筛 { if(!np[i]) p[++p[0]] = i,ind[i] = 1;//压入素数队列,素数一定是关键数 for(int j = 1;p[j] * i <= r;j++) { np[p[j]*i] = 1;//筛掉一个 if(i < l) ind[p[j]*i] = 1;//记录关键数 if(!(i % p[j])) { break;//保证每个数都被最大因子筛去 } } } } ll ksm(ll a,ll b) { ll q = 1; while(b) { if(b&1) q *= a,q %= MOD; a *= a,a %= MOD; b >>= 1; } return q % MOD; } void init() { sum[0] = 1;inv[0] = 1; rep(i,1,r) sum[i] = sum[i-1] * i % MOD; inv[r] = ksm(sum[r],MOD-2); per(i,r,2) inv[i-1] = inv[i] * i % MOD; rep(i,l,r) if(ind[i]) s++; } ll calc(int s,int t) { return sum[n-s] * inv[t-s] % MOD * s % MOD * sum[t-1] % MOD * t % MOD; } int main() { l = read(),r = read();n = r-l+1; if(l != 1) euler(); else s = 1; init(); rep(i,l+s-1,r) { ll t = i-l+1; ans += calc(s,t); ans %= MOD; } printf("%lld\n",ans); return 0; }
D题 六省联考2017 期末考试
听说这是一道非常水的题……但是我智商太低想不出来……
而且……这个题的debug经历给我的启示就是……没想好之前别瞎写……浪费的时间太多了……而且最后无可奈何只能放弃lowerbound做法,用rabbithu的做法A掉这道题……
好……言归正传。阅读题目之后可以发现,学生的等待时间与顺序无关,老师阅卷时间也与顺序无关(同样的修改操作也不影响时间),最终影响到学生等待和因为修改操作而导致的问题都与最晚发布成绩的时间有关,因此,我们选择枚举最晚发布成绩的时间。
确定这个思路之后,我们就要计算一下对于任意一个时间所产生的不愉快度。如何计算呢?因为我们枚举最晚公布成绩的时间,所以对于任意一个枚举的时间,我们都可以用修改操作把原来晚于这个时间的公布成绩时间修改到这个时间。(这里需要保证没有负面影响,即使a操作代价更大也不行,因为我们后面还会再一次枚举更晚公布成绩的时间)因为我们要求最小值,那么如果a操作(没有负面影响)比b操作(会导致一部分时间推后)的代价还小,那就不用b了,否则的话我们要在这两种操作中选择代价最小的。
还有一个事情……让我debug到头秃……咋子处理等待的时间,以及每次需要修改的操作有多少?原来用了特别特别复杂的方法,可是总是会有一点点差错难以debug出来……最后还是用了rabbithu的方法。每次枚举一个时间,如果这个时间小于当前枚举的时间,那就继续枚举(就相当于一个upperbound-1啊&……可是我为什么错……),之后直接按照题目描述计算就行了……
说到这里感觉心累……上代码吧……
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<queue> #define rep(i,a,n) for(ll i = a;i <= n;i++) #define per(i,n,a) for(ll i = n;i >= a;i--) #define enter putchar('\n') using namespace std; const int M = 150000; typedef unsigned long long ll; ll read() { ll ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } ll a,b1,c,n,m,t[M],b[M],sumt[M],sumb[M],pt,pb; ll k1,k2,k3,k4,tot,maxt,ans = 999999999999999999; void time() { rep(i,1,n) t[i] = read();sort(t+1,t+1+n); rep(i,1,n) sumt[i] = sumt[i-1] + t[i]; } void teach() { rep(i,1,m) b[i] = read();sort(b+1,b+1+m); rep(i,1,m) sumb[i] = sumb[i-1] + b[i]; } int main() { a = read(),b1 = read(),c = read(); n = read(),m = read(); time();teach(); pt = 0,pb = 0; rep(i,1,b[m]) { while(pt < n && t[pt+1] < i) pt++; while(pb < m && b[pb+1] < i) pb++; k1 = pb * i - sumb[pb],k2 = sumb[m] - sumb[pb] - (m-pb) * i; tot = min(k2 * b1, min(k1,k2) * a + (k2 - min(k1,k2)) * b1); tot += (pt * i - sumt[pt]) * c; ans = min(tot,ans); } printf("%lld\n",ans); return 0; }