传送门:
[1]:UPC比赛场
[2]:UPC补题场
F.gu集合(数论)
•题目描述
题目描述、输入、输出1 题目描述: 2 Dew有一个长为n的集合S。 3 有一天,他想选k个不同的元素出来做游戏。 4 但是Dew只有两只手,所以他只能先选出k个元素,然后拿出这k个元素中最小的两个。 5 事实上,Dew更喜欢这k个元素中第二小的那个。 6 因此他会记一个集合T的第二小值为g(T)。 7 此时Dew可以获得c^g(T)!的得分,其中c是一个常数,!表示阶乘。 8 现在你需要求出Dew从集合S中选出k个元素后,他的期望得分对998244353取模的结果。 9 10 输入: 11 输入共两行。 12 第一行三个正整数n,k,c,分别表示集合S的大小,Dew要选的元素个数,和常数c。 13 第二行n个互不相同的正整数ai,表示集合S中的元素。保证集合s升序。 14 15 输出: 16 输出一行一个非负整数,表示Dew的期望得分对998244353取模的结果。样例输入输出1 样例输入 2 5 3 2 3 1 2 3 4 5 4 5 样例输出 6 803628674样例解释
•题解
在tyk的帮助下顺利AC;
下面开始扯淡;
第 i 个数 ai 可以作为第二小的数的条件是:
①存在比 ai 小的数,即 i > 1;
②比 ai 大的数的个数要多于 k-2,即 n-i ≥ k-2;
对于满足条件的 ai,共有
,
那么得到 ai 的概率为:
最终答案就是 ∑ Pi×cai!;
•细节处理
对于除法取模Pi%MOD直接用逆元处理即可,关键是 cai! 该如何处理?
根据费马小定理:ap-1 ≡ 1 (mod p) 可知循环节为 p-1(ap-1 ≡ a0 (mod p) ⇔ 0~p-2 为一个循环) ;
即 ax ≡ ax%(p-1) (mod p) ;
那么 cai! ≡ cai!%(p-1)(mod p);
而 ai!%(p-1) 可以提前预处理出来;
•Code
View Code1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 const int MOD=998244353; 5 const int maxn=5e5+50; 6 7 int n,k,c; 8 int a[maxn]; 9 ll fact[maxn];///fact只开到5e5就足够了,如果开到1e7,返回内存超限 10 ll g[(int)1e7+50]; 11 12 ll qPow(ll a,ll b) 13 { 14 ll ans=1; 15 a %= MOD; 16 while(b) 17 { 18 if(b&1) 19 ans=ans*a%MOD; 20 21 a=a*a%MOD; 22 b >>= 1; 23 } 24 return ans; 25 } 26 int Solve() 27 { 28 fact[0]=g[0]=1; 29 for(int i=1;i < maxn;++i) 30 fact[i]=i*fact[i-1]%MOD; 31 for(int i=1;i <= (int)1e7;++i) 32 g[i]=i*g[i-1]%(MOD-1); 33 34 ll ans=0; 35 for(int i=2;i <= n;++i) 36 { 37 if(n-i < k-2) 38 break; 39 40 ll b=fact[k-2]*fact[n-i-k+2]%MOD*fact[n]%MOD;///概率的分母取模后的结果 41 ///qPow(b,MOD-2)为逆元 42 ll p=(i-1)*fact[n-i]%MOD*fact[k]%MOD*fact[n-k]%MOD*qPow(b,MOD-2)%MOD; 43 44 ans += p*qPow(c,g[a[i]]); 45 ans %= MOD; 46 } 47 return ans; 48 } 49 int main() 50 { 51 scanf("%d%d%d",&n,&k,&c); 52 for(int i=1;i <= n;++i) 53 scanf("%d",a+i); 54 55 printf("%d ",Solve()); 56 57 return 0; 58 }
•感悟
满满的数论知识,有些知识点早就学过,但就学死了,没能做到活用;
看来,还是得多做题,多总结,要活学活用;
H.奇迹(暴力+欧拉筛法)
•题目描述
题目描述、输入、输出题目描述 相信奇迹的人,本身就和奇迹一样了不起。——笛亚 《星游记》 我们称一个日期为一个八位数,第1~4位构成年,第5~6位构成月,第7~8位构成日,不足位数用0补足。 同时,要求日期所代表的这一天真实存在,且年的范围为1~9999。 出现奇迹的日期都存在相同的特点:由“日”组成的两位数,由“月+日”组成的四位数,由“年+月+日”组成的八位数均为质数。 但并不是所有存在这样特点的日期都一定会出现奇迹。 现在,你得到了一个可能会出现奇迹的日期,然而不幸的是这个日期却是残缺的,八位中可能有若干位无法确定。 你需要知道这个日期有多少种可能,这样你才能做好充足的准备去迎接奇迹的到来。 输入 本题有多组数据。 第一行一个正整数T,表示数据组数。 接下来的T行,每行一个八位字符串。 其中第i位如果为 '-',则表示日期的第i位无法确定,否则表示日期的第i位为字符串中第i位上的数字。 输出 对每组数据,一行一个整数,表示答案。样例输入输出样例输入 2 53-7-3-7 20190629 样例输出 6 0提示53-7-3-7 的 种可能的日期如下: 53070307 53070317 53170307 53370307 53570317 53770307 一共10个测试点,记c为八位字符串中 '-' 的个数。 对前9个测试点,在第i个测试点中保证c=i-1。 对100%的数据保证1≤T≤10。
•题解(暴力+欧拉筛法预处理)
枚举所有可能的日期,并判断①其是否符合字符串 s;②符合正常日期;
在满足上述条件的请款下,并判断其是否符合出现奇迹的要求,如果符合,ans++;
•Code
View Code1 #include<bits/stdc++.h> 2 using namespace std; 3 #define mem(a,b) memset(a,b,sizeof(a)) 4 const int day[2][20]={{0,31,28,31,30,31,30,31,31,30,31,30,31}, 5 {0,31,29,31,30,31,30,31,31,30,31,30,31}/**闰年*/}; 6 char s[100]; 7 8 int prime[10000000]; 9 bool isPrime[99991232]; 10 void Prime() 11 { 12 mem(isPrime,true); 13 int cnt=0; 14 isPrime[1]=false; 15 for(int i=2;i < 99991232;++i) 16 { 17 if(isPrime[i]) 18 prime[++cnt]=i; 19 for(int j=1;j <= cnt && prime[j]*i < 99991232;++j) 20 { 21 isPrime[i*prime[j]]=false; 22 if(i%prime[j] == 0) 23 break; 24 } 25 } 26 } 27 bool isRun(int y) 28 { 29 return (y%100 != 0 && y%4 == 0)||(y%400 == 0); 30 } 31 bool isSatY(int y) 32 { 33 if(s[1] != '-' && (s[1]-'0') != y/1000) 34 return false; 35 if(s[2] != '-' && (s[2]-'0') != y/100%10) 36 return false; 37 if(s[3] != '-' && (s[3]-'0') != y/10%10) 38 return false; 39 if(s[4] != '-' && (s[4]-'0') != y%10) 40 return false; 41 42 return true; 43 } 44 bool isSatM(int m) 45 { 46 if(s[5] != '-' && (s[5]-'0') != m/10) 47 return false; 48 if(s[6] != '-' && (s[6]-'0') != m%10) 49 return false; 50 51 return true; 52 } 53 bool isSatD(int d) 54 { 55 if(s[7] != '-' && (s[7]-'0') != d/10) 56 return false; 57 if(s[8] != '-' && (s[8]-'0') != d%10) 58 return false; 59 60 return true; 61 } 62 int Solve() 63 { 64 int ans=0; 65 for(int y=1;y <= 9999;y++) 66 { 67 if(!isSatY(y))///判断y是否符合s 68 continue; 69 70 for(int m=1;m <= 12;m++) 71 { 72 if(!isSatM(m))///判断m是否符合s 73 continue; 74 75 for(int d=1;d <= day[isRun(y)][m];d++)///判断m是否符合s 76 if(isSatD(d) && isPrime[m*100+d] && isPrime[d] && isPrime[y*10000+m*100+d]) 77 ans++; 78 } 79 } 80 return ans; 81 } 82 int main() 83 { 84 Prime();///预处理1~99991232内的所有素数 85 86 int T; 87 scanf("%d",&T); 88 while(T--) 89 { 90 scanf("%s",s+1); 91 printf("%d ",Solve()); 92 } 93 return 0; 94 }
•思考
上述代码耗时:
如果将三重循环的顺序颠倒以下,改成如下顺序:
View Code1 int Solve() 2 { 3 int ans=0; 4 for(int d=1;d <= 31;++d) 5 { 6 if(!isPrime[d] || !isSatD(d)) 7 continue; 8 for(int m=1;m <= 12;++m) 9 { 10 if(!isPrime[d+m*100] || !isSatM(m)) 11 continue; 12 for(int y=1;y <= 9999;++y) 13 { 14 if(!isPrime[y*10000+m*100+d] || d > day[isRun(y)][m] || !isSatY(y)) 15 continue; 16 ans++; 17 } 18 } 19 } 20 return ans; 21 }耗时:
快了将近三百毫秒!!!
对于此题而言,颠倒一下顺序,枚举的情况会少一下;
•坑
起初,我用vector<>存储的prime,返回 TLE,改成 int 后,AC;
看来,动态分配空间还是比提前计算出所需的空间慢一些;