Codeforces Round 775 A ~ D 题解

A. Weird Sum

题意

给定一个 m×n 的矩阵,求任意一对值相同的元素的曼哈顿距离和。

m×n105

解法

枚举值,取出所有这个值的元素,用 vector 记录即可。x 从小到大扫描,依次加入元素,用树状数组维护当前元素和前面所有的和。具体的,树状数组以 y 为下标,分别记录元素个数、y 坐标加 x 坐标、y 坐标减 x 坐标,分别计算 y 坐标比当前元素小的和大的贡献。

code

code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1000001;
int m,n;
ll T1[N],T2[N],T3[N];
void add(ll *T,int x,ll k)
{
while(x<=n) T[x]+=k,x+=x&-x;
}
ll sum(ll *T,int x)
{
ll s=0;
while(x>=1) s+=T[x],x-=x&-x;
return s;
}
struct str
{
int x,y;
str(){}
str(int x,int y):x(x),y(y){}
};
vector<str> a[N];
int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=m;++i)
{
for(int j=1;j<=n;++j)
{
int x;
scanf("%d",&x);
a[x].push_back(str(i,j));
}
}
ll s=0;
for(int i=1;i<=100000;++i)
{
for(auto j:a[i])
{
s+=sum(T1,j.y)*(j.x+j.y)-sum(T2,j.y);
s+=(sum(T3,n)-sum(T3,j.y))-(sum(T1,n)-sum(T1,j.y))*(j.y-j.x);
add(T1,j.y,1);
add(T2,j.y,j.y+j.x);
add(T3,j.y,j.y-j.x);
}
for(auto j:a[i])
{
add(T1,j.y,-1);
add(T2,j.y,-(j.y+j.x));
add(T3,j.y,-(j.y-j.x));
}
}
printf("%lld",s);
return 0;
}

B. Integral Array

题意

给定一个数组 a,求是否对于任意 a 中的两个元素 x,y,xyxy 都出现在数组中。

n106,1ai106

解法

考虑枚举没有出现在数组中的数 z,如果数组中存在 x,y,xy 满足 xy=z,这个数组就不不合法。考虑枚举 y,判断是否存在 x[yz,(y+1)z1] 即可。由于 1yxz,所以 y 的总个数是调和级数。

code

code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2000001;
int n,m,a[N],b[N],c[N];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) b[i]=c[i]=0;
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
++b[a[i]];
}
for(int i=1;i<=m;++i) c[i]=c[i-1]+b[i];
sort(a+1,a+n+1);
bool u=true;
for(int i=1;i<=m;++i)
{
if(b[i]!=0) continue;
for(int j=1;j<=m/i;++j)
{
if(b[j]&&c[min(j*(i+1)-1,m)]-c[j*i-1]>0)
{
u=false;
break;
}
}
if(!u) break;
}
if(u) printf("Yes\n");
else printf("No\n");
}
return 0;
}

C. Tyler and Strings

题意

给定两个字符串 st,求重排 s 后有多少种情况 s 的字典序小于 t

|s|,|t|2×105,字符集 2×105

解法

枚举 s 第一个比 t 小的地方 i[1,i1] 的部分 st 一定相同,如果无法满足情况数为 0,下面讨论的情况已钦定此条件满足。如果能够只需要满足 si<ti,后面任意。假如我们钦定了 i 选什么,后面的总情况就是 w=(aj)!(aj!)1ai 为字符 i 剩下的个数。考虑上 si,选定 si 的影响就是 asi1,总情况变为 w=((aj)1)!((aj[j=si])!)1,可以发现对于不同的 si,变化的项只有 1 个,我们令 w=((aj)1)!(aj!)1,选定 si 的贡献即为 w=w×(aj!)×((aj1)!)1,我们只需要统计后面这一块的和 t 即可,用树状数组维护。

i 每向后移动一位,asi1 都会减少 1wt 也会相应改变,但是每次只改变一项,直接维护即可。

code

code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1000001;
const ll P=998244353;
int m,n,a[N],b[N],h[N];
ll jc[N],T[N];
void exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0) x=1,y=0;
else exgcd(b,a%b,y,x),y-=a/b*x;
}
ll inv(ll a)
{
ll x,y;
exgcd(a,P,x,y);
x=(x%P+P)%P;
return x;
}
void add(int x,ll k)
{
while(x<=200000) T[x]+=k,x+=x&-x;
}
ll sum(int x)
{
ll s=0;
while(x>=1) s+=T[x],x-=x&-x;
return s;
}
int main()
{
jc[0]=1;
for(int i=1;i<=200000;++i) jc[i]=(jc[i-1]*i)%P;
scanf("%d%d",&m,&n);
for(int i=1;i<=m;++i)
{
int x;
scanf("%d",&x);
++a[x];
}
for(int i=1;i<=n;++i)
{
scanf("%d",&b[i]);
}
ll w=jc[m];
for(int i=1;i<=200000;++i)
{
if(a[i]>0)
{
add(i,jc[a[i]]*inv(jc[a[i]-1])%P);
w=w*inv(jc[a[i]])%P;
}
}
ll s=0;
for(int i=1;i<=min(m,n);++i)
{
w=w*inv(jc[m-i+1])%P*jc[m-i]%P;
s=(s+w*sum(b[i]-1)%P)%P;
if(a[b[i]]>0)
{
w=w*jc[a[b[i]]]%P;
add(b[i],-(jc[a[b[i]]]*inv(jc[a[b[i]]-1])%P));
--a[b[i]];
if(a[b[i]]>0)
{
add(b[i],jc[a[b[i]]]*inv(jc[a[b[i]]-1])%P);
w=w*inv(jc[a[b[i]]])%P;
}
}
else
{
w=0;
break;
}
}
if(m<n&&w!=0) s=(s+1)%P;
printf("%lld",s);
return 0;
}

D. Serious Business

题意

给定一个 3×n 的矩阵,每个位置有一个值。第一行和第三行可以走,第二行不可以。共有 q 个操作,第 i 个操作可以用 ki 的代价把第二行 [li,ri] 变为可以走,求经过的位置的价值和减去总代价的最大值。

解法

显然在第二行经过的位置是一段连续的区间,所以选择的操作的区间也是连续的,且除了第一段和最后一段区间之外全部会走满。我们把总价值拆成两部分:albl1br+(cncr1),其中 a,b,c 分别是三行的价值, l,r 是进入和离开第二行的位置。我们考虑把第一部分贡献转移到最后一段区间:首先把 ajbj1 转移到 fj,由于第一个和中间的区间都是走到了区间末尾的,所以区间 i 的贡献就是把 maxlijrifjki 贡献到 fri。用线段树维护。

然后我们需要把在同一个区间的两种贡献合并,也就是对于区间 i,对于任意 j,p[li,ri],jp,把 fj+bp+(cncp1)pi 贡献到总答案。这里需要保证 jp,也可以用线段树维护,每次把左子树最大的 fj 和右子树最大的 bp+(cpcp1) 相加贡献到答案即可。

code

code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1000001;
int n,m;
ll a[N],b[N],c[N];
struct str
{
int x,y,k;
str(){}
str(int x,int y,int k):x(x),y(y),k(k){}
};
vector<str> d[N],e;
struct tree
{
int l,r;
ll s,t1,t2,s2;
}T[N<<2];
void pushup(tree &T,tree T1,tree T2)
{
T.s=max(T1.s,T2.s);
T.s2=max(T1.t1+T2.t2,max(T1.s2,T2.s2));
T.t1=max(T1.t1,T2.t1);
T.t2=max(T1.t2,T2.t2);
}
void build(int x,int l,int r)
{
T[x].l=l;
T[x].r=r;
if(l==r)
{
T[x].s=a[l]-b[l-1];
T[x].t1=a[l]-b[l-1];
T[x].t2=b[l]+(c[n]-c[l-1]);
T[x].s2=T[x].t1+T[x].t2;
return;
}
int z=l+r>>1;
build(x<<1,l,z);
build(x<<1|1,z+1,r);
pushup(T[x],T[x<<1],T[x<<1|1]);
}
void add(int x,int q,ll k)
{
if(T[x].l==T[x].r)
{
T[x].s=max(T[x].s,k);
T[x].t1=max(T[x].t1,k);
T[x].s2=T[x].t1+T[x].t2;
return;
}
int z=T[x].l+T[x].r>>1;
if(q<=z) add(x<<1,q,k);
else add(x<<1|1,q,k);
pushup(T[x],T[x<<1],T[x<<1|1]);
}
ll sum(int x,int l,int r)
{
if(T[x].l>=l&&T[x].r<=r)
{
return T[x].s;
}
int z=T[x].l+T[x].r>>1;
ll s=-1e18;
if(l<=z) s=max(s,sum(x<<1,l,r));
if(r>z) s=max(s,sum(x<<1|1,l,r));
return s;
}
tree sum2(int x,int l,int r)
{
if(T[x].l>=l&&T[x].r<=r)
{
return T[x];
}
int z=T[x].l+T[x].r>>1;
tree s;
if(l<=z&&(!(r>z))) s=sum2(x<<1,l,r);
else if((!(l<=z))&&r>z) s=sum2(x<<1|1,l,r);
else pushup(s,sum2(x<<1,l,r),sum2(x<<1|1,l,r));
return s;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%lld",&a[i]);
a[i]+=a[i-1];
}
for(int i=1;i<=n;++i)
{
scanf("%lld",&b[i]);
b[i]+=b[i-1];
}
for(int i=1;i<=n;++i)
{
scanf("%lld",&c[i]);
c[i]+=c[i-1];
}
for(int i=1;i<=m;++i)
{
int x,y,k;
scanf("%d%d%d",&x,&y,&k);
d[y].push_back(str(x,y,k));
e.push_back(str(x,y,k));
}
build(1,1,n);
for(int i=1;i<=n;++i)
{
ll s=-1e18;
for(auto j:d[i])
{
s=max(s,sum(1,j.x,j.y)-j.k);
}
if(i<n) add(1,i+1,s);
}
ll s=-1e18;
for(auto i:e)
{
s=max(s,sum2(1,i.x,i.y).s2-i.k);
}
printf("%lld",s);
return 0;
}