https://atcoder.jp/contests/abc245/tasks
G - Foreign Friends N个点, 第i个人颜色是Ki
其中一些点是好的点
初始没有边
有m个可选边, 第i个可以花费 Ci 让ui和vi连一条边(无重边自环)
对于每个点, 独立的求最小代价 让它和一个异色好点连通, 或报告不可能
范围 N 1e5
M 1e5
我的思路 既然是每个到异色好点最短距离, 那么路径上一定都是同色或非好点
目前思路是 按一个颜色的个数能不能根号分治
另一个是记录到每个点最小代价不同色的两个最近点
考虑用Floyd+提前退出? 问题是 时间复杂度怎么估计和保证
题解 如果 忽略颜色
多源的好点做dij, 然后 每个点取最小, 然而这个肯定TLE
但如果增加一个点S, 然后它到所有好点距离都是0(单向边!), 那么就可以单源最短路在范围内了
再考虑颜色不同的限制
如果暴力,就是把上面的最短距离多一个维度 [首个好点的颜色], 然而这样也是复杂度超了
注意到, 第三小的颜色的距离永远不对答案有贡献, 所以只用记录最小的两个不同, (这个我倒是想到了)
代码 https://atcoder.jp/contests/abc245/submissions/35054327
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define rep(i,a,n) for (ll i=a;i<(ll)n;i++) const ll INF = 0x3f3f3f3f3f3f3f3f ;template <typename T> using minPQ = priority_queue<T,vector<T>, greater<T>>; ll read () {ll r;scanf ("%lld" ,&r);return r;}int main () { int n = read (); int m = read (); int k = read (); int l = read (); vector<int >a (n); vector<vector<pair<int , ll> > >e (n); vector<int >used (n, 0 ); auto d2 = vector (n,map <int ,ll>()); minPQ<tuple<ll, int , int > >pq; rep (i,0 ,n) a[i] = read (); rep (i,0 ,l) { int u = read () - 1 ; pq.push ({0 ,u,a[u]}); } rep (i,0 ,m) { int u = read ()-1 ; int v = read ()-1 ; int c = read (); e[u].push_back ({v,c}); e[v].push_back ({u,c}); } while (!pq.empty ()) { auto [dis,u,c] = pq.top (); pq.pop (); if (d2[u].count (c) || d2[u].size () == 2 )continue ; d2[u][c] = dis; for (auto [v,len]:e[u]) pq.push ({dis+len, v, c}); } rep (u,0 ,n) { ll dis = INF; for (auto [c,d]:d2[u]) if (c != a[u]) dis=min (dis,d); printf ("%lld%c" ,dis == INF?-1 :dis," \n" [u==n-1 ]); } return 0 ; }
Ex - Product Modulo 2 长$k$的序列, 求满足要求的序列的个数mod 998244353
$A_i\in [0,M-1]$
$\prod_{i=1}^k A_i \equiv N \pmod M$
范围 k 1e9
$0\le N < M \le 10^{12}$
2s
1024mb
我的思路 数数题 还是 数论题
这里没有说M是质数
如果M是质数的话
那么, 如果N = 0 吧,这个特殊, 因为Ai < M , 所以如果全部不为0,则不可能为0, 所以这是其中一些为0,剩下的随便填的方案数, 简单的排列组合问题, 或者所有情况-非零的时候
如果 $N\ne 0$, 那么 f(i,j) 表示前i个 乘积为j的方案数, 则有f(i,j0) = f(i,j1), 因为归纳法, i=1时,所有相等,而i时如果所有相等,那么f(i+1,j) = sum f(i,1..m-1), f(i+1,j) = (m-1)f(i,j), 啊不就很好算, ans[n] = (m-1)^{k-1}
回到一般情况, 如果M不是质数
那用上面的想法,就是找等价类
去计算等价类之间的关系
直接想想不出, 用2x3=6和2x3x5=30来试试
1 2 3 4 5 6 7 8 9 N = 6 a = [1 for i in range (N)] for t in range (20 ): b = [0 for i in range (N)] for i in range (N): for j in range (N): b[(i*j)%N] += a[i] print (b) a = b
输出
1 2 3 4 5 6 7 8 6: [15, 2, 6, 5, 6, 2] [133, 4, 28, 19, 28, 4] [975, 8, 120, 65, 120, 8] 15: [135, 8, 24, 20, 24, 18, 60, 8, 24, 20, 54, 8, 60, 8, 24, 45, 24, 8, 60, 8, 54, 20, 24, 8, 60, 18, 24, 20, 24, 8] [8113, 64, 448, 304, 448, 244, 2128, 64, 448, 304, 1708, 64, 2128, 64, 448, 1159, 448, 64, 2128, 64, 1708, 304, 448, 64, 2128, 244, 448, 304, 448, 64] [359775, 512, 7680, 4160, 7680, 2952, 62400, 512, 7680, 4160, 44280, 512, 62400, 512, 7680, 23985, 7680, 512, 62400, 512, 44280, 4160, 7680, 512, 62400, 2952, 7680, 4160, 7680, 512]
发现: 从一次以后,就可以分类了, 相同的持续相同,不同的持续不同
感觉上是和N的gcd有关, gcd相同的为一组
稍微证明, 前i个达到v,那么v开始得到的i+1的 kv % N, 有 gcd(kv%N,N) = gcd(kv,N), 是gcd(v,N)的倍数
那么归纳法,如果i次时,gcd相同的数的值一样,那么i+1次也显然就一样了
N 1e12 的话, gcd就都是它的约数, 第一轮算个矩阵, 后面矩阵快速幂
2*3*5*7*11*13*17*19*23*29*31*37 = 7420738134810 = 7e12
2^12 = 4096
但似乎 4096^3 log(k) 会超时?
重新考虑下
上面注意到i的值的和N的gcd只会增不会减, 考虑指定一些位置是贡献这些gcd的
剩下的就全是与N互质的数的乘积?
但这样的话, 其实并不可行, 因为比如6, 2 * 2 依然是gcd = 2
而对于12这种, 2 * 2 更难分配出现的位置
题解 $M=p_1^{q_1} p_2^{q_2} p_3^{q_3}\ldots$
对于每一组, 求$0\le a_i \le p^q -1, \prod_{i=1}^k a_i \equiv N \pmod {p^q}$
然后答案乘起来
emmmm, 为啥, 虽然一个数 如果满足每个组的要求,那么一定是一个满足题意的方案, 而每一个满足题意的,也一定满足每组的要求, 但为啥是方案数之乘积呢?
即是要证明
0<= x < p1p2
存在且唯一存在一个x满足
x = k1 mod p1
x = k2 mod p2
证
x1 != x2 但是都满足
有 x1-x2 = 0 mod p1, x1-x2 = 0 mod p2, 所以不可能存在两个不同解
x = (k1 p2 * (p2 在 mod p1 的逆元) + k2 p1 * (p1 在 mod p2 的逆元)) mod (p1p2)
显然是一个值, 且满足上述要求, 则是一个解
对于高次就是 孙子定理(中国剩余定理)
设 F(p,q,K,N) 是上面子问题(p,q) 的方案数
一样,先考虑次放q=1, 得到我那个都相等的结论 (p-1)^{k-1},
q > 1 时
要证明的性质是 可以把 p的幂次相等的一组方案数相等
同样是归纳,i时一样,i+1时也一样
比如 (p=3,q=3) mod 3^3
1 2 3 4 5 6 7 (1,2,4,5,7,8,10,11,13,14,16,17,19,20,22,23,25,26) (3,6,12,15,21,24) (9,18) (0)
证明, 每个和它同组和比它组小的变成它的方法数一致, 得证, (感觉我应该 都得到了gcd有关,这个也可以得到的, 关键是没想到第一步的拆分
// 举例 , 3->12 可以 x4,13,22 , 而12–>3, 是x7,16,25… , (都是3个方案)
因此直接分组, 组之间关系, 就
math.log(10)/math.log(2) * 12 = 39.863137138648355
直接矩阵快速幂即可
然后注意有个坑, 是如果与998244353 有关,运算可能会 除以0, 但幸运的是998244353^2 > 1e12, 所以一定是一次方, 而一次方可以直接表达式
代码(矩阵快速幂) https://atcoder.jp/contests/abc245/submissions/35055072
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <bits/stdc++.h> #include <atcoder/modint> using mint = atcoder::modint998244353;using namespace std;typedef long long ll;#define rep(i,a,n) for (int i=a;i<(int)n;i++) #define per(i,a,n) for (int i=n;i-->(int)a;) ll read () {ll r;scanf ("%lld" ,&r);return r;}int cp (ll &n, ll p) { if (n == 0 ) return 1e9 ; ll e = 0 ; while (n % p == 0 ) { ++e; n/=p; } return e; } vector<pair<ll, ll>> factor (ll n) { vector<pair<ll, ll>> pe; for (ll i = 2 ;i*i<= n; ++i) if (n%i==0 ) pe.push_back ({i, cp (n,i)}); if (n > 1 ) pe.push_back ({n, 1 }); return pe; } vector<vector<mint>> mul (const vector<vector<mint>>&m0, const vector<vector<mint>>&m1){ auto r = vector (m0.size (),vector <mint>(m1[0 ].size (),0 )); rep (i,0 ,m0.size ()) rep (j,0 ,m1[0 ].size ()) rep (k,0 ,m0[0 ].size ()) r[i][j] += m0[i][k] * m1[k][j]; return r; } mint f (ll p, int e, ll k, ll n) { vector<mint> x (e + 1 ) ; x[e] = 1 ; x[e - 1 ] = p - 1 ; per (i,0 ,e-1 ) x[i] = x[i + 1 ] * p; if (e == 1 ) return (n%p == 0 )?(mint (p).pow (k) - mint (p-1 ).pow (k)) : (mint (p-1 ).pow (k-1 )); auto r = vector (1 ,vector <mint>(e+1 ,0 )); r[0 ][0 ] = 1 ; auto t = vector (e+1 ,vector <mint>(e+1 ,0 )); rep (i,0 ,e+1 ) rep (j,0 ,e+1 ) t[j][min (e,i+j)] += x[i]; while (k) { if (k&1 ) r = mul (r, t); t = mul (t,t); k/=2 ; } int c = min (cp (n, p),e); return r[0 ][c] / x[c]; }; int main () { ll k = read (); ll n = read (); ll m = read ();; auto pe = factor (m); mint ans (1 ) ; for (auto [p, e] : pe) ans *= f (p, e, k, n); printf ("%d\n" ,ans.val ()); return 0 ; }
压缩版 https://atcoder.jp/contests/abc245/submissions/35055288
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <bits/stdc++.h> #include <atcoder/modint> using mint=atcoder::modint998244353;using namespace std;using ll=long long ;#define rep(i,a,n)for(ll i=a;i<(ll)n;i++) #define M(a,b) vector(a,vector<mint> (b,0)) ll g (ll&n,ll p) { if (!n)return 64 ; ll e=0 ; while (n%p==0 ){ ++e; n/=p; } return e; } template <class T >T mul (T&a,T&b) { auto r=M (a.size (),b[0 ].size ()); rep (i,0 ,a.size ())rep (j,0 ,b[0 ].size ())rep (k,0 ,b.size ())r[i][j]+=a[i][k]*b[k][j]; return r; } mint f (ll p,ll e,ll k,ll n) { vector<mint>x (e+1 ); x[e]=1 ; x[e-1 ]=p-1 ; for (ll i=e-1 ;i-->0 ;)x[i]=x[i+1 ]*p; if (e==1 )return n%p?mint (p-1 ).pow (k-1 ):mint (p).pow (k)-mint (p-1 ).pow (k); auto r=M (1 ,e+1 ),t=M (e+1 ,e+1 ); r[0 ][0 ]=1 ; rep (i,0 ,e+1 )rep (j,0 ,e+1 )t[j][min (e,i+j)]+=x[i]; while (k){ if (k&1 )r=mul (r,t); t=mul (t,t); k/=2 ; } ll c=min (g (n,p),e); return r[0 ][c]/x[c]; } int main () { ll k,n,m; cin>>k>>n>>m; mint r=1 ; for (ll i=2 ;i*i<=m;++i)if (m%i==0 )r*=f (i,g (m,i),k,n); if (m>1 )r*=f (m,1 ,k,n); printf ("%d\n" ,r.val ()); return 0 ; }
总结 G
第二次遇到 dij 中使用长0的边!!!, 第三大的颜色不会贡献 倒是我自己能想到
Ex
数论 的拆的想法有,但是这之间的计数关系完全不熟
官方答案还有个推组合数的,学不会
虽然这个可以矩阵快速幂,据说还有 BM线性递推 没学过
参考 官方题解
bilibili dls
Berlekamp_Massey 算法
BM