“这是一场难度约为 $CSP-J$ 的模拟赛”

正确的,一针见血的。这么说我都不晓得当年怎么拿的2=(悲

T1 考场上想到了前缀和这么个鬼东西,也在考虑是不是该旋转矩阵,然后发现自己完全转不来,只能随手敲个 $O(n^2k)$ 的暴力前缀和,然后 $n \leq 400$ 的数据硬是越界了8个点。高低也算个黄题吧。

T2 只记得以前做过一道类似的,但是看到超级大的 $k$ 是在想不吃什么不超时的揭解法了,随手敲了个优先队列交上去准备骗点分。黄题水准罢。

T3 一眼线段树模板题,然后我用字符串操作写了30行的代码直接跑路,要不是那个数据太坑说不准还能骗点分。可能有个绿题。

T4 是真的没头绪,大概猜出来是个 $dp$ 但是完全不知道方程怎么推,然后完全没动(悲。绿到蓝吧。

T1 sum

题意: 给定一个 $n * m$ 的矩阵,可以随意选一个点向上下左右走 $k$ 步,求取得的和的最大值。

solution:
把矩阵旋转45度后利用前缀和求解。
这个旋转的方法确实有点骚。

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
#include<bits/stdc++.h>
using namespace std;
int n,k,mp[1005][1005],pre[1005][1005],ans=-0x7fffffff,m,x,y,xr,xl,yr,yl;
int main(){
freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&mp[i][j]);
}
}
m=n*2-1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) pre[i+j-1][n-i+j]=mp[i][j];
}
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++) pre[i][j]=pre[i][j]+pre[i][j-1]+pre[i-1][j]-pre[i-1][j-1];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
x=i+j-1,y=n-i+j;
//xr=x+k;yr=y+k;yl=y-k;xl=x-k;
xr=min(m,x+k);
yr=min(m,y+k);
xl=max(1,x-k);
yl=max(1,y-k);
int cnt=pre[xr][yr]-pre[xr][yl-1]-pre[xl-1][yr]+pre[xl-1][yl-1];
//if(cnt>ans) cout<<xr<<" "<<yr<<" "<<xl<<" "<<yl<<endl;
ans=ans>cnt?ans:cnt;
}
}
printf("%d",ans);
return 0;
}


一个小设想:能不能不旋转直接前缀和求值?理论复杂度是 $O(n^2k)$ 确实只有 $WA$没有 $TLE$ 。

(30pts)

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
#include<bits/stdc++.h>
using namespace std;
int n,k,mp[1005][1005],pre[1005][1005],ans=-0x7fffffff,a,b,l,r,x,y;
int main(){
freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&mp[i][j]);
pre[i][j]=mp[i][j]+pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
a=i;b=min(j+k,n);l=0;r=2*k;x=max(0,a-l-1);y=max(0,b-r-1);
// cout<<a<<" "<<b<<" "<<l<<" "<<r<<" "<<x<<" "<<y<<endl;
int cnt=pre[a][b]-pre[x][b]-pre[a][y]+pre[x][y];
for(int p=1;p<=k;p++){
a=min(i+p,n);b=min(j+k-p,n);l=0;r=2*k-2*p;x=max(0,a-l-1);y=max(0,b-r-1);
cnt+=pre[a][b]-pre[x][b]-pre[a][y]+pre[x][y];
// cout<<a<<" "<<b<<" "<<l<<" "<<r<<" "<<x<<" "<<y<<endl;
a=max(0,i-p);b=min(j+k-p,n);l=0;r=2*k-2*p;x=max(0,a-l-1);y=max(0,b-r-1);
cnt+=pre[a][b]-pre[x][b]-pre[a][y]+pre[x][y];
// cout<<a<<" "<<b<<" "<<l<<" "<<r<<" "<<x<<" "<<y<<endl;
}
ans=cnt>ans?cnt:ans;
//cout<<ans<<"\n";
}
}
printf("%d\n",ans);
//fclose(stdin);
//fclose(stdout);
return 0;
}


T2 number

题意: 一个 $n$ 个元素的序列 $A$ 以及 $m$ 个元素的序列 $B$ ,求 第k小的 $a_i * b_j$。

solution:

50pts(考场code):因为肯定满足 $a_i b_j <a_{i+1} b_j$ 所以先将所有 $a_1 b_j$ $push$ 进优先队列,然后依次取出堆顶元素,压入 $a_{i+1} b_j$
,一直循环 $k$ 次即可。

100pts: 同样事由于具有单调性,所以可以二分答案,$check$ 利用单调性枚举即可。

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
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define in read()
const int maxn=2e5+10;
inline ll read(){
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+c-'0';
c=getchar();
}
return x*f;
}
ll n,m,k,a[maxn],b[maxn];
bool check(ll mid){
ll t=0;
ll j=m;
for(int i=1;i<=n;i++){
while(j>=1&&a[i]*b[j]>=mid) j--;
t+=j;
}
return t>=k;
}
int main(){
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
n=in;m=in;k=in;
for(int i=1;i<=n;i++) a[i]=in;
for(int i=1;i<=m;i++) b[i]=in;
sort(a+1,a+1+n);
sort(b+1,b+1+m);
ll l=0,r=a[n]*b[m]+1;
while(l+1<r){
ll mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid;
}
printf("%lld",l);
return 0;
}

T3 bigint

题意: 一个长度为 $n$ 的长整数,区间修改,区间赋值。

solution: 一眼线段树模板。只不过因为区间维护的是个整数的一段,所以需要一些小小的预处理。还有很多细节。不明白对拍出来没有问题但是 OJ 上任然是 $WA$ 。

如果你直接利用字符串的操作,也可以骗到 $50pts$ ,代码量是线段树的三分之一,何乐而不为呢?考场上10分钟打完就直接跑路,然后发现输入的 $l$ 和 $r$ 有亿点点坑。

“我们从大整数的最低位(最右边)开始标号”

不要问我怎么被坑了,问就是样例太有迷惑性了。

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
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define in read()
inline ll read(){
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+c-'0';
c=getchar();
}
return x*f;
}
const ll mod=1e9+7;
const int maxn=1e5+10;
ll opt,l,r,n,m,L,R,a[maxn],ten[maxn],kkk[maxn];
struct tree{
ll l,r,len,mul,tag;
}t[maxn<<2];
void build(ll l,ll r,ll p){
t[p].l=l;t[p].r=r;t[p].len=t[p].r-t[p].l+1;t[p].tag=-1;
if(l==r){
t[p].mul=a[l];
return ;
}
ll mid=l+r>>1;
build(l,mid,p<<1);
build(mid+1,r,p<<1|1);
t[p].mul=(t[p<<1].mul*ten[t[p<<1|1].len]%mod+t[p<<1|1].mul)%mod;
}
void pushdown(ll p){
if(t[p].tag!=-1){
t[p<<1].mul=kkk[t[p<<1].len]*t[p].tag%mod;
t[p<<1].tag=t[p].tag;
t[p<<1|1].mul=kkk[t[p<<1|1].len]*t[p].tag%mod;
t[p<<1|1].tag=t[p].tag;
t[p].tag=-1;
}
}
void update(ll p,ll l,ll r,ll v){
if(l<=t[p].l&&t[p].r<=r){
t[p].mul=kkk[t[p].len]*v%mod;
t[p].tag=v;
return ;
}
pushdown(p);
ll mid=t[p].l+t[p].r>>1;
if(r<=mid) update(p<<1,l,r,v);
else if(l>mid) update(p<<1|1,l,r,v);
else{
update(p<<1,l,mid,v);
update(p<<1|1,mid+1,r,v);
}
t[p].mul=(t[p<<1].mul*ten[t[p<<1|1].len]%mod+t[p<<1|1].mul)%mod;
}
ll query(ll l,ll r,ll p){
if(l<=t[p].l&&t[p].r<=r) return t[p].mul%mod;
pushdown(p);
ll mid=t[p].l+t[p].r>>1;
if(r<=mid) return query(l,r,p<<1);
else if(l>mid) return query(l,r,p<<1|1);
int ans1=query(l,mid,p<<1);
int ans2=query(mid+1,r,p<<1|1);
return (ans1*ten[min(t[p].r,r)-mid]%mod+ans2)%mod;
}
int main(){
//freopen("bigint.in","r",stdin);
//freopen("bigint.out","w",stdout);
n=in;m=in;
ten[0]=1;
kkk[0]=0;
for(int i=1;i<=n;i++){
ten[i]=ten[i-1]*10%mod;
kkk[i]=kkk[i-1]*10%mod+1;
char c=getchar();
a[i]=c-'0';
}
build(1,n,1);
while(m--){
opt=in;L=in;R=in;
l=n-R;r=n-L;
if(opt==1){
printf("%lld\n",query(l,r,1));
}
if(opt==2){
ll v=in;
update(1,l,r,v);
}
}
fclose(stdin);
fclose(stdout);
return 0;
}

96行,这么看起来还是字符串来的划算

字符串的算法:

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
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
string s;
ll opt,l,r,n,m,L,R;
char v;
int main(){
freopen("bigint.in","r",stdin);
freopen("bigint.out","w",stdout);
scanf("%lld%lld",&n,&m);
cin>>s;
while(m--){
scanf("%lld%lld%lld",&opt,&L,&R);
l=n-R-1;r=n-L-1;
if(opt==1){
ll res=0;
for(int i=l;i<=r;i++){
res=res*10+s[i]-'0';
res%=mod;
}
printf("%lld\n",res);
}
if(opt==2){
int ch;
scanf("%d",&ch);
v=ch+'0';
for(int i=l;i<=r;i++) s[i]=v;
}
}
fclose(stdin);
fclose(stdout);
return 0;
}

T4 array

题意: 有一个数组 $A$ ,可以随意修改 $k$ 个值,最小化 $T(A)=max_{i-2}^n|A_i-A_{i-1}|$ 的值。

solution: 看到题我还以为是差分。玄学 $dp$ (确信 。首先考虑最大化最小值,毫无疑问用二分(机房$dalao$原话),然后考虑用 $dp$ 来做 $check$ ,很明显的一点,对于两点 $i,j$ ,如果不需要修改,那么他们的计算结果一定小于等于二分值 $mid*(j-i)$ ,所以我们从最坏情况开始向前枚举。那么最坏情况就是修改所有点,我们记录 $dp_i$ 表示前i个全部修改,后 $i+1~n$ 的点合法最小代价。然后从 $i+1$ 向后枚举,如果枚举到一个点满足段首所述性质,那么就修改两个点之间的所有点。最后看一下当前代价是否小于等于 $k$ 即可。这个 $dp$ 是真的毒瘤。

代码就将就理解吧,因为我当时候也没太听懂,现在又要去刷题了(逃

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
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define in read()
inline ll read(){
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+c-'0';
c=getchar();
}
return x*f;
}
ll n,k,a[2005],dp[2005];
bool check(ll m){
for(int i=n;i>=1;i--){
dp[i]=n-i-1;
for(int j=i+1;j<=n;j++){
if(abs(a[j]-a[i])<=m*(j-i)) dp[i]=min(dp[i],dp[j]+j-i-1);
}
if(dp[i]+i<=k) return true;
}
return false;
}
int main(){
freopen("array.in","r",stdin);
freopen("array.out","w",stdout);
n=in;k=in;
for(int i=1;i<=n;i++) a[i]=in;
ll l=0,r=2e9,ans=r;
while(l<=r){
ll mid=l+r>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%lld",ans);
fclose(stdin);
fclose(stdout);
return 0;
}