视频讲解:BV1bU4y1q7CB
A. Grass Field
题目大意
给定 2 × 2 2\times 2 2×2 的01矩阵,每次操作可以将一行和一列的 1 1 1 变成 0 0 0 ,求最少需要几次操作,得到全零矩阵。
题解
简单模拟。
如果总和为
4
4
4 则需
2
2
2 次。
如果总和为
0
0
0 则需
0
0
0 次。
其余为
1
1
1 次。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int T,a,b,c,d,sum;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d%d",&a,&b,&c,&d);
sum=a+b+c+d;
if(sum==4)
puts("2");
else if(sum==0)
puts("0");
else
puts("1");
}
}
B. Permutation
题目大意
对于排列 p p p 和给定正整数 d d d ,定义排列 p p p 的费用为满足 p i ⋅ d = p i + 1 p_i\cdot d=p_{i+1} pi⋅d=pi+1 的 i ( 1 ≤ i < n ) i(1 \leq i <n) i(1≤i<n) 的数量。
现在给定 n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 ) n(2 \leq n \leq 2 \cdot 10^5) n(2≤n≤2⋅105) ,求出一组费用最大的正整数 d d d 和排列 p p p 。
题解
首先可以想到,最优的
d
d
d 为
2
2
2 。
当
n
=
6
n=6
n=6 时,一组最优解为
1 2 4 3 6 5
因此从小到大枚举,对于未出现过的数,逐个输出其 2 2 2 倍即可。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=200200;
int vis[MAXN];
int main()
{
int T,n,i,j;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
memset(vis,0,sizeof(vis));
puts("2");
for(i=1;i<=n;i++)
{
for(j=i;j<=n&&!vis[j];j*=2)
{
printf("%d ",j);
vis[j]=1;
}
}
puts("");
}
}
C. Schedule Management
题目大意
给定 n n n 个工人和 m m m 个任务。第 i i i 个任务如果由 a i a_i ai 号工人完成,消耗 1 1 1 小时吗,其他工人完成消耗 2 2 2 小时。
所有工人同时并行独立开始工作,每名工人同时只能完成一个工作。
求所有任务完成的最少消耗时间。
题解
直接二分答案。
注意开long long。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=200200;
int n,m;
int num[MAXN];
bool judge(int limit)
{
ll undo=0;
for(int i=1;i<=n;i++)
{
if(num[i]<=limit)
undo-=(limit-num[i])/2;
else
undo+=num[i]-limit;
}
return undo<=0;
}
int main()
{
int T,x,i,lef,rig,mid;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
num[i]=0;
for(i=1;i<=m;i++)
{
scanf("%d",&x);
num[x]++;
}
lef=0;rig=2*m;
while(lef<=rig)
{
mid=(lef+rig)/2;
if(judge(mid))
rig=mid-1;
else
lef=mid+1;
}
printf("%d\n",lef);
}
}
D. Permutation Restoration
题目大意
数组
b
b
b 由排列
a
a
a 通过
b
i
=
⌊
i
a
i
⌋
b_i=\lfloor \frac{i}{a_i} \rfloor
bi=⌊aii⌋ 得到。
给定长度为
n
(
1
≤
n
≤
5
⋅
1
0
5
)
n(1 \leq n \leq 5 \cdot 10^5)
n(1≤n≤5⋅105) 的
b
b
b ,求出任意一个合法的
a
a
a 排列。
题解
由
b
i
=
⌊
i
a
i
⌋
b_i=\lfloor \frac{i}{a_i} \rfloor
bi=⌊aii⌋ 可得
a
i
b
i
≤
i
≤
a
i
(
b
i
+
1
)
−
1
a_i b_i \leq i \leq a_i(b_i+1)-1
aibi≤i≤ai(bi+1)−1
当
b
i
=
0
b_i=0
bi=0 时
a
i
≥
⌈
i
+
1
b
i
+
1
⌉
=
⌊
i
+
b
i
+
1
b
i
+
1
⌋
a_i\geq \lceil \frac{i+1}{b_i+1} \rceil=\lfloor \frac{i+b_i+1}{b_i+1} \rfloor
ai≥⌈bi+1i+1⌉=⌊bi+1i+bi+1⌋
当
b
i
≠
0
b_i \neq 0
bi=0 时
⌊
i
+
b
i
+
1
b
i
+
1
⌋
≤
a
i
≤
⌊
i
b
i
⌋
\lfloor \frac{i+b_i+1}{b_i+1} \rfloor \leq a_i \leq \lfloor \frac{i}{b_i} \rfloor
⌊bi+1i+bi+1⌋≤ai≤⌊bii⌋
由此,对于排列
a
a
a 上的每个位置,选择一个区间内的数填充。这是经典的贪心问题。
将所有区间按右端点排序,对于每个区间,选择区间内未被选中过的最小的数,填充到对应位置上。
选择区间内未被选中过的最小的数,可以采用set+lower_bound实现。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=500500;
int a[MAXN];
set<int> st;
set<int>::iterator it;
struct Node
{
int l,r,id;
}seg[MAXN];
bool cmp(Node n1,Node n2)
{
if(n1.r!=n2.r)
return n1.r<n2.r;
else
return n1.l<n2.l;
}
int main()
{
int T,n,i,x;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
st.clear();
for(i=1;i<=n;i++)
{
scanf("%d",&x);
seg[i].id=i;
seg[i].l=(i+1+x)/(x+1);
if(x)
seg[i].r=i/x;
else
seg[i].r=n+1;
st.insert(i);
}
sort(seg+1,seg+n+1,cmp);
for(i=1;i<=n;i++)
{
it=st.lower_bound(seg[i].l);
a[seg[i].id]=*it;
st.erase(it);
}
for(i=1;i<=n;i++)
printf("%d ",a[i]);
puts("");
}
}
E. Text Editor
题目大意
在文本编辑器上,通过若干次操作,将长度为
n
n
n 的字符串
s
s
s 删减为 长度为
m
m
m 的字符串
t
t
t 。
1
≤
m
≤
n
≤
5000
1 \leq m \leq n \leq 5000
1≤m≤n≤5000
初始光标在末尾,每次操作,可以选择以下动作之一:
- 将光标左移一位
- 将光标右移一位
- 将光标置于最左边
- 将光标置于最右边
- 删除光标左边的字符
求最少操作数。
题解
首先操作4将光标置于最右边,肯定是没用的。
关键考虑操作3将光标置于最左边。
设在 p o s pos pos 字符前,将光标置于最左边。此时 s [ p o s , n ] s[pos,n] s[pos,n] 与 t t t 的某一后缀匹配,花费为 n − p o s + 1 n-pos+1 n−pos+1 ,最长匹配长度可以在 O ( N ) O(N) O(N) 复杂度内求出,设 s u f i suf_i sufi 表示 s [ i , n ] s[i,n] s[i,n] 与 t t t 的后缀匹配的最大长度。
若 s u f 1 ≠ m suf_1\neq m suf1=m ,则无解输出 − 1 -1 −1 。
然后进行操作3将光标置于最左边。
考虑求解
s
[
1
,
i
]
s[1,i]
s[1,i] 与
t
[
1
,
j
]
t[1,j]
t[1,j] 匹配的最小代价,此时需满足后半部分也能对应匹配,即
s
u
f
i
+
1
+
j
>
=
m
suf_{i+1}+j>=m
sufi+1+j>=m 。
如果直接考虑删除,需要右移光标再删除,花费为
2
2
2 。
如果只是右移光标,花费为
1
1
1 。
如果
s
[
1
,
i
]
s[1,i]
s[1,i] 和
t
[
1
,
j
]
t[1,j]
t[1,j] 的后缀匹配,则可以剩下最后一段的右移光标操作。
设
d
p
i
,
j
dp_{i,j}
dpi,j 表示
s
[
1
,
i
]
s[1,i]
s[1,i] 和
t
[
1
,
j
]
t[1,j]
t[1,j] 的最长公共后缀长度,可以通过以下转移式或Z函数得到
d
p
i
,
j
=
{
d
p
i
−
1
,
j
−
1
+
1
s
i
=
s
j
0
s
i
≠
s
j
dp_{i,j}=\begin{cases} dp_{i-1,j-1}+1 & s_i=s_j\\ 0 & s_i\neq s_j \end{cases}
dpi,j={dpi−1,j−1+10si=sjsi=sj
如果有操作3将光标置于最左边,则花费为
1
+
2
i
−
j
−
d
p
i
,
j
+
n
−
i
1+2i-j-dp_{i,j}+n-i
1+2i−j−dpi,j+n−i 。
如果没有,则花费为
n
−
i
n-i
n−i
枚举所有 i , j i,j i,j ,求出最小花费即可。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=5050;
const int inf=1<<30;
char s[MAXN],t[MAXN];
int dp[MAXN][MAXN],suf[MAXN];
int main()
{
int T,n,m,i,j,now,ans,com;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
scanf("%s",s+1);
scanf("%s",t+1);
com=0;
for(i=n;i>=1;i--)
{
if(com<m&&s[i]==t[m-com])
com++;
suf[i]=com;
}
if(suf[1]!=m)
{
puts("-1");
continue;
}
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
{
if(s[i]==t[j])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=0;
}
ans=1e9;
for(j=0;j<=m;j++)
{
com=0;
for(i=0;i<=n;i++)
{
if(com<j&&s[i]==t[com+1])
com++;
if(com==j&&suf[i+1]+j>=m)
{
if(i==j)
ans=min(ans,n-i);
else
ans=min(ans,1+2*i-j-dp[i][j]+n-i);
}
}
}
printf("%d\n",ans);
}
}
F. Points
题目大意
对于三元组
i
,
j
,
k
i,j,k
i,j,k ,若
i
<
j
<
k
i<j<k
i<j<k 且
k
−
i
≤
d
k-i\leq d
k−i≤d ,则称其为漂亮的。
给定一个初始为空的集合,维护以下操作:
- 输入一个 a i a_i ai 。若 a i a_i ai 在集合中,则删去。反之添加到集合。
- 添加或删除后,输出总的漂亮三元组数。
题解
首先注意到,每个值必定是唯一的。
设
f
i
f_i
fi 表示有多少
j
j
j 点在
[
i
+
1
,
i
+
d
]
[i+1,i+d]
[i+1,i+d] 范围内。
最终答案为
∑
i
f
i
2
−
f
i
2
\sum_i \frac{f_i^2-f_i}{2}
∑i2fi2−fi
采用矩阵+线段树,维护 f i 2 f_i^2 fi2 和 f i f_i fi 即可。
参考代码引用ZongDeChuangSiYiGeBa 的AC代码
参考代码
#include<bits/stdc++.h>
#define For(i,x,y) for (int i=(x);i<=(y);i++)
#define FOR(i,x,y) for (int i=(x);i<(y);i++)
#define Dow(i,x,y) for (int i=(x);i>=(y);i--)
#define mp make_pair
#define fi first
#define se second
#define pb push_back
#define ep emplace_back
#define siz(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
#define fil(a,b) memset((a),(b),sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pa;
typedef pair<ll,ll> PA;
typedef vector<int> poly;
inline ll read(){
ll x=0,f=1;char c=getchar();
while ((c<'0'||c>'9')&&(c!='-')) c=getchar();
if (c=='-') f=-1,c=getchar();
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
const int N = 2e5+10;
int q,n=2e5,d;
bool vis[N];
ll s0[N<<2],s1[N<<2],s2[N<<2],tag[N<<2];
inline void push_up(int u){
s0[u]=s0[u<<1]+s0[u<<1^1];
s1[u]=s1[u<<1]+s1[u<<1^1];
s2[u]=s2[u<<1]+s2[u<<1^1];
}
inline void upd(int u,ll x){
s2[u]+=2*x*s1[u]+x*x*s0[u];
s1[u]+=x*s0[u];
tag[u]+=x;
}
inline void push_down(int u){
if (tag[u]){
upd(u<<1,tag[u]);
upd(u<<1^1,tag[u]);
tag[u]=0;
}
}
inline void Add(int u,int l,int r,int ql,int qr,ll x){
if (l>=ql&&r<=qr) return upd(u,x),void(0);
int mid=l+r>>1;push_down(u);
if (ql<=mid) Add(u<<1,l,mid,ql,qr,x);
if (qr>mid) Add(u<<1^1,mid+1,r,ql,qr,x);
push_up(u);
}
inline void update(int u,int l,int r,int ql,ll x,ll y){
if (l==r){
s0[u]=x,s1[u]=x*y,s2[u]=x*y*y;
return;
}
int mid=l+r>>1;push_down(u);
if (ql<=mid) update(u<<1,l,mid,ql,x,y);
else update(u<<1^1,mid+1,r,ql,x,y);
push_up(u);
}
inline int Query(int u,int l,int r,int ql,int qr){
if (l>=ql&&r<=qr) return s0[u];
int mid=l+r>>1,ret=0;push_down(u);
if (ql<=mid) ret+=Query(u<<1,l,mid,ql,qr);
if (qr>mid) ret+=Query(u<<1^1,mid+1,r,ql,qr);
return ret;
}
int main(){
q=read(),d=read();
For(i,1,q){
int x=read();
if (!vis[x]){
ll tmp=Query(1,1,n,x,min(x+d,n));
update(1,1,n,x,1,tmp);
if (x>1) Add(1,1,n,max(1,x-d),x-1,1);
vis[x]=1;
} else {
ll tmp=Query(1,1,n,x,min(x+d,n));
update(1,1,n,x,0,0);
if (x>1) Add(1,1,n,max(1,x-d),x-1,-1);
vis[x]=0;
}
ll ans=(s2[1]-s1[1])/2;
printf("%lld\n",ans);
}
}