2019牛客暑期多校训练营(第九场)
// 今天真是自闭的一场。 // 上来我就先看A题,数学题,题意很明确,斐波那契数的幂求和,项数很大,应该是打表找规律 // 打了表看不出规律。。。看榜,没人过 // Yu今天不舒服上洗手间回来了,准备做I题 // Wu也在看I题,我瞟了两眼,感觉可以把 k=1~N的二进制位个数分别算出来,每一位单独计算然后结果求和 // Wu继续打表找规律,我开始写数位dp,写了一半不会写,手算验证一下发现不对 // Yu提醒没把握不写了,让我看B,我推了半天发现就是要解两个一元二次方程,但是常数c+k*p不确定,暴力需要O(p)怎么都要T // 想着 b^2-4*(c+k*p)要能开方,搜勾股数,没有找到确定斜边得到两直角边的公式 // 方程里有x^2%p 感觉要用到二次剩余的知识,实在不会,换题 // Wu去写D,不知不觉莽了一发,我一看,2^36绝壁T了,让他对sum剪枝又莽一发 // 我感觉我能写,搜一半存下来再搜一半,写完有bug,让Yu调,他状态不好没调出来 // Wu这边还在莽,各种优化各种利用性质,都是徒劳 // 我debug不出来,换E,一推公式出来了,Wu还在推公式,我直接开始并查集写了 // 样例过了,一交WA,改了ll继续WA,没辙了,验了公式,实在找不到bug // 。。。。。。然后没时间了就结束了 // 比赛结束,发了题解,直接看E,跟我想法完全一致,不解 // 看别人AC代码,看不懂,忽然看到main函数有跳过的操作 // 想起原来有重复连边的操作,改了一行,AC了。。。 // 不把D AC不吃饭,在dfs2上面调啊调,发现now值一直都没变,原来sta变负数了 // 左移改成1LL<<k,还是无输出。一直调啊调,发现bag里面怎么搜不出东西来 // 原来dfs1存反了,bag[val] = state,写反了找你妈啊卡了我那么久 // 借鉴了别人的思路,找前一半存下来,一改,A了。。。 // F**k
A - The power of Fibonacci
题意:
求 ∑(1, n) f[i]^m mod p,其中 f[i] 为斐波那契数,p = 1000000000,n <= 1000000000,m <= 1000。
题解:
找规律是不可找出来的。
p太大,循环节也会很长,存不下来。
注意到 p = 2^9 * 5^9,可以先求 sum mod 2^9 与 sum mod 5^9 ,这两部分斐波那契数的循环节分别为 768 与 7812500,可以预处理得到,并暴力求 f[i]^m 的和。
得到同余方程组
sum = a1 mod 2^9
sum = a2 mod 5^9
由中国剩余定理可求出 sum % p
AC代码:
#include<iostream> #include<cstdio> using namespace std; // mod = 512*1953125 typedef long long ll; const int mod1 = 512; const int mod2 = 1953125; const int mod = 1000000000; const int len1 = 768; const int len2 = 7812500; ll f2[len1+10]; ll f5[len2+10]; ll f[len2+10]; ll n, m; void init() { f2[1] = f5[1] = f[1] = 1; for(int i=2;i<=len1;i++) f2[i] = (f2[i-1] + f2[i-2]) % mod1; for(int i=2;i<=len2;i++) { f5[i] = (f5[i-1] + f5[i-2]) % mod2; f[i] = (f[i-1] + f[i-2]) % mod; } } ll myPow(ll a, ll n, ll p) { // if(a==0) return 0; ll res = 1; while(n) { if(n&1) res = res * a % p; a = a * a % p; n >>= 1; } return res; } ll sum(ll f[], ll k, ll mod) { ll res = 0; for(int i=1;i<=k;i++) { res = (res + myPow(f[i], m, mod)) % mod; } return res; } void exgcd(ll a, ll b, ll &x, ll &y) { if(b==0){ x=1; y=0; return;} exgcd(b, a%b, x, y); ll tp = x; x = y; y = tp - a/b*y; } ll a[2], b[2] = {mod1, mod2}; ll china() { ll ans = 0, x, y; ll lcm = mod; for(int i=0;i<=1;++i) { ll tp = lcm/b[i]; exgcd(tp, b[i], x, y); x = (x%b[i] + b[i]) % b[i]; ans = (ans + tp*x*a[i]) % lcm; } return (ans + lcm) % lcm; } int main() { init(); cin>>n>>m; if(n<len2) { printf("%lld\n", sum(f, n, mod)); } else { a[0] = n/len1 %mod1* sum(f2, len1, mod1)%mod1 + sum(f2, n%len1, mod1); a[0] %= mod1; a[1] = n/len2* sum(f5, len2, mod2)%mod2 + sum(f5, n%len2, mod2); a[1] %= mod2; // printf("%lld %lld\n", a[0], a[1]); printf("%lld\n", china()); } return 0; }
B - Quadratic equation
题意:
已知b,c,p,求两个整数 x,y 满足 x + y mod p = b,x * y mod p = c 。( p = 1000000007)
题解:
已知 x + y,设法求得 x - y 就能得到 x,y。
又 (x - y)^2 = (x + y)^2 - 2xy = b^2 - 4c ( mod p ),求出模p下的二次剩余方程即可。
令 d = b^2 - 4c,参考博客,p = 1e9+7 时,属于奇素数下的第一种情况,方程的解为 d^[(p+1)/4]。
WA点:
求出了 x^2 = a mod p 的根以后,即为 x - y 的差,x = ( b + x_y) / 2,但不能用除法。2的逆元为 500000004 = (p+1)/2 。
AC代码:
#include<iostream> #include<cstdio> using namespace std; const int p = 1e9+7; typedef long long ll; ll myPow(ll a, ll b) { ll res = 1; while(b) { if(b&1) res = res * a % p; a = a * a % p; b >>= 1; } return res; } int main() { int t; cin>>t; while(t--) { ll sum, c; scanf("%lld %lld", &sum, &c); ll d = ((sum*sum-4*c) % p + p) % p; if(d==0) { printf("%lld %lld\n", sum/2, sum/2); continue; } ll x_y = myPow(d, (p+1)/4); if(x_y*x_y%p != d) { puts("-1 -1"); continue; } ll x = (sum + x_y)*(p+1 >>1) % p; // (a/2 %p ==> a * 2^-1 % p ==> a*(p+1)/2) ll y = (sum - x + p) % p; if(x>y) swap(x, y); printf("%lld %lld\n", x, y); } return 0; }
D - Knapsack Cryptosystem
题意:
有一种背包加密算法,给长度为 n 的数组和它的某个子集的和,那么密码为长度为 n 的01串,即1代表子集的元素在原数组中,0代表不在。输出该序列。
题解:
就是求 n 个数中的哪些数和为给定的 s。直接暴搜 O(2^n),n<=36肯定TLE。
折半枚举。很容易想到枚举一半,再dfs另一半验证 sum - sum1是否在记录中,有的话可以直接输出答案。
时间复杂度:在map上的插入与查询都是logN,这里N = 2^(n/2),故两次都是O(2^(n/2)*log(2^(n/2))),最终时间复杂度为O(n*2^(n/2))
我真是傻逼把map键-值插反了。。。
AC代码:
#include<iostream> #include<cstdio> #include<map> using namespace std; typedef long long ll; ll a[40], sum; int n; map<ll, ll> bag; bool flag; void dfs1(int k, ll now, ll sta) { if(k>=n/2) { bag[now] = sta; return; } dfs1(k+1, now, sta); dfs1(k+1, now+a[k], sta|(1LL<<k)); } void dfs2(int k, ll now, ll sta) { if(k>n) { map<ll, ll>::iterator it = bag.find(sum-now); if(it!=bag.end()) { flag = true; ll sta1 = it->second; for(int i=0;i<n;i++) { if((sta1>>i)&1 || (sta>>i)&1) putchar('1'); else putchar('0'); } } return; } if(!flag) dfs2(k+1, now, sta); if(!flag) dfs2(k+1, now+a[k], sta|(1LL<<k)); } int main() { cin>>n>>sum; for(int i=0;i<n;i++) { cin>>a[i]; } // 处理前一半,保存部分和与状态 dfs1(0, 0, 0); // 搜索后一半 dfs2(n/2, 0, 0); return 0; }
E - All men are brothers
题意:
开始有 n 个人互相不认识,每一轮让 x,y 两个人认识成为朋友,则他们和他们的朋友都互相认识。给 m 次操作,输出 m + 1 行表示从最开始及每次操作后,这群人中找到 4 个互相不认识的组合有多少种。
题解:
一看肯定需要并查集,手推一下,发现记录人群中 2 个互相不认识的组合 C2,每次操作更新相应值即可。
设现在合并的两群人的集合大小分别为 mm = sz[Find(x)],nn = sz[Find(y)],剩下的人为 k = n - mm - nn,用 C2 - mm*nn 更新C2;
4 个人互相不认识的组合 C4 = 上一次C4结果 - 新合并的两组人选两个和剩下人中选两个的情况 = C4 - ( mm * nn* (C2 - mm*k - nn*k ) 。
AC代码:
#include<iostream> #include<cstdio> using namespace std; typedef long long ll; int fa[100010], sz[100010]; int Find(int x) { return x==fa[x]?x:Find(fa[x]); } bool Union(int x, int y) { int a = Find(x), b = Find(y); if(a==b) return false; if(sz[b]>sz[a]) { fa[a] = b; sz[b] += sz[a]; } else { fa[b] = a; sz[a] += sz[b]; } return true; } int main() { int n, m; cin>>n>>m; for(int i=0;i<=n;i++) sz[i] = 1, fa[i] = i; ll C4 = 1LL*n*(n-1)*(n-2)/3/4*(n-3)/2, C2 = 1LL*n*(n-1)/2; printf("%lld\n", C4); bool flag = 0; while(m--) { int x, y; scanf("%d %d", &x, &y); int mm = sz[Find(x)]; int nn = sz[Find(y)]; if(Union(x, y)) { // 没判断是否为同一组,WA到自闭 C2 -= 1LL*mm*nn; ll tmp = C2 - 1LL*mm*(n-mm-nn) - 1LL*nn*(n-mm-nn); C4 -= 1LL*mm*nn*tmp; } if(C4==0) flag = true; if(flag) puts("0"); else printf("%lld\n", C4); } return 0; }
(未完待补)