20201005模拟
*前置知识补充(一)巴什博奕(Bash Game):
只有一堆石子,这一堆里有$n$个石子,两个人轮流从这堆物品中取物,规定每次最少取一个,最多取$m$个,取走最后一个石子的人获胜。
显然,如果$n$=$m$+1,那么由于一次最多只能取$m$个,所以无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜。由此推的取胜法则:如果$n$=($m$+1)$k$+$mod$($k$为任意自然数,$mod$不大于$m$),那么先取者要拿走$mod$个石子,如果后取者拿走$x$个,那么先取者再拿走$m+1-x$个,每次都使剩下的石子为$m+1$的倍数,最后就能取胜
**结论:$n$=$k$*($m+1$)则后者取胜,否则先手取胜
T1:结论题+优化,正解$O$($N$$log^2$$N$),期望$O$($N^2$)50,实际50
$n$堆石子,每堆$a_i$个石子,两个人取至少取1个,最多取$k$个,问先手胜还是后手胜
设$t$=($a_1$$mod({k+1})$)^($a_2$$mod({k+1})$)^($a_3$$mod({k+1})$)^……^($a_n$$mod({k+1})$),如果$t$=0那么先手必胜,否则后手必胜,信息学奥赛一本通446页有证明,先记结论好了。
优化看不懂……$n^2$代码:
1 #include <iostream>
2 #include <algorithm>
3 #include <cstdio>
4 using namespace std;
5 const int maxn=5e5+10;
6 int n,a[maxn],b[maxn];
7 int main(){
8 freopen("stone.in","r",stdin);
9 freopen("stone.out","w",stdout);
10 scanf ("%d",&n);
11 for (int i = 1;i <= n;i++) scanf ("%d",&a[i]);
12 for (int i = 1;i <= n;i++){
13 int t=a[1]%(i+1);
14 for (int j = 1;j <= n;j++){
15 b[j]=a[j]%(i+1);
16 if (j>=2) t=t^b[j];
17 }
18 if (t==0) printf("Bob");
19 else printf("Alice");
20 if (i!=n) printf(" ");
21 }
22 return 0;
23 }
T2:正解:线段树上二分(这啥啊,不懂,二分的题就一直没懂过)期望得分[0,100]不等,实际得分30
四个优先队列维护:消失的鱼,被吃掉的鱼(这个其实可以不用优先队列),能被吃的鱼,不能被吃的鱼,但是以后可能可以被吃。
因为我现在的鱼和消失的鱼都是排好序的,所以我们每次看两个堆的堆顶元素是否相同,就可以知道鱼消没消失,然后我们每次还需要在之前不能被吃的鱼里找到现在可以被吃的鱼压进能被吃的鱼里,和合并果子,蚯蚓,这一类题的想法很相近。
代码,又不是正解:
1 #include <iostream>
2 #include <cstdio>
3 #include <algorithm>
4 #include <queue>
5 #define ll long long
6 using namespace std;
7 priority_queue<ll> fish;
8 priority_queue<ll> dispear;
9 priority_queue<ll> ag;
10 priority_queue<ll> minu;
11 int n,q,type;
12 ll w,s,k;
13 int main(){
14 freopen("fish.in","r",stdin);
15 freopen("fish.out","w",stdout);
16 scanf ("%d",&n);
17 for (int i = 1;i <= n;i++){
18 scanf ("%lld",&w); fish.push(w);
19 }
20 scanf ("%d",&q);
21 while (q--){
22 scanf ("%d",&type);
23 if (type==3){
24 scanf("%lld",&w); dispear.push(w);
25 }
26 if (type==2){
27 scanf("%lld",&w); fish.push(w);
28 }
29 if (type==1){
30 scanf ("%lld%lld",&s,&k);
31 int ans=0;
32 bool flag=true;
33 while (s<k){
34 ll dis=-1,eat;
35 if (!dispear.empty()) dis=dispear.top();
36 int line=ag.size();
37 while (!ag.empty()){
38 ll e=-ag.top();
39 if (e>=s) break;
40 ag.pop(),fish.push(e);
41 }
42 if (fish.empty()){flag=false;break;}
43 eat=fish.top();
44 if (eat==dis){fish.pop(),dispear.pop();continue;}
45 if (s<=eat){fish.pop(),ag.push(-eat);continue;}
46 s+=eat; ans++;
47 fish.pop(); minu.push(eat);
48 }
49 if (!flag){cout<<-1<<endl;}
50 else printf("%d\n",ans);
51 while (!minu.empty()){
52 ll eat=minu.top();
53 fish.push(eat),minu.pop();
54 }
55 while(!ag.empty()){
56 ll eat=-ag.top();
57 fish.push(eat),ag.pop();
58 }
59 }
60 }
61 return 0;
62 }
T3:今天唯一一道可以写正解的题,虽然考场上也没写出来,$O$($n^2$),$n$=999,期望得分40,实际得分40
暴力很明显了,也不知道题干说的最优策略是啥,难道不就是全比一遍么???
正解:因为最后合法的能对答案产生贡献的$x$和$y$的范围为[0,999],所以我们枚举两者,计算在询问范围内的倍数的数量即可。
100分代码:
1 #include <iostream>
2 #include <cstdio>
3 #include <algorithm>
4 #define ll long long
5 using namespace std;
6 const int mod=1e9+7;
7 int gcd(int a,int b){
8 if (a%b==0) return b;
9 gcd(b,a%b);
10 }
11 ll a,b,c,d;
12 int main(){
13 scanf ("%lld%lld%lld%lld",&a,&b,&c,&d);
14 ll ans=0;
15 for (int i = 1;i <= 999;i++){
16 for (int j = 1;j <= 999;j++){
17 if (gcd(i,j)==1){
18 ll l=max((a-1)/i+1,(c-1)/j+1),r=min(b/i,d/j);
19 ans=(ans+max((ll)(0),r-l+1)*(i+j))%mod;
20 }
21 }
22 }
23 printf("%lld\n",ans);
24 return 0;
25 }