Petrozavodsk Winter-2017. Xiaoxu Guo Contest 5【雜題】
G Matrix Recurrence
給定 \(M_0,B\in\Z_{\text{mod}}^{m\times m}\) 和 \(c_1,\cdots,c_n\in\N\),定義
\[M_i=\left(\prod_{j=c_i}^{i-1}M_j\right)\times B \]求 \(M_n\)。\(n\le 10^6\),\(m\le 5\),\(2\le\text{mod}\le 10^9\),\(c_i<i\),\(c_1\le c_2\le\cdots\le c_n\),\(\text{TL}=10\text s\)。
“バカ-trick”(兩個棧模擬佇列)的模板題。時間複雜度 \(O(nm^3)\)
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N = 1000003; int n, m, mod; struct Mat { int x[5][5]; Mat(){memset(x, 0, sizeof x);} void reset(){ for(int i = 0;i < 5;++ i) for(int j = 0;j < 5;++ j) x[i][j] = i == j; } Mat operator = (const Mat &o){memcpy(x, o.x, sizeof x); return *this;} Mat operator * (const Mat &o) const { Mat res; for(int i = 0;i < m;++ i) for(int j = 0;j < m;++ j){ LL tmp = 0; for(int k = 0;k < m;++ k) tmp += (LL)x[i][k] * o.x[k][j]; res.x[i][j] = tmp % mod; } return res; } } A[N], B, now; void solve(){ for(int i = 0;i < m;++ i) for(int j = 0;j < m;++ j) scanf("%d", &A[0].x[i][j]); for(int i = 0;i < m;++ i) for(int j = 0;j < m;++ j) scanf("%d", &B.x[i][j]); int p = 0; now.reset(); for(int i = 1, c;i <= n;++ i){ scanf("%d", &c); if(c > p){ for(int j = i-2;j >= c;-- j) A[j] = A[j] * A[j+1]; p = i-1; now.reset(); } A[i] = A[c] * now * B; now = now * A[i]; } for(int i = 0;i < m;++ i) for(int j = 0;j < m;++ j) printf("%d%c", A[n].x[i][j], " \n"[j==m-1]); } int main(){while(~scanf("%d%d%d", &n, &m, &mod)) solve();}
F Multi-stage Marathon
給定 \(n\) 個點的有向圖,\(m\) 個人從時刻 \(t_i\) 開始從點 \(v_i\) 隨機遊走,設 \(E_t\) 表示 \(n\) 號點在時刻 \(t\) 的期望人數模 \(10^9+7\),求 \(\bigoplus_{t=1}^TE_t\)。
\(n\le 70\),\(m\le 10^4\),\(T\le 2\cdot 10^6\)。
矩陣乘法模板題,注意到矩陣乘向量的複雜度是 \(O(n^2)\),而求矩陣乘向量的某一維的複雜度是 \(O(n)\)。
平衡一下,預處理 \(G^0,G^1,\cdots,G^L\),其中 \(G\) 是轉移矩陣。就可以做到 \(O(n^2\lceil\frac{T}{L}\rceil)\)
時間複雜度 \(O(n^3L+nT+n^2(\frac TL+m))\),大概取 \(L\approx100\) 即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 10003, LEN = 125, mod = 1e9+7;
int n, m, T, t[N], v[N], inv[75], ans;
char str[75];
void qmo(int &x){x += x >> 31 & mod;}
struct Mat {
int x[70][70];
Mat(){memset(x, 0, sizeof x);}
Mat operator = (const Mat &o){memcpy(x, o.x, sizeof x); return *this;}
void reset(){
for(int i = 0;i < n;++ i)
for(int j = 0;j < n;++ j)
x[i][j] = i == j;
}
Mat operator * (const Mat &o) const {
Mat res;
for(int i = 0;i < n;++ i)
for(int k = 0;k < n;++ k)
for(int j = 0;j < n;++ j)
res.x[i][j] = (res.x[i][j] + (LL)x[i][k] * o.x[k][j]) % mod;
return res;
}
} A[LEN+1];
struct Vec {
int x[70];
Vec(){memset(x, 0, sizeof x);}
Vec operator * (const Mat &o) const {
Vec res;
for(int i = 0;i < n;++ i)
for(int j = 0;j < n;++ j)
res.x[j] = (res.x[j] + (LL)x[i] * o.x[i][j]) % mod;
return res;
}
int operator ^ (const Mat &o) const {
int res = 0;
for(int i = 0;i < n;++ i)
res = (res + (LL)x[i] * o.x[i][n-1]) % mod;
return res;
}
} now;
int main(){
ios::sync_with_stdio(false);
cin >> n >> m >> T; A[0].reset(); inv[1] = 1;
for(int i = 2;i <= n;++ i) inv[i] = mod - (LL)mod / i * inv[mod % i] % mod;
for(int i = 0;i < n;++ i){
cin >> str; int cnt = 0;
for(int j = 0;j < n;++ j) cnt += str[j] - '0';
for(int j = 0;j < n;++ j) A[1].x[i][j] = str[j] != '0' ? inv[cnt] : 0;
}
for(int i = 2;i <= LEN;++ i) A[i] = A[i-1] * A[1];
for(int i = 0;i < m;++ i){cin >> t[i] >> v[i]; -- v[i];}
t[m] = T;
for(int i = 0;i < m;++ i){
++now.x[v[i]];
int step = t[i+1] - t[i];
for(;step >= LEN;step -= LEN){
for(int j = 0;j < LEN;++ j) ans ^= now ^ A[j];
now = now * A[LEN];
}
for(int j = 0;j < step;++ j) ans ^= now ^ A[j];
now = now * A[step];
}
printf("%d\n", ans ^ now.x[n-1]);
}
H Permutation and noitatumreP
【題目描述略】
寫個暴力扔到 OEIS 裡,發現了線性遞推式,於是就做完了
看上去不太有社論,只有 std,所以給我整不會了。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9+7, P[] = {0, 3, mod-2, 1, mod-1}, R[] = {2, 6, 16, 36, 80};
int n, f[30][5], g[5];
void mul(const int *a, const int *b, int *c){
static int tmp[9];
memset(tmp, 0, sizeof tmp);
for(int i = 0;i < 5;++ i)
for(int j = 0;j < 5;++ j)
tmp[i+j] = (tmp[i+j] + (LL)a[i] * b[j]) % mod;
for(int i = 8;i > 4;-- i){
for(int j = 1;j < 5;++ j)
tmp[i-j] = (tmp[i-j] + (LL)P[j] * tmp[i]) % mod;
tmp[i] = 0;
}
memcpy(c, tmp, 20);
}
void solve(){
if(n == 1){puts("1"); return;} n -= 2;
memset(g, 0, sizeof g); g[0] = 1;
for(int i = 29;~i;-- i) if(n>>i&1) mul(g, f[i], g);
int ans = 0;
for(int i = 0;i < 5;++ i) ans = (ans + (LL)R[i] * g[i]) % mod;
printf("%d\n", ans);
}
int main(){
ios::sync_with_stdio(false);
f[0][1] = 1;
for(int i = 1;i < 30;++ i) mul(f[i-1], f[i-1], f[i]);
while(cin >> n) solve();
}
C City United
給定 \(n\) 個點的無向圖,求連通匯出子圖個數\(\bmod 2\)。
\(n\le 50\),邊的兩端點編號之差 \(\le 13\)。
可以轉化為對連通塊黑白染色的方案數\(\bmod 4\)。即對所有點染黑白灰三色,使得黑色點與白色點之間沒有連邊。直接 dp 即可,時間複雜度 \(O(n3^k)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 50, M = 1594323;
int n, m, k, G[N], pw[14], pre[M][2];
char f[M], g[M];
void upd(char &a, const char &b){a = a + b & 3;}
template<typename T>
bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int main(){
ios::sync_with_stdio(false);
cin >> n >> m;
for(int i = 0, u, v;i < m;++ i){
cin >> u >> v;
if(u > v) swap(u, v);
chmax(k, v-u); -- v;
G[v] |= 1 << v-u;
} pw[0] = 1;
for(int i = 1;i <= k;++ i) pw[i] = 3 * pw[i-1];
for(int i = 1;i < pw[k];++ i)
for(int j = 0;j < 2;++ j)
pre[i][j] = pre[i/3][j] << 1 | (i%3 == j+1);
f[0] = 1;
for(int i = 0;i < n;++ i){
memset(g, 0, sizeof g);
for(int S = 0;S < pw[k];++ S) if(f[S]){
int T = S % pw[k-1] * 3;upd(g[T], f[S]);
if(!(G[i] & pre[S][1])) upd(g[T+1], f[S]);
if(!(G[i] & pre[S][0])) upd(g[T+2], f[S]);
}
memcpy(f, g, sizeof f);
}
char res = 3;
for(int i = 0;i < pw[k];++ i) upd(res, f[i]);
putchar(res >> 1 | '0');
}
D Coins 2
給定面值為 \(1,2,\cdots,n\) 的硬幣分別 \(a_1,a_2,\cdots,a_n\) 個,求能組合出的錢數。
\(n\le 15\),\(a_i\le 10^9\)。
設 \(m=\text{lcm}(1,2,\cdots,n)\),若 \(x\ge nm\) 能夠拼成,則必有一種面值 \(i\) 的個數 \(\ge\frac mi\),得到 \(x-m\) 也可以被拼成。
根據對稱性,設 \(s=\sum ia_i\),若 \(x\le s-nm\),則 \(x+m\) 也能拼成。
所以 \(\forall x\in[nm,s-(n+1)m]\),\(x\) 能拼成當且僅當 \(x+m\) 能拼成。所以只需要算出 \([1,(n+1)m]\) 能否被拼成即可。
時間複雜度 \(O(n^2m)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5765760;
int n, m, L, a[16], f[N]; LL sum, half, ans;
int lcm(int x, int y){return x / __gcd(x, y) * y;}
template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int main(){
ios::sync_with_stdio(false);
while(cin >> n){
sum = ans = 0; m = 1;
for(int i = 1;i <= n;++ i){
cin >> a[i]; m = lcm(m, i);
sum += (LL)a[i] * i;
}
L = m * n; half = sum >> 1;
memset(f, 0x3f, L + m << 2); f[0] = 0;
for(int i = 1;i <= n;++ i)
for(int j = 0;j < L + m;++ j){
f[j] = f[j] <= a[i-1] ? 0 : 1e9;
if(j >= i && f[j-i] < a[i])
chmin(f[j], f[j-i] + 1);
}
for(int i = 0;i < L + m;++ i) f[i] = f[i] <= a[n];
for(int i = 0;i < L && i <= half;++ i) ans += f[i];
for(int i = L;i < L + m && i <= half;++ i)
if(f[i]) ans += (half - i) / m + 1;
ans <<= 1;
if(!(sum & 1) && f[half < L ? half : half % m + L]) -- ans;
printf("%lld\n", ans);
}
}