G Matrix Recurrence
給定 \(M_0,B\in\Z_{\text{mod}}^{m\times m}\) 和 \(c_1,\cdots,c_n\in\N\),定義
求 \(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)\) 算一項答案。
時間復雜度 \(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);
}
}
K Welcome to ICPCCamp 2017
給定 \(m\) 支隊伍打 \(n\) 場區域賽和一場 EC-Final。第 \(i\) 場區域賽有 \(k_i\) 支隊伍打,排名是 \(r_{i,1},\cdots,r_{i,k_i}\)。所有隊伍都打了 EC-Final,排名是 \(r_{n+1,1},\cdots,r_{n+1,m}\)。
你任意選取正整數 \(x,y\) 和長為 \(n\) 的排列 \(p\),求 \((r_{n+1,1},\cdots,r_{n+1,y},r_{p_1,1},\cdots,r_{p_n,1},r_{p_1,2},\cdots,r_{p_n,2},\cdots)\) 的前 \(x+y\) 支不同隊伍的集合的個數\(\bmod(10^9+7)\)。
\(\sum k_i,\sum m\le 2\cdot 10^5\)。
這就是叉姐說的聊天題嗎
從大到小枚舉 EC-Final 的最高排名的未出線隊伍 \(i\),不妨設其前面的隊伍都通過 EC-Final 名額出線,考慮其后面有隊伍通過區域賽出線的情況,只需要讓隊伍 \(i\) 拿不到區域賽出線名額即可,需要單點修改前綴和查詢,用樹狀數組維護即可。
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <vector>
const int MOD = (int)1e9 + 7;
void update(int& x, int a)
{
x += a;
if (x >= MOD) {
x -= MOD;
}
}
int main()
{
int n, m;
while (scanf("%d%d", &n, &m) == 2) {
std::vector<std::vector<int>> regionals;
for (int i = 0; i < n; ++ i) {
int k;
scanf("%d", &k);
std::vector<int> regional(k);
for (int j = 0; j < k; ++ j) {
scanf("%d", ®ional.at(j));
}
regionals.push_back(regional);
}
std::vector<int> ec(m), rank(m, m);
for (int i = 0; i < m; ++ i) {
int team;
scanf("%d", &team);
team --;
ec.at(team) = i;
}
for (auto&& regional : regionals) {
for (int i = 0; i < static_cast<int>(regional.size()); ++ i) {
int team = regional.at(i);
team --;
auto& ref = rank.at(ec.at(team));
ref = std::min(ref, i);
}
}
int result = m + 1; // y == m
std::vector<int> power(m + 1, 1);
std::vector<int> count(m + 1);
for (int y = m - 1; y >= 0; -- y) {
auto&& r = rank.at(y);
for (int i = r; i >= 0; i -= ~i & i + 1) {
update(result, count.at(i));
}
if (rank.at(y) < m) {
for (int i = r; i <= m; i += ~i & i + 1) {
update(count.at(i), power.at(r));
}
update(power.at(r), power.at(r));
}
}
printf("%d\n", result);
}
}
不太想寫了放個 std 在這里
A Even Three is Odd
給定 \(w_1,w_2,\dots,w_n\),求
\(\sum n\le 2000\)。
神仙 dp 。
不能把兩個數記錄進狀態里,就考慮只記錄最大值,設 \(f_{i,j,k}\) 表示考慮 \(x\) 的前 \(i+j\) 個值,\(x_i,x_{i+1},x_{i+2}\) 的最靠后的最大值是 \(x_{i+j}=k\) 的情況下,\(\prod_{l=1}^iw_{\max(x_l,x_{l+1},x_{l+2})}\) 之和。考慮什么情況下 \(f_{i,j,k}\) 能轉移到 \(f_{i+1,j',k'}\),枚舉一下發現是 \(j'=j-1\land k'=k\),或者 \(j=0\land k>k'\),或者 \(j'=2\land k\le k'\),使用前/后綴和優化,時間復雜度 \(O(n^2)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2003, mod = 1e9+7;
int n, w[N], f[3][N], g[3][N];
void qmo(int &x){x += x >> 31 & mod;}
int main(){
ios::sync_with_stdio(false);
while(cin >> n){
for(int i = 0;i < n;++ i){
cin >> w[i]; f[0][i] = w[i];
f[1][i] = (i+1ll) * w[i] % mod;
f[2][i] = (i+1ll) * f[1][i] % mod;
}
for(int i = 3;i < n;++ i){
memset(g, 0, sizeof g); g[2][0] = f[2][0];
for(int j = 1;j < n;++ j)
g[2][j] = (g[2][j-1] + (LL)j*j*f[0][j] + (LL)j*f[1][j] + f[2][j]) % mod;
int tmp = 0;
for(int j = n-1;~j;-- j){
qmo(g[0][j] = tmp + f[1][j] - mod);
g[1][j] = (tmp*(j+1ll) + f[2][j]) % mod;
g[2][j] = (tmp*(j+1ll)*(j+1ll) + g[2][j]) % mod;
for(int k = 0;k < 3;++ k) g[k][j] = (LL)g[k][j] * w[j] % mod;
qmo(tmp += f[0][j] - mod);
}
memcpy(f, g, sizeof f);
}
int ans = 0;
for(int i = 0;i < n;++ i)
ans = (ans + (LL)i*i*f[0][i] + (LL)i*f[1][i] + f[2][i]) % mod;
printf("%d\n", ans);
}
}