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);
}
}