N
N
N 个点,第
i
i
i 个点目标颜色为
a
i
a_i
ai 有
Q
Q
Q 个询问,第
i
i
i 个表示只涂区间
[
L
i
,
R
i
]
[L_i,R_i]
[Li,Ri] 的最少次数
刷一次颜色
c
c
c 的要求:刷满一个连续的区间,且区间区间中所有已涂颜色都必须小于等于
c
c
c,然后该区间的所有颜色变成
c
c
c
1
≤
N
,
Q
≤
2
e
5
1\le N,Q\le 2e5
1≤N,Q≤2e5
思路
之前的某道题目是相当于询问一次
[
1
,
N
]
[1,N]
[1,N] 的答案,是使用单调栈去做的,但是这次有很多个询问,肯定不能全部暴力去跑。
场上想到了莫队,但是貌似挺麻烦的… 看一下用线段树的解法:
首先,我们原题目为什么用单调栈去做呢? 对于两个位置
p
1
,
p
2
p_1,p_2
p1,p2 ,
a
p
1
=
a
p
2
a_{p_1}=a_{p_2}
ap1=ap2且
p
1
<
p
2
p_1<p_2
p1<p2 如果
∀
p
3
\forall p_3
∀p3 满足
p
1
<
p
3
<
p
2
p_1<p_3<p_2
p1<p3<p2,且
a
p
3
>
a
p
1
=
a
p
2
a_{p_3}>a_{p_1}=a_{p_2}
ap3>ap1=ap2 也就是说这两个位置我们只用一次涂就可以了
转换一下,也就是我们对于某个询问区间
[
L
,
R
]
[L,R]
[L,R] 我们需要知道有多少对
p
1
,
p
2
p_1,p_2
p1,p2,在这个区间中且满足上述的要求,假设有
t
m
p
tmp
tmp 对 那么答案为多少呢?为
(
R
−
L
+
1
)
−
t
m
p
(R-L+1)-tmp
(R−L+1)−tmp 为什么呢?可以理解为如果找到了一对,因为他们可以通过一次涂色解决,相当于把这两个点给合并了,即点的个数
−
1
-1
−1 最后就是求这个区间内还剩下多少个无法合并的点,就是区间长度 减去 合并次数
那么我们怎么去求这个
t
m
p
tmp
tmp 呢? 我们可以离线做,把询问对给排序,按照右端点升序 然后像一般的单调栈一样,我们按顺序一个一个把点加进来,然后把栈顶大于目前点的颜色的点给
p
o
p
pop
pop 掉
如果栈顶颜色等于目前点的颜色,那么我们就是找到了这一对点 也就是说,如果区间的左端点小于等于栈顶元素的位置,那么这个区间囊括了这一对点 转化一下,我们每次把这个栈顶元素的位置
p
p
p 获得,
v
a
l
[
p
]
+
+
val[p]++
val[p]++ 最后询问,只要查询
[
L
,
R
]
[L,R]
[L,R] 之内
v
a
l
[
i
]
val[i]
val[i] 的和,就是我们的
t
m
p
tmp
tmp 值 用树状数组会快一点
代码
时间复杂度:
O
(
Q
log
Q
+
N
log
N
)
O(Q\log Q+N\log N)
O(QlogQ+NlogN)
给定一个
N
×
N
N\times N
N×N 的矩阵
S
i
,
j
S_{i,j}
Si,j 求有多少个子矩阵
S
′
S^\prime
S′,满足子矩阵元素
min
{
S
′
}
=
100
\min\{S^\prime\}=100
min{S′}=100
1
≤
N
≤
500
1\le N\le 500
1≤N≤500
思路
求
=
A
=A
=A 问题难,就转化为求
≥
A
\ge A
≥A 以及
>
A
>A
>A 的问题 这里,就是求有多少个子矩阵满足
min
{
S
′
}
≥
100
\min\{S^\prime\}\ge100
min{S′}≥100 以及
=
100
=100
=100
我们就看第一个问题,若
S
i
,
j
≥
100
S_{i,j}\ge 100
Si,j≥100,我们就令
A
i
,
j
=
1
A_{i,j}=1
Ai,j=1,否则
A
i
,
j
=
0
A_{i,j}=0
Ai,j=0 然后就转化为了另一个问题:求有多少个全
1
1
1子矩阵
首先数据范围当然不允许我们把每个子矩阵去遍历检查 我们想一下,如果矩阵左上角为
(
i
,
j
)
(i,j)
(i,j),然后有多少个高为
1
1
1 的全一子矩阵?就是获得
max
{
t
}
\max\{t\}
max{t},满足
A
i
,
j
∼
A
i
,
k
A_{i,j}\sim A_{i,k}
Ai,j∼Ai,k 都是
1
1
1 然后有多少个高为
2
2
2 的全一子矩阵?就是
min
{
t
,
p
}
\min\{t,p\}
min{t,p},满足
A
i
+
1
,
j
∼
A
i
+
1
,
p
=
1
A_{i+1,j}\sim A_{i+1,p}=1
Ai+1,j∼Ai+1,p=1 看图:
也就是说,我们事先预处理出
y
o
u
[
i
]
[
j
]
you[i][j]
you[i][j] 表示从
A
i
,
j
A_{i,j}
Ai,j 开始,向右,第一个碰到
0
0
0 的位置是多少 预处理是可以
O
(
N
2
)
O(N^2)
O(N2) 的,我偷个懒写了个
O
(
N
3
)
O(N^3)
O(N3)的 然后答案查询,枚举左上角
O
(
N
2
)
O(N^2)
O(N2),然后一行一行向下枚举
O
(
N
)
O(N)
O(N),总共
O
(
N
3
)
O(N^3)
O(N3)
代码
时间复杂度:
O
(
N
3
)
O(N^3)
O(N3)
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =5e2+50;constint MOD =1e9+7;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-5;int shu[MAX][MAX];int aa[MAX][MAX];int you[MAX][MAX];
ll solve(int n){for(int i =1;i <= n;++i){for(int j =1;j <= n;++j){if(aa[i][j]==0){
you[i][j]=0;continue;}for(int k = j;k <= n+1;++k){// 注意下如果右边没有0的话,you[i][j]=n+1if(aa[i][k]==0){
you[i][j]= k;break;}}}}
ll ans =0;for(int i =1;i <= n;++i)for(int j =1;j <= n;++j){int mn = INF;if(aa[i][j]==0)continue;for(int k = i;k <= n;++k){
mn =min(mn,you[k][j]);if(mn >= j){
ans +=(mn - j);}elsebreak;}}return ans;}intmain(){int n;scanf("%d",&n);for(int i =1;i <= n;++i)for(int j =1;j <= n;++j)scanf("%d",&shu[i][j]);
ll ans1,ans2;for(int i =1;i <= n;++i){for(int j =1;j <= n;++j){if(shu[i][j]>=100)aa[i][j]=1;else aa[i][j]=0;}}
ans1 =solve(n);for(int i =1;i <= n;++i){for(int j =1;j <= n;++j){if(shu[i][j]>100)aa[i][j]=1;else aa[i][j]=0;}}
ans2 =solve(n);// show(ans1,ans2);printf("%lld",ans1-ans2);return0;}/**
*/
D
题意
有
N
N
N 个点,第
i
i
i 个点的位置为
A
i
A_i
Ai 如果位置为
12
X
12X
12X,其中
X
∈
N
∗
X\in N^*
X∈N∗,那么该位置有一个传送门,只能在传送门传送到另一个传送门 没有点的位置在传送门处 你一开始在位置
0
0
0,只能使用
K
K
K 次传送门,且移动只能每秒向负方向移动
1
1
1,传送不花时间 问你访问过所有点,最后回到原点的最短时间
1
≤
N
≤
65536
1\le N\le 65536
1≤N≤65536
1
≤
K
≤
N
1\le K\le N
1≤K≤N
思路
因为每个点一定在某两个相邻传送门的中间,我们按块分,算出每个点的块。我们设
[
0
,
11
]
[0,11]
[0,11] 属于第一个块,
[
12
(
X
−
1
)
,
12
X
)
[12(X-1),12X)
[12(X−1),12X) 属于第
X
X
X 个块
那么,所有的点所在的块都需要移动过,这些
12
12
12 单位时间都是必须的
传送影响的是什么呢?影响的是块与块之间的距离,你到底是传送走还是自己慢慢走,看图:
直接排序贪心,块与块之间的最大的那
K
K
K 个距离我们传送走,其他的我们就自己走 不过要稍微注意一下,因为我们还要回来原点,特判一下就行
代码
时间复杂度:
O
(
N
log
N
)
O(N\log N)
O(NlogN)
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =1e5+50;constint MOD =1e9+7;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-5;int aa[MAX];int blo[MAX];intget_zuo(int blo){return(blo +1)*12;}intget_you(int blo){return blo *12;}int id;int tmp[MAX];int cha[MAX];intmain(){int n,k;scanf("%d%d",&n,&k);for(int i =1;i <= n;++i)scanf("%d",&aa[i]);sort(aa+1,aa+1+n,greater<int>());int las =-1;int ans =0;for(int i =1;i <= n;++i){
blo[i]= aa[i]/12;if(blo[i]!= las){// 获得所有不同的块,存在 tmp[] 里面
tmp[id++]= blo[i];
las = blo[i];
ans +=12;// 这些块的12是必跑的}}if(tmp[id-1]!=0){// 如果最后一个块不包含原点,我们新加一个原点块
tmp[id++]=-1;// show("ADD");}for(int i =0;i+1< id;++i){
cha[i]=get_you(tmp[i])-get_zuo(tmp[i+1]);// 距离为左边块的右端点减去右边块的左端点// show(get_you(tmp[i]),get_zuo(tmp[i+1]));}sort(cha,cha+id-1,greater<int>());
k--;for(int i =0;i+1< id;++i){// show(cha[i]);if(k)k--;else ans += cha[i];}printf("%d",ans);return0;}/**
1 1
100
1 2
100
1 1
5
*/
F
题意
给定一个有方向的路径,长度为
S
S
S,每一步为向东南西北其中走一步 这个路径只有开头和结尾重合。问该路径是顺时针还是逆时针的
思路
看图:
我们只要知道其中一条最侧面的线段,其方向决定了该闭合路径是顺时针还是逆时针的
代码
时间复杂度:
O
(
∣
S
∣
)
O(|S|)
O(∣S∣)
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =3e2+50;constint MOD =1e9+7;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-5;intmain(){int n;scanf("%d",&n);while(n--){
string ss;int l;cin >> ss;
l = ss.size();int nowx =0;int nowy =0;int zuoy =0;int dir =0;for(int i =0;i < l;++i){if(ss[i]=='N')nowx--;elseif(ss[i]=='S')nowx++;elseif(ss[i]=='W')nowy--;elseif(ss[i]=='E')nowy++;if(nowy == zuoy){if(ss[i]=='N')dir =-1;elseif(ss[i]=='S')dir =1;}if(nowy < zuoy){
zuoy = nowy;}}if(dir ==-1)puts("CW");elseputs("CCW");}return0;}
G
题意
有
N
N
N 个点,一开始每个点值为
0
0
0 每一步,可以选择一段区间
[
L
,
R
]
[L,R]
[L,R],然后把他们的值都变为一个任意的
c
c
c 问最少步数,让第
i
i
i 个点的值为
A
i
A_i
Ai
1
≤
N
≤
300
1\le N\le 300
1≤N≤300
思路
容易想到是区间
D
P
DP
DP,但是场上对一笔画的点想错了于是只有
60
60
60 分…
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示区间
[
i
,
j
]
[i,j]
[i,j] 全部满足要求的最少步数 先枚举区间长度
L
L
L,再枚举区间左端点,同时获得了区间右端点;再枚举区间端点,很快获得很简单的维护:
d
p
[
i
]
[
j
]
=
min
{
d
p
[
i
]
[
k
]
+
d
p
[
k
+
1
]
[
j
]
}
dp[i][j]=\min\{dp[i][k]+dp[k+1][j]\}
dp[i][j]=min{dp[i][k]+dp[k+1][j]} 就是一个区间涂完,等于左边一个区间涂完步数+右边区间涂完步数
然后再考虑某一笔横穿整个区间的情况。 *由于某一笔横穿部分区间的情况在
L
L
L 较小的情况已经考虑到,因此我们只考虑某一笔横穿某个区间 也就是满足
A
i
=
A
j
A_i=A_j
Ai=Aj 此时的转移:
d
p
[
i
]
[
j
]
=
min
{
d
p
[
i
+
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
}
dp[i][j]=\min\{dp[i+1][j],dp[i][j-1]\}
dp[i][j]=min{dp[i+1][j],dp[i][j−1]}
代码
时间复杂度:
O
(
N
3
)
O(N^3)
O(N3)
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =3e2+50;constint MOD =1e9+7;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-5;int aa[MAX];int dp[MAX][MAX];intmain(){int n;scanf("%d",&n);for(int i =1;i <= n;++i)scanf("%d",&aa[i]);for(int i =1;i <= n;++i)for(int j = i;j <= n;++j)
dp[i][j]= INF;for(int i =1;i <= n;++i)dp[i][i]=1;for(int L =2;L <= n;++L){for(int i =1;i + L -1<= n;++i){int j = i + L -1;if(aa[i]== aa[j])dp[i][j]=min(dp[i+1][j],dp[i][j-1]);// 场上写成 dp[i][j]=dp[i+1][j-1]+1,明显是不对的for(int k = i;k+1<= j;++k){
dp[i][j]=min(dp[i][j],dp[i][k]+ dp[k+1][j]);}}}printf("%d",dp[1][n]);return0;}
H
题意
共
N
N
N 头牛,第
i
i
i 次加入一头牛,坐标为
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi) 问你每次加入后有多少头牛是舒服的 一头牛是舒服的,当且仅当他上下左右四个相邻位置有三头牛
1
≤
N
≤
1
0
5
1\le N\le 10^5
1≤N≤105
思路
模拟,加入牛判断它舒不舒服,以及对周围四个方向的影响
代码
时间复杂度:
O
(
N
)
O(N)
O(N)
#include<bits/stdc++.h>#defineIOSios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;typedeflonglong ll;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}constint MAX =1e3+50;constint MOD =1e9+7;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;constdouble EPS =1e-5;
bool aa[MAX][MAX];int dx[]={-1,1,0,0};int dy[]={0,0,-1,1};intshu(int x,int y){// 坐标(i,j) 相邻四个位置有几头牛int num =0;for(int i =0;i <4;++i){int tx = x + dx[i];int ty = y + dy[i];if(tx <0|| ty <0|| tx >1000|| ty >1000)continue;if(aa[tx][ty])num++;}return num;}intmain(){int ans =0;int n;scanf("%d",&n);for(int i =1;i <= n;++i){int x,y;scanf("%d%d",&x,&y);
aa[x][y]= true;for(int j =0;j <4;++j){int tx = x + dx[j];int ty = y + dy[j];if(tx <0|| ty <0|| tx >1000|| ty >1000)continue;if(!aa[tx][ty])continue;// 它不是牛就不用去check了int tmp =shu(tx,ty);if(tmp ==3)ans++;elseif(tmp ==4)ans--;}if(shu(x,y)==3)ans++;printf("%d\n",ans);}return0;}
I
题意
有
N
N
N 头牛,第
i
i
i 次加入牛的位置为
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi) 问你对于已经加入
1
∼
i
1\sim i
1∼i 头牛的情况下,最少需要加多少头牛,才能让每头牛(包括新加入的牛)都不舒服 一头牛是舒服的,当且仅当他上下左右四个相邻位置有三头牛 每一次询问是独立的
1
≤
N
≤
1
0
5
1\le N\le 10^5
1≤N≤105
思路
设
M
[
i
]
[
j
]
=
0
M[i][j]=0
M[i][j]=0 表示位置
(
i
,
j
)
(i,j)
(i,j) 什么都没有
M
[
i
]
[
j
]
=
1
M[i][j]=1
M[i][j]=1 表示该位置放了牛
M
[
i
]
[
j
]
=
2
M[i][j]=2
M[i][j]=2 表示该位置放了我们假想的牛