Bestcoder Round #85
A:问一个长度为n小于等于100000的序列是否存在能整除m的连续子段。
前缀和之后,$ S[l,r] = S(r) - S(l-1) $ 取余m后只要查询在S里是否存在出现两次的数值即可。
注意事项:由于是多组数据的题目,一定要把上一组的数字读完,而不是得出了答案直接break!!!!!!
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 5 #define N 100010 6 7 using namespace std; 8 9 int n,m,cnt[N],S[N]; 10 11 int main(){ 12 int T; 13 scanf("%d",&T); 14 while(T--){ 15 memset(cnt,0,sizeof(cnt)); 16 scanf("%d%d",&n,&m); 17 cnt[S[0]=0]++; 18 for(int i=1,x;i<=n;i++){ 19 scanf("%d",&x); 20 S[i]=(S[i-1]+x)%m; 21 cnt[S[i]]++; 22 } 23 bool flag=0; 24 for(int i=0;i<5000;i++) 25 if(cnt[i]>=2) flag=1; 26 if(!flag) puts("NO"); 27 else puts("YES"); 28 } 29 return 0; 30 }
B:
Little White plays a game.There are n pieces of dominoes on the table in a row. He can choose a domino which hasn't fall down for at most k times, let it fall to the left or right. When a domino is toppled, it will knock down the erect domino. On the assumption that all of the tiles are fallen in the end, he can set the height of all dominoes, but he wants to minimize the sum of all dominoes height. The height of every domino is an integer and at least 1.
认真分析此题目:假设我们推动了一个骨牌,后面的骨牌连续倒下去,那么显然对于第一个骨牌和中间的骨牌,我们要取 (它和下一个骨牌的dist) +1作为其高度,而对于最后一个骨牌我们让其高度为1 。
所以我们耗费一次推动的机会实际上是将第i个骨牌 dist(i,i+1)+1 的高度改为了 1,答案减小了dist(i,i+1)。
得到解法:对dist(i,i+1) 从小到大排序,取后 K-1 dist值,将其删去。
$ Ans = \sum{dist(i,i+1) (未被删去的dist)} + n $
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 6 #define N 100010 7 #define LL long long 8 9 using namespace std; 10 11 int n,K; 12 int a[N]; 13 14 int main(){ 15 int T; 16 cin>>T; 17 while(T--){ 18 cin>>n>>K; 19 LL ans=(LL)n; 20 n--; K--; 21 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 22 sort(a+1,a+n+1); 23 for(int i=1;i<=n-K;i++) ans+=(LL)a[i]; 24 cout << ans << endl; 25 } 26 return 0; 27 }
。。。
C:
1.与其求 $ y $,不如求 $ \sqrt{y} $,这样原先的 $ 10^{18} $ 就变为了 $ 10^9 $,这样就能够暴力质因数分解,判定一个数字是否满足条件了。
2.素数定理可知 $ \pi(x) - \pi(x-1) \approx O(lnx) $
然后暴力求解。
注意事项:
1.在调用 $ sqrt(n) $ 时,含有较大的误差,所以取 $ sqrt(n)+0.5 $
2.在执行 $ if(A() || B()) $ 时如果 $ A() = true $,那么就无法执行$ B() $了,具体表现在代码的 48,49 行。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 6 #define LL long long 7 #define sqr(x) ((x)*(x)) 8 9 using namespace std; 10 11 LL n,ans; 12 13 LL min(LL a,LL b){ 14 if(a<b) return a; 15 return b; 16 } 17 18 LL Abs(LL x){ 19 if(x<0) return -x; 20 return x; 21 } 22 23 bool check(LL x){ 24 if(x<2) return 0; 25 LL nt=x; 26 for(LL i=2;i*i<=nt;i++) 27 if(x%i==0){ 28 x/=i; 29 if(x%i==0) return 0; 30 } 31 ans = min(ans, Abs(n-sqr(nt))); 32 return 1; 33 } 34 35 int main(){ 36 int T; 37 cin>>T; 38 while(T--){ 39 cin>>n; 40 ans = 0x3f3f3f3f3f3f3f3fLL; 41 LL m = (LL)(sqrt(n)+0.5); 42 if(check(m)){ 43 cout << Abs(n-sqr(m)) << endl; 44 continue; 45 } 46 bool flag=0; 47 for(LL t=1;!flag;t++){ 48 if(check(m+t)) flag=1; 49 if(check(m-t)) flag=1; 50 if(flag){ 51 cout << ans << endl; 52 break; 53 } 54 } 55 } 56 return 0; 57 }
。。。
D:
本套题目中的压轴题,和之前做过的多校联盟,求二分图个数的题目很像。
但是由于引入了最短路,还是真的很难想呀。
分析此题目:如果不存在长度为K的最短路,那么根据最短路算法可知一定不存在长度大于K的最短路,原题转化为求1到任意点最短路小于K的无向图的个数。
比较新奇的状态:
f(i,j,k) 表示有 i 个点和 1相连(类比多校),各个点到1最长的最短路为 j ,有k个点到1的距离为j的方案数。
为什么要有第三维状态呢?
因为根据最短路算法的流程,我们可以知道任意的 dist(i) = K $ ,都是由一个 dist(j) = K-1 转移来的,而如果我们只留下在从 1 出发的最短路上面的边,那么原图就变为了一个以dis(i) 为层编号的分层图。
深入发掘其性质,对于所有的无向图的边可以如下分类:
1.第i层连向第i-1层的边
2.第i层之内随意连接的边
而要满足相应的最短路情况,还要:
对于任意的第i层的点,必然要至少连向第i-1层一条边。
所以可以考虑dp转移了
$ f(i+t,j+1,t) = \sum f(i,j,k) * C_{n-i}^t * (2^k - 1)^t * 2^{\frac{t(t-1)}{2}} $
初始状态为:
f(1,0,1) = 1
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4
5 #define P 1000000007
6 #define LL long long
7 #define N 71
8
9 using namespace std;
10
11 int n,K;
12 int f[N][N][N],C[N][N];
13
14 int add(int a,int b){
15 if(a+b>=P) return a+b-P;
16 return a+b;
17 }
18
19 int mul(int a,int b){
20 return a*(LL)b%(LL)P;
21 }
22
23 int qpow(int x,int n){
24 int ans=1;
25 for(;n;n>>=1,x=mul(x,x)) if(n&1) ans=mul(ans,x);
26 return ans;
27 }
28
29 int S(int t){
30 if(t==0) return 1;
31 return qpow(2, (t*(t-1))/2);
32 }
33
34 int main(){
35 memset(f,0,sizeof(f));
36 int T,tmp;
37 cin>>T;
38 C[0][0]=1;
39 for(int i=1;i<N;i++){
40 C[i][0]=1;
41 for(int j=1;j<=i;j++){
42 C[i][j] = add(C[i-1][j-1],C[i-1][j]);
43 }
44 }
45 while(T--){
46 memset(f,0,sizeof(f));
47 cin>>n>>K;
48 int ans=0;
49 f[1][0][1]=1;
50 for(int i=1,j,k,t;i<=n;i++)
51 for(j=0;j<min(i,K);j++)
52 for(k=1;k<=i;k++){
53 if(!f[i][j][k]) continue;
54 ans = add(ans, mul(f[i][j][k],S(n-i)) );
55 for(t=1;t<=n-i;t++){
56 tmp = mul(f[i][j][k],C[n-i][t]);
57 tmp = mul(tmp, qpow( (qpow(2,k)-1LL+(LL)P)%P, t ) );
58 tmp = mul(tmp, S(t));
59 f[i+t][j+1][t] = add(f[i+t][j+1][t], tmp);
60 }
61 }
62 cout << ans << endl;
63 }
64 }
。。。。
非常简单的题目,我们来看加强版
$ \sum_{a=1}^n {\sum_{b=1}^m {(x^a-1,x^b-1)}} $
首先有:$ (x^a-1,x^b-1) = x^{(a,b)}-1 $
然后,原式变为:
$ \sum_{a=1}^n {\sum_{b=1}^m {x^{(a,b)}}}-nm $
而
$ \sum_{a=1}^n {\sum_{b=1}^m {x^{(a,b)}}}$ = $\sum_{d=1}^n{\sum_{a=1}^{[\frac{n}{d}]}\sum_{b=1}^{[\frac{m}{d}]}[(a,b)=1]}$ =
$ \sum_{d=1}^n{ x^d \cdot \sum_{i=1}^{[\frac{n}{d}]}{u(i) \cdot [\frac{n}{id}] \cdot [\frac{m}{id}]}} $ =
记 $T = id$
原式 = $ \sum_{T=1}^{n}{[\frac{n}{T}] \cdot [\frac{m}{T}] \cdot \sum_{d|T}}{ x^d \cdot u(\frac{T}{d})} $
后面的式子可以由狄里克莱卷积证明是一个积性函数,从而我们可以用线性筛预处理。
在查询时要做到 $O(\sqrt n)$ 还需要应用下技巧,具体看代码。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 5 #define N 1000010 6 #define LL long long 7 #define P 1000000007LL 8 9 using namespace std; 10 11 LL x,f[N],phi[N]; 12 bool v[N]; 13 14 LL qpow(LL x,LL n){ 15 LL ans=1; 16 for(;n;n>>=1,x=x*x%P) 17 if(n&1) ans=ans*x%P; 18 return ans; 19 } 20 21 void prepare(){ 22 for(int i=1;i<N;i++) phi[i]=i; 23 for(int i=2;i<N;i++){ 24 if(v[i]) continue; 25 phi[i]=i-1; 26 for(int j=i+i;j<N;j+=i){ 27 v[j]=1; 28 phi[j]-=phi[j]/i; 29 } 30 } 31 for(int i=1;i<N;i++) f[i]=(f[i-1]+phi[i])%P; 32 } 33 34 int n; 35 36 int gcd(int a,int b){ 37 if(!a) return b; 38 if(!b) return a; 39 return gcd(b,a%b); 40 } 41 42 LL calc(int x,int n){ 43 if(n==0) return 0LL; 44 if(x==1) return (LL)n; 45 LL ans=qpow(x,n)-1LL; 46 ans = (ans%P+P)%P; 47 ans = ans*qpow(x-1,P-2)%P; 48 ans = ans*x%P; 49 // printf("%d^%d = %I64d\n",x,n,ans); 50 return ans; 51 } 52 53 void work(){ 54 scanf("%d%d",&x,&n); 55 LL ans=0; 56 int t; 57 for(int i=1;i<=n;i=t+1){ 58 t=n/(n/i); 59 ans = ans + (calc(x,t)-calc(x,i-1))*f[n/i]%P; 60 ans = (ans%P+P)%P; 61 } 62 ans=ans*2%P; 63 ans -= calc(x,n); 64 ans -= n*(LL)n%P; 65 cout<< (ans%P + P)%P <<endl; 66 } 67 68 int main(){ 69 prepare(); 70 int T; 71 scanf("%d",&T); 72 while(T--) work(); 73 return 0; 74 }