[SNOI2019]纸牌
又是麻将
d
p
dp
dp,令
f
i
,
j
,
k
f_{i,j,k}
fi,j,k 表示
(
i
,
i
+
1
,
i
+
2
)
(i,i+1,i+2)
(i,i+1,i+2) 选了
j
j
j 个,
(
i
−
1
,
i
,
i
+
1
)
(i-1,i,i+1)
(i−1,i,i+1) 选了
k
k
k 的方案数
f
i
,
l
,
j
<
−
f
i
−
1
,
j
,
k
∗
(
C
−
l
−
j
−
k
3
+
1
)
f_{i,l,j}<-f_{i-1,j,k}*(\frac{C-l-j-k}{3}+1)
fi,l,j<−fi−1,j,k∗(3C−l−j−k+1)
把
(
j
,
k
)
(j,k)
(j,k) 表示成一维,中间已经有的点暴力转移,其余矩阵乘法即可
#include<bits/stdc++.h>
#define N 9
#define M 1050
using namespace std;
typedef long long ll;
const int Mod = 998244353;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b;}
int mul(int a, int b){ return 1ll * a * b % Mod;}
void Add(int &a, int b){ a = add(a, b);}
int C, X, a[M];
ll n, p[M];
struct Matrix{
int a[N][N];
Matrix(){ memset(a, 0, sizeof(a));}
Matrix operator * (const Matrix &A){
Matrix B; for(int i = 0; i < 9; i++)
for(int j = 0; j < 9; j++) for(int k = 0; k < 9; k++)
Add(B.a[i][j], mul(a[i][k], A.a[k][j]));
return B;
}
};
Matrix power(Matrix A, ll b){
Matrix ans; for(int i = 0; i < 9; i++) ans.a[i][i] = 1;
for(;b;b>>=1){if(b&1) ans = ans*A; A = A*A;} return ans;
}
int main(){
scanf("%lld%d%d", &n, &C, &X);
for(int i = 1; i <= X; i++){
scanf("%lld%d", &p[i], &a[i]);
} Matrix A, B; A.a[0][0] = 1;
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
for(int k = 0; k < 3; k++)
if(i + j + k <= C) B.a[j*3+k][i*3+j] = (C-i-j-k) / 3 + 1;
for(int d = 1; d <= X; d++){
A = A * power(B, p[d] - 1 - p[d-1]);
Matrix tmp;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
for(int k = 0; k < 3; k++){
int x = i * 3 + j, y = j * 3 + k;
int now = i + j + k, must = (now >= a[d]) ? now : (a[d] + ((now - a[d]) % 3 + 3) % 3);
if(must <= C) tmp.a[y][x] = (C - must) / 3 + 1;
}
}
} A = A * tmp;
}
A = A * power(B, n - p[X]);
cout << A.a[0][0]; return 0;
}
[SNOI2019]通信
网络流模型显然,
i
i
i 向
j
′
j'
j′ 连
∣
a
i
−
a
j
∣
|a_i-a_j|
∣ai−aj∣ 的边再向
T
T
T 连
W
W
W 的边即可
边数达到了
O
(
n
2
)
O(n^2)
O(n2) 凉凉
发现
i
i
i 会向它前面的比它小的连
a
i
−
a
j
a_i-a_j
ai−aj 的边,比它大的连
a
j
−
a
i
a_j-a_i
aj−ai 的边
建两棵主席树,一棵底层向原节点连正边,一棵连负边
还有一种分治优化建图的巧妙思想
考虑
c
d
q
cdq
cdq,每次解决后一半的向前一半连边的问题
把权值嗲出来排成一排排序去重,相邻连边
a
i
+
1
−
a
i
a_{i+1}-a_i
ai+1−ai
这样的好处是从一个点进一个点出中间的可以抵消掉
然后对一个点找到权值,如果在前一半就连出边,后一半就连入即可
#include<bits/stdc++.h>
#define N 200050
#define M 3000050
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch=='-') f = -1; }
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int first[N], nxt[M], to[M], w[M], c[M], tot = 1;
void add(int x, int y, int z, int v){
nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z, c[tot] = v;
nxt[++tot] = first[y], first[y] = tot, to[tot] = x, w[tot] = 0, c[tot] = -v;
}
typedef long long ll;
int n, W;
int a[N], b[N], len;
int rt1, rt2, node, ls[N], rs[N];
int st, ed;
#define mid ((l+r)>>1)
void Add(int x, int l, int r, int L, int R, int pos, int v){
if(!x) return;
if(L<=l && r<=R){ add(pos, x, 1, v); return;}
if(L <= mid) Add(ls[x], l, mid, L, R, pos, v);
if(R > mid) Add(rs[x], mid+1, r, L, R, pos, v);
}
void ins(int las, int &x, int l, int r, int p, int pos, int v){
x = ++node; ls[x] = ls[las]; rs[x] = rs[las];
if(l == r){ add(x, pos, 1, v); return;}
if(p <= mid) ins(ls[las], ls[x], l, mid, p, pos, v);
else ins(rs[las], rs[x], mid+1, r, p, pos, v);
if(ls[x]) add(x, ls[x], 1e9, 0);
if(rs[x]) add(x, rs[x], 1e9, 0);
}
ll dis[N]; bool vis[N]; int from[N], froms[N];
bool spfa(){
for(int i = 0; i <= node; i++) dis[i] = 1e18, vis[i] = 0;
dis[st] = 0; queue<int> q; q.push(st);
while(!q.empty()){
int x = q.front(); q.pop(); vis[x] = 0;
for(int i = first[x]; i; i = nxt[i]){
int t = to[i]; if(w[i] && dis[t] > dis[x] + c[i]){
from[t] = x; froms[t] = i;
dis[t] = dis[x] + c[i]; if(!vis[t]) q.push(t), vis[t] = 1;
}
}
} return dis[ed] != 1e18;
}
int calc(){
int flow = 1e9, u = ed;
while(u^st) flow = min(flow, w[froms[u]]), u = from[u]; u = ed;
while(u^st) w[froms[u]] -= flow, w[froms[u]^1] += flow, u = from[u];
return flow;
}
ll dinic(){ ll ans = 0; while(spfa()) ans += 1ll * calc() * dis[ed]; return ans;}
int main(){
n = read(), W = read();
for(int i = 1; i <= n; i++) a[i] = b[++len] = read();
sort(b + 1, b + len + 1); len = unique(b + 1, b + len + 1) - (b + 1);
for(int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
st = 0; ed = node = 2 * n + 1;
for(int i = 1; i <= n; i++){
add(st, i, 1, 0); add(i + n, ed, 1, 0);
add(i, ed, 1, W);
}
for(int i = 1; i <= n; i++){
Add(rt1, 1, len, 1, a[i], i, b[a[i]]);
Add(rt2, 1, len, a[i], len, i, -b[a[i]]);
ins(rt1, rt1, 1, len, a[i], n + i, -b[a[i]]);
ins(rt2, rt2, 1, len, a[i], n + i, b[a[i]]);
} cout << dinic(); return 0;
}
[SNOI2019]数论
对于
A
A
A 集合中的一个数
a
i
a_i
ai, 显然
a
i
+
k
∗
p
a_i+k*p
ai+k∗p 都是合法的
k
≤
T
−
1
−
a
i
p
k\le \frac{T-1-a_i}{p}
k≤pT−1−ai,而如果这样一直加下去,在模
q
q
q 意义下一定会循环
考虑循环节大小:
l
c
m
(
p
,
q
)
p
=
q
g
c
d
(
p
,
q
)
\frac{lcm(p,q)}{p}=\frac{q}{gcd(p,q)}
plcm(p,q)=gcd(p,q)q,发现不是很大
于是有一个巧妙的转换,类似同余最短路之类的东西
对于
x
∈
[
0
,
q
)
x\in[0,q)
x∈[0,q),
x
x
x 向
(
x
+
p
)
%
q
(x+p)\%q
(x+p)%q 连边,如果属于
B
B
B 集合那么权值为
1
1
1
问题转换为从一个
x
x
x 开始,走
T
−
1
−
x
p
\frac{T-1-x}{p}
pT−1−x 步,经过的权值是多少
考虑到最后的图一定是一个个大小为
q
g
c
d
(
p
,
q
)
\frac{q}{gcd(p,q)}
gcd(p,q)q 的环
对环统计一个前缀和就可以
O
(
1
)
O(1)
O(1) 知道答案
这种数学问题通过图论建模的转换十分巧妙
#include<bits/stdc++.h>
#define N 1000050
using namespace std;
typedef long long ll;
int n, m, P, Q, tot;
vector<int> cir[N], sum[N];
int A[N], B[N], vis[N], v[N], pos[N], val[N];
ll T, p[N];
int dfs(int u){
if(vis[u]) return 0;
vis[u] = tot; cir[tot].push_back(u); pos[u] = cir[tot].size() - 1;
return dfs((u + P) % Q) + v[u];
}
int gcd(int a, int b){ return !b ? a : gcd(b, a % b);}
int main(){
scanf("%d%d%d%d%lld", &P, &Q, &n, &m, &T);
for(int i = 1; i <= n; i++) scanf("%d", &A[i]);
for(int i = 1; i <= m; i++) scanf("%d", &B[i]);
if(P > Q) swap(P, Q), swap(n, m), swap(A, B);
for(int i = 1; i <= m; i++) v[B[i]] = 1;
for(int i = 1; i <= n; i++) p[i] = (T - 1 - A[i]) / P;
for(int i = 0; i < Q; i++){
if(!vis[i]) val[++tot] = dfs(i);
}
for(int i = 1; i <= tot; i++){
int Siz = cir[i].size();
for(int j = 0; j < Siz; j++) cir[i].push_back(cir[i][j]);
sum[i].push_back(v[cir[i][0]]);
for(int j = 1; j < cir[i].size(); j++) sum[i].push_back(v[cir[i][j]] + sum[i][j-1]);
}
int len = Q / gcd(P, Q);
ll ans = 0;
for(int i = 1; i <= n; i++){
ans += 1ll * (p[i] / len) * val[vis[A[i]]];
int res = p[i] % len;
ans += sum[vis[A[i]]][pos[A[i]] + res] - sum[vis[A[i]]][pos[A[i]]] + v[A[i]];
} cout << ans; return 0;
}
[SNOI2019]字符串
先把相邻的相同的压成一个
分类讨论:
若
a
i
>
a
i
+
1
a_i>a_{i+1}
ai>ai+1 那么删除
a
i
a_i
ai 比删除后面的任意一个都更优
a
i
<
a
i
+
1
a_i<a_{i+1}
ai<ai+1 那么删除
a
i
a_i
ai 比删除后面任意一个都不优
然后可以从小到大扫一遍,按上述规则将
a
i
a_i
ai 放到当前的最前面或者最后面即可
[SNOI2019]积木
首先并未要求操作数最小
发现找到空格到终点空格的路径后,中间的经过的块可以顺便归位
但这样就导致了没有经过的地方没有归位
我们就可以暴力把空格挪过去归位后在挪回来
空格在从起点移到终点的过程中,我们看一下哪个方向的格子没有走过
然后把空格挪过去转换为更原来一样的问题
这种构造题做的还是不多
本质思想是通过操作次数来让写法变得更简便
#include<bits/stdc++.h>
#define N 2050
#define cs const
using namespace std;
int n, m, Sx, Sy, Tx, Ty;
char S[N][N], T[N][N];
int dx[5] = {1, 0, -1, 0};
int dy[5] = {0, 1, 0, -1};
int way(char c){
if(c == 'n') return 0;
if(c == '<') return 1;
if(c == 'u') return 2;
if(c == '>') return 3;
}
cs char *str = "n<u>", *op = "DRUL";
bool used[N][N], vis[N][N];
void go(int d){
putchar(op[d]);
int ox = Sx + dx[d], oy = Sy + dy[d], di = way(S[ox][oy]);
int nx = ox + dx[di], ny = oy + dy[di];
S[nx][ny] = 'o'; S[Sx][Sy] = str[d]; S[ox][oy] = str[d ^ 2];
Sx = nx, Sy = ny;
}
void dfs2(int x, int y){
if(vis[x][y] || (x == Tx && y == Ty)) return;
int d = way(T[x][y]);
vis[x][y] = true; x += dx[d], y += dy[d];
vis[x][y] = true; d = way(S[x][y]);
x += dx[d], y += dy[d]; dfs2(x, y);
}
void dfs(int x, int y){
if(used[x][y]) return; used[x][y] = true;
dfs2(x, y);
for(int i = 0; i < 4; i++){
int nx = x + dx[i], ny = y + dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > m || vis[nx][ny]) continue;
go(i); dfs(Sx, Sy);
}
if(x == Tx && y == Ty) return;
go(way(T[x][y])); dfs(Sx, Sy);
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%s", S[i] + 1);
for(int i = 1; i <= n; i++) scanf("%s", T[i] + 1);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if(S[i][j] == 'o') Sx = i, Sy = j;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if(T[i][j] == 'o') Tx = i, Ty = j;
dfs(Sx, Sy); return 0;
}