定义
f
(
x
,
y
)
f(x,y)
f(x,y) 表示在矩阵中取出第
x
x
x 行和第
y
y
y 列
设一个新矩阵
b
b
b ,这个矩阵只有第
x
x
x 行和第
y
y
y 列
对于
b
b
b 中的任意两个元素
b
i
,
j
b_{i,j}
bi,j 和
b
u
,
v
b_{u,v}
bu,v
如果
b
i
,
j
<
b
u
,
v
b_{i,j}<b_{u,v}
bi,j<bu,v 则
a
i
,
j
a_{i,j}
ai,j 必须小于
a
u
,
v
a_{u,v}
au,v
如果
b
i
,
j
=
b
u
,
v
b_{i,j}=b_{u,v}
bi,j=bu,v 则
a
i
,
j
a_{i,j}
ai,j 必须等于
a
u
,
v
a_{u,v}
au,v
>
>
> 同理
f
(
x
,
y
)
f(x,y)
f(x,y) 等于矩阵
b
b
b 所有合法的方案中,
b
b
b 中最大元素的最小值
对于每个
1
≤
x
≤
n
,
1
≤
y
≤
m
1\le x\le n,1\le y\le m
1≤x≤n,1≤y≤m 求
f
(
x
,
y
)
f(x,y)
f(x,y)
1
≤
n
,
m
≤
1000
1\le n,m\le 1000
1≤n,m≤1000
题解
显然,我们要求的是
max
(
第
x
行
比
a
x
,
y
小
的
数
个
数
,
第
y
列
比
a
x
,
y
小
的
数
个
数
)
+
1
\max(第x行比a_{x,y}小的数个数,第y列比a_{x,y}小的数个数)+1
max(第x行比ax,y小的数个数,第y列比ax,y小的数个数)+1
+
max
(
第
x
行
比
a
x
,
y
大
的
数
个
数
,
第
y
列
比
a
x
,
y
大
的
数
个
数
)
+\max(第x行比a_{x,y}大的数个数,第y列比a_{x,y}大的数个数)
+max(第x行比ax,y大的数个数,第y列比ax,y大的数个数)
max
\max
max 里面的四个东西都可以把矩阵的每行每列离散化之后求出
O
(
n
m
(
log
n
+
log
m
)
)
O(nm(\log n+\log m))
O(nm(logn+logm))
代码
#include<bits/stdc++.h>// 20030830inlineintread(){int res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);return bo ?~res +1: res;}constint N =1005;int n, m, a[N][N], cntx[N], cnty[N], thx[N][N], thy[N][N], w[N];intmain(){
n =read(); m =read();for(int i =1; i <= n; i++)for(int j =1; j <= m; j++)
a[i][j]=read();for(int i =1; i <= n; i++){for(int j =1; j <= m; j++) w[j]= a[i][j];
std::sort(w +1, w + m +1);
cntx[i]= std::unique(w +1, w + m +1)- w -1;for(int j =1; j <= m; j++)
thx[i][j]= std::lower_bound(w +1, w + cntx[i]+1, a[i][j])- w;}for(int i =1; i <= m; i++){for(int j =1; j <= n; j++) w[j]= a[j][i];
std::sort(w +1, w + n +1);
cnty[i]= std::unique(w +1, w + n +1)- w -1;for(int j =1; j <= n; j++)
thy[j][i]= std::lower_bound(w +1, w + cnty[i]+1, a[j][i])- w;}for(int i =1; i <= n; i++){for(int j =1; j <= m; j++)printf("%d ", std::max(thx[i][j], thy[i][j])+ std::max(cntx[i]- thx[i][j], cnty[j]- thy[i][j]));puts("");}return0;}
B
题意
两个
01
01
01 串
s
s
s ,
t
t
t
构造一个串,满足
(1) 该串中
0
0
0 的个数和
1
1
1 的个数都与
s
s
s 一样多
(2)
t
t
t 在该串中出现的次数最多
1
≤
∣
s
∣
,
∣
t
∣
≤
500000
1\le|s|,|t|\le500000
1≤∣s∣,∣t∣≤500000
算法: KMP + 贪心
问题可以看成一个完整的
t
t
t 串,后面接上一些
t
t
t 的后缀(这里的后缀
t
[
∣
t
∣
−
x
+
1
:
∣
t
∣
]
t[|t|-x+1:|t|]
t[∣t∣−x+1:∣t∣] 需要满足对于所有的
1
≤
i
≤
∣
t
∣
−
x
+
1
1\le i\le|t|-x+1
1≤i≤∣t∣−x+1 都有
t
[
i
]
=
t
[
i
+
x
−
1
]
t[i]=t[i+x-1]
t[i]=t[i+x−1] ,即
t
t
t 的一个 period ),使得
0
0
0 和
1
1
1 的个数在一个上限范围内时,接入的
t
t
t 的后缀尽可能多
而
t
t
t 的最短 period 后缀就是
n
n
n 减去
t
t
t 的最长 border 长度,可以使用 KMP 求出
O
(
n
)
O(n)
O(n)
代码
#include<bits/stdc++.h>// 20030830constint N =5e5+5;int n, m, tt, cnt1, cnt0, c0, c1, nxt[N], s0[N], s1[N];char s[N], t[N];struct node
{int id, cst, ccc;} a[N];inlineboolcomp(node a, node b){return a.cst < b.cst ||(a.cst == b.cst && a.ccc < b.ccc);}intmain(){scanf("%s%s", s +1, t +1);
n =strlen(s +1);
m =strlen(t +1);for(int i =1; i <= n; i++)if(s[i]=='1') cnt1++;else cnt0++;for(int i =1; i <= m; i++)if(t[i]=='1') c1++;else c0++;for(int i =2, j =0; i <= m; i++){while(j && t[j +1]!= t[i]) j = nxt[j];if(t[j +1]== t[i]) j++;
nxt[i]= j;}if(cnt1 < c1 || cnt0 < c0){for(int i =1; i <= n; i++)putchar(s[i]);returnputs(""),0;}for(int i = m; i >=1; i--)if(t[i]=='1') s1[i]= s1[i +1]+1, s0[i]= s0[i +1];else s1[i]= s1[i +1], s0[i]= s0[i +1]+1;for(int i = nxt[m];; i = nxt[i]){
a[++tt]=(node){i +1, s0[i +1], s1[i +1]};if(!i)break;}for(int i =1; i <= m; i++)putchar(t[i]);int tql = cnt0 - c0, trl = cnt1 - c1;
std::sort(a +1, a + tt +1, comp);while(a[1].cst <= tql && a[1].ccc <= trl){
tql -= a[1].cst, trl -= a[1].ccc;for(int j = a[1].id; j <= m; j++)putchar(t[j]);}while(tql--)putchar('0');while(trl--)putchar('1');puts("");return0;}
C
题意
n
n
n 个城市,
m
m
m 条单向道路
一周有
d
d
d 天,第
i
i
i 个城市的博物馆在一周的第
j
j
j 天开放当且仅当
s
i
,
j
=
1
s_{i,j}=1
si,j=1
周一从城市
1
1
1 出发,走一条道路需要
1
1
1 天时间
如果一周的第
j
j
j 天恰好在城市
i
i
i 并且
s
i
,
j
=
1
s_{i,j}=1
si,j=1 则能参观到城市
i
i
i 的博物馆
求最多能参观到多少个城市的博物馆
1
≤
n
≤
100
,
000
1\le n\le100,000
1≤n≤100,000 ,
0
≤
m
≤
100
,
000
0\le m\le100,000
0≤m≤100,000 ,
1
≤
d
≤
50
1\le d\le50
1≤d≤50
算法: Tarjan 强连通分量缩点 + DP
把一个城市拆成
d
d
d 个点,点
(
i
,
j
)
(i,j)
(i,j) 表示一周第
j
j
j 天的城市
i
i
i
对于有向边
(
u
,
v
)
(u,v)
(u,v) 和任意
1
≤
i
≤
d
1\le i\le d
1≤i≤d ,连边
<
(
u
,
i
)
,
(
v
,
i
 
m
o
d
 
d
+
1
)
>
<(u,i),(v,i\bmod d+1)>
<(u,i),(v,imodd+1)>
把这张图强连通分量缩点之后,就是经典的 DAG 最长链 DP 了
Q :一个城市的博物馆被多次参观只计算一次,如何处理?
A :可以证明,如果
(
u
,
i
)
(u,i)
(u,i) 能到达
(
u
,
j
)
(u,j)
(u,j) ,那么
(
u
,
j
)
(u,j)
(u,j) 也能到达
(
u
,
i
)
(u,i)
(u,i) (可以使用数论相关知识证明),换句话说,一个城市拆出的两个点
(
u
,
i
)
(u,i)
(u,i) 和
(
u
,
j
)
(u,j)
(u,j) ,要么属于同一个强连通分量,要么两两不可到达。于是任意一条从
(
1
,
1
)
(1,1)
(1,1) 开始的链都不会存在
(
u
,
i
)
(u,i)
(u,i) 和
(
u
,
j
)
(u,j)
(u,j) 不在同一个强连通分量内,于是这个 DP 的正确性有了保证
O
(
(
n
+
m
)
d
)
O((n+m)d)
O((n+m)d)
代码
#include<bits/stdc++.h>// 20030830inlineintread(){int res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);return bo ?~res +1: res;}template<classT>inline T Min(const T &a,const T &b){return a < b ? a : b;}template<classT>inline T Max(const T &a,const T &b){return a > b ? a : b;}constint N =1e5+5, E =55, M =5e6+5;int n, m, d, ecnt, nxt[M], adj[M], go[M], ToT, dfn[M], low[M], top, stk[M],
tot, bel[M], vis[M], typ[M], ecnt2, nxt2[M], adj2[M], go2[M], f[M];char s[N][E];bool ins[M];intwhich(int x,int y){return(x -1)* d + y;}voidadd_edge(int u,int v){
nxt[++ecnt]= adj[u]; adj[u]= ecnt; go[ecnt]= v;}voidadd_edge2(int u,int v){
nxt2[++ecnt2]= adj2[u]; adj2[u]= ecnt2; go2[ecnt2]= v;}voiddfs(int u){
dfn[u]= low[u]=++ToT;
ins[stk[++top]= u]=1;for(int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])if(!dfn[v]){dfs(v);
low[u]=Min(low[u], low[v]);}elseif(ins[v]) low[u]=Min(low[u], dfn[v]);if(dfn[u]== low[u]){
bel[u]=++tot; ins[u]=0;int v;while(v = stk[top--], v != u) bel[v]= tot, ins[v]=0;}}intmain(){int x, y;
n =read(); m =read(); d =read();while(m--){
x =read(); y =read();for(int i =1; i <= d; i++)add_edge(which(x, i),which(y, i % d +1));}for(int i =1; i <= n; i++)scanf("%s", s[i]+1);for(int i =1; i <= n * d; i++)if(!dfn[i])dfs(i);for(int i =1; i <= n; i++)for(int j =1; j <= d; j++){int u =which(i, j);if(s[i][j]=='1'&& vis[bel[u]]< i)
vis[bel[u]]= i, typ[bel[u]]++;}for(int u =1; u <= n * d; u++)for(int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])if(bel[u]!= bel[v])add_edge2(bel[u], bel[v]);for(int u =1; u <= tot; u++){for(int e = adj2[u], v = go2[e]; e; e = nxt2[e], v = go2[e])
f[u]=Max(f[u], f[v]);
f[u]+= typ[u];}
std::cout << f[bel[which(1,1)]]<< std::endl;return0;}
而这时候
u
u
u 在
t
 
m
o
d
 
c
t\bmod c
tmodc 号点,
v
v
v 在
0
0
0 号点
相当于
u
u
u 继续走
2
(
(
c
−
t
)
 
m
o
d
 
c
)
2((c-t)\bmod c)
2((c−t)modc) 步,
v
v
v 走
(
c
−
t
)
 
m
o
d
 
c
(c-t)\bmod c
(c−t)modc 步后
u
u
u 能追上
v
v
v
这时候
v
v
v 的位置为
(
c
−
t
)
 
m
o
d
 
c
(c-t)\bmod c
(c−t)modc
你发现了什么?
u
u
u 和
v
v
v 一起再走
t
t
t 步就到了环和链的交点处!
也就是说,我们这时候把所有的棋子一起移动,直到所有棋子都在一个点上为止
移动步数分析:
(1)
v
v
v 到达交点处:耗费
2
t
2t
2t 步
(2)
u
u
u 和
v
v
v 到达一个点上:
2
(
(
c
−
t
)
m
o
d
  
c
)
<
2
c
2((c-t)\mod c)<2c
2((c−t)modc)<2c
颜
色
比
u
小
的
点
数
+
u
所
在
的
实
链
中
比
u
深
的
点
数
+
1
颜色比u小的点数+u所在的实链中比u深的点数+1
颜色比u小的点数+u所在的实链中比u深的点数+1
第一者可以用树状数组维护每种颜色的点数的前缀和
每种颜色的点数的变化可以在 Access 的过程中计算贡献
第二者在 Splay 上查排名即可
O
(
(
n
+
q
)
log
2
n
)
O((n+q)\log^2n)
O((n+q)log2n)
代码
#include<bits/stdc++.h>// 20030830inlineintread(){int res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);return bo ?~res +1: res;}template<classT>inlinevoidSwap(T &a, T &b){T t = a; a = b; b = t;}inlinecharget(){char c;while((c =getchar())!='u'&& c !='w'&& c !='c');return c;}constint N =4e5+5;int n, m, q, ecnt, nxt[N], adj[N], go[N], A[N], fa[N], lc[N], rc[N], d[N],
sze[N], col[N], rev[N], len, que[N];
std::priority_queue<int, std::vector<int>, std::greater<int>> pq;voidadd_edge(int u,int v){
nxt[++ecnt]= adj[u]; adj[u]= ecnt; go[ecnt]= v; d[u]++;
nxt[++ecnt]= adj[v]; adj[v]= ecnt; go[ecnt]= u; d[v]++;}voidchange(int x,int v){for(; x <= n + q; x += x &-x)
A[x]+= v;}intask(int x){int res =0;for(; x; x -= x &-x)
res += A[x];return res;}intwhich(int x){return rc[fa[x]]== x;}boolisroot(int x){return!fa[x]||(lc[fa[x]]!= x && rc[fa[x]]!= x);}voiddown(int x){if(rev[x])Swap(lc[x], rc[x]), rev[x]=0,
rev[lc[x]]^=1, rev[rc[x]]^=1;}voidupt(int x){
sze[x]= sze[lc[x]]+ sze[rc[x]]+1;}voidinit(int u,int fu){
fa[u]= fu;for(int e = adj[u], v; e; e = nxt[e])if((v = go[e])!= fu)init(v, u);}voidrotate(int x){int y = fa[x], z = fa[y], b = lc[y]== x ? rc[x]: lc[x];
col[x]= col[y]; col[y]=0;if(z &&!isroot(y))(lc[z]== y ? lc[z]: rc[z])= x;
fa[x]= z; fa[y]= x;if(b) fa[b]= y;if(lc[y]== x) rc[x]= y, lc[y]= b;else lc[x]= y, rc[y]= b;upt(y);upt(x);}voidsplay(int x){
que[len =1]= x;for(int y = x;!isroot(y); y = fa[y]) que[++len]= fa[y];for(int i = len; i >=1; i--)down(que[i]);while(!isroot(x)){if(!isroot(fa[x])){if(which(x)==which(fa[x]))rotate(fa[x]);elserotate(x);}rotate(x);}}voidaccess(int x){int c =++m;for(int y =0; x; y = x, x = fa[x]){splay(x);if(rc[x]) col[rc[x]]= col[x];change(col[x], sze[rc[x]]- sze[x]);change(c, sze[x]- sze[rc[x]]);
rc[x]= y; col[x]= c; col[y]=0;if(y) fa[y]= x;upt(x);}}voidmakeroot(int x){access(x);splay(x); rev[x]^=1;}intrk(int x){splay(x);returnask(col[x]-1)+ sze[rc[x]]+1;}intmain(){char c;int x, y;
m = n =read(); q =read();for(int i =1; i < n; i++)
x =read(), y =read(),add_edge(x, y);for(int i =1; i <= n; i++)if(d[i]<=1) pq.push(i);for(int i =1; i <= n; i++){
sze[i]=1;int u = pq.top(); pq.pop();
col[u]= i;change(i,1);for(int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])if(!col[v]&&(--d[v])<=1) pq.push(v);}init(n,0);for(int T =1; T <= q; T++){
c =get(); x =read();if(c =='u')makeroot(x);elseif(c =='w')printf("%d\n",rk(x));else y =read(),printf("%d\n",rk(x)<rk(y)? x : y);}return0;}