A. Mind Control
略
B. Irreducible Anagrams
略
C. Prefix Enlightenment
略
D. Coffee Varieties (hard version)
vp的时候智障的算错了常数,写了一个操作次数
2
n
2
k
\frac{2n^2}{k}
k2n2WA了,然后不想改了(虽然区别不大)。
考虑分治,定义solve(l,r)返回一个
l
∼
r
l\sim r
l∼r间的下标集合,使得
[
l
,
r
]
[l,r]
[l,r]中每种不同权值恰好在这个下标集合中对应出现一次。
令
l
e
n
=
r
−
l
+
1
len=r-l+1
len=r−l+1,这里有
l
e
n
=
2
c
len=2^c
len=2c,当
l
e
n
=
k
len=k
len=k的时候我们可以直接
k
k
k次操作求解(每次加入一个看是否出现过,操作完后要清空)。
对于
l
e
n
>
k
len>k
len>k的,相当于要合并两个下标集合,其实只需要把两个集合并起来,再删去同时出现在两边的权值的其中一次出现即可。
一个暴力的实现是将左右下标集合各自
k
2
\frac{k}{2}
2k个分一块,每次取出一个左边的块插入所有数字,再取出一个右边的块依次插入看有没有出现过即可(操作完后要清空)。这样单层最多要
l
e
n
2
k
\frac{len^2}{k}
klen2次操作,易知总操作次数上界为
2
n
2
k
\frac{2n^2}{k}
k2n2。
考虑稍微优化一下,仍然
k
2
\frac{k}{2}
2k个分一块,但是不急着清空。每次取出左边两个块和右边两个块,先加入左边第一个块,然后插入右边第一个块并查看是否出现过,再插入左边第二个块并查看是否出现过,再插入右边第二个块并查看是否出现过,最后再插一次左边第一个块。这样利用了只保留最后
k
k
k个的特性,不会同时存在自身的两次插入。注意最后剩下的散块要暴力处理。
简单分析可知这样的操作次数上界为
8
n
2
5
+
O
(
n
)
\frac{8n^2}{5}+\mathcal O(n)
58n2+O(n)的,时间复杂度为
O
(
n
2
)
\mathcal O(n^2)
O(n2)。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
int val[1025],q[1000005];
int L=1,R=0;
int vis[1025];
int n,k,sz;
int query(int y) {
if (!y) {
/*L=1;
R=0;
memset(vis,0,sizeof(vis));
return 0;*/
puts("R");
fflush(stdout);
return 0;
}
else {
sz++;
assert(sz<=(3*n*n)/(2*k));
/*bool v=vis[val[y]];
vis[val[y]]++;
q[++R]=y;
if (R-L+1>k) {
vis[val[q[L]]]--;
L++;
}
return v;*/
printf("? %d\n",y);
fflush(stdout);
char str[5];
scanf("%s",str);
return (str[0]=='Y');
}
}
vector<int> solve(int l,int r) {
if (r-l+1==k) {
vector<int> vt;
for(int i=l;i<=r;i++)
if (!query(i)) vt.push_back(i);
query(0);
return vt;
}
int m=((l+r)>>1);
vector<int> a=solve(l,m);
vector<int> b=solve(m+1,r);
bool cur[1025];
memset(cur,0,sizeof(cur));
for(int i=0;i<a.size();i+=k)
for(int j=0;j<b.size();j+=k) {
if (i+k-1>=a.size()||j+k-1>=b.size()) {
for(int l=i;l<i+(k>>1)&&l<a.size();l++) query(a[l]);
for(int l=j;l<j+(k>>1)&&l<b.size();l++)
if (query(b[l])) cur[b[l]]=1;
query(0);
for(int l=i;l<i+(k>>1)&&l<a.size();l++) query(a[l]);
for(int l=j+(k>>1);l<j+k&&l<b.size();l++)
if (query(b[l])) cur[b[l]]=1;
query(0);
for(int l=i+(k>>1);l<i+k&&l<a.size();l++) query(a[l]);
for(int l=j;l<j+(k>>1)&&l<b.size();l++)
if (query(b[l])) cur[b[l]]=1;
query(0);
for(int l=i+(k>>1);l<i+k&&l<a.size();l++) query(a[l]);
for(int l=j+(k>>1);l<j+k&&l<b.size();l++)
if (query(b[l])) cur[b[l]]=1;
query(0);
}
else {
for(int l=i;l<i+(k>>1);l++) query(a[l]);
for(int l=j;l<j+(k>>1);l++)
if (query(b[l])) cur[b[l]]=1;
for(int l=i+(k>>1);l<i+k;l++)
if (query(a[l])) cur[a[l]]=1;
for(int l=j+(k>>1);l<j+k;l++)
if (query(b[l])) cur[b[l]]=1;
for(int l=i;l<i+(k>>1);l++)
if (query(a[l])) cur[a[l]]=1;
query(0);
}
}
vector<int> vt;
for(int i=0;i<a.size();i++)
if (!cur[a[i]]) vt.push_back(a[i]);
for(int i=0;i<b.size();i++)
if (!cur[b[i]]) vt.push_back(b[i]);
return vt;
}
int main() {
scanf("%d%d",&n,&k);
//for(int i=1;i<=n;i++) scanf("%d",&val[i]);
if (k==1) {
int s=0;
for(int i=1;i<=n;i++) {
bool ok=1;
for(int j=i+1;j<=n;j++) {
query(i);
if (query(j)) ok=0;
query(0);
if (!ok) break;
}
s+=ok;
}
printf("! %d\n",s);
fflush(stdout);
return 0;
}
vector<int> ans=solve(1,n);
printf("! %d\n",(int)ans.size());
fflush(stdout);
return 0;
}
/*
4 2
1 4 1 3
*/
E. Cartesian Tree
答案等价于计算点对
(
i
,
j
)
(i,j)
(i,j)的数目,使得点
j
j
j在点
i
i
i的子树中。根据笛卡尔树的性质,
j
j
j在
i
i
i的子树中当且仅当在序列上
i
i
i与
j
j
j所在位置之间最大值为
i
i
i。
考虑分治,对于一个最终序列中的区间
[
l
,
r
]
[l,r]
[l,r],求出
[
l
,
m
i
d
]
[l,mid]
[l,mid]与
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r]之间的贡献。根据上面的讨论,这里显然只需要关注在区间中的元素,且可以发现一个右边的点
a
j
a_j
aj会在左边的点
a
i
a_i
ai的子树中当且仅当
a
i
=
max
k
=
i
m
i
d
a
k
a_i=\max_{k=i}^{mid}a_k
ai=maxk=imidak且
a
i
>
max
k
=
m
i
d
+
1
j
a
k
a_i>\max_{k=mid+1}^{j}a_k
ai>maxk=mid+1jak,反之亦然。那么可以发现一个左边的元素
a
i
a_i
ai会与右边的点有贡献当且仅当它是
[
i
,
m
i
d
]
[i,mid]
[i,mid]中的最大值,且有贡献的
j
j
j是右边的前缀。
考虑按插入时间从小到大维护对答案的贡献。维护两个从中间向两侧的元素单调栈(即保存左边的后缀最大值和右边的前缀最大值),同时用树状数组维护当前所有元素,支持查询区间元素个数。不放设现在在左边插入了一个最大值
a
x
a_x
ax,那么我们会从左边的单调栈中弹出所有在
x
x
x左侧的后缀最大值,并减去它们在右边的子树大小和,这可以在右边的单调栈中二分出它们在右边的贡献前缀,并用树状数组查询个数完成。同时也要减去右边的前缀最大值对左边的贡献,这部分可以考虑反过来统计,由于
a
i
a_i
ai在右边的祖先数目只跟
max
j
=
i
m
i
d
a
j
\max_{j=i}^{mid}a_j
maxj=imidaj有关,那么可以对刚刚弹出的后缀最大值划分出的每一段分别计算,用树状数组算出左边的个数,在右边单调栈二分即可。最后还要记得加上
x
x
x在右边的子树大小,也即右边当前元素个数。
时间复杂度为
O
(
n
log
2
n
)
\mathcal O(n\log^2n)
O(nlog2n)。
#include <bits/stdc++.h>
#define lowbit(x) (x&-x)
using namespace std;
typedef long long ll;
int sumv[200005];
void add(int x,int n,int num) {
for(;x<=n;x+=lowbit(x)) sumv[x]+=num;
}
int sum(int x) {
int s=0;
for(;x;x-=lowbit(x)) s+=sumv[x];
return s;
}
int sum(int l,int r) {
return sum(r)-sum(l-1);
}
int num[200005],pos[200005],n;
ll ans[200005];
int stl1[200005],stl2[200005];
int str1[200005],str2[200005];
int cur[200005];
void solve(int l,int r) {
if (l==r) return;
int m=((l+r)>>1);
solve(l,m);
solve(m+1,r);
int top1=0,top2=0,sz=0;
for(int i=l;i<=r;i++) cur[++sz]=num[i];
sort(cur+1,cur+sz+1);
int lsz=0,rsz=0;
for(int i=1;i<=sz;i++) {
int x=cur[i],y=pos[x];
if (y<=m) {
lsz++;
int rx=upper_bound(stl2+1,stl2+top1+1,-y)-stl2-1;
if (rsz) {
for(int j=rx+1;j<=top1;j++) {
int t=upper_bound(str1+1,str1+top2+1,stl1[j])-str1;
if (t>top2) ans[x]-=sum(m+1,r);
else if (t) ans[x]-=sum(m+1,str2[t]-1);
if (t<=top2) ans[x]-=(ll)sum(((j==top1)?l:-stl2[j+1]+1),-stl2[j])*(top2-t+1);
}
if (rx) {
int t=upper_bound(str1+1,str1+top2+1,stl1[rx])-str1;
if (t<=top2) ans[x]-=(ll)sum(((rx==top1)?l:-stl2[rx+1]+1),y)*(top2-t+1);
}
ans[x]+=rsz;
}
top1=rx+1;
stl1[top1]=x;
stl2[top1]=-y;
}
else {
rsz++;
int rx=upper_bound(str2+1,str2+top2+1,y)-str2-1;
if (lsz) {
for(int j=rx+1;j<=top2;j++) {
int t=upper_bound(stl1+1,stl1+top1+1,str1[j])-stl1;
if (t>top1) ans[x]-=sum(l,m);
else if (t) ans[x]-=sum(-stl2[t]+1,m);
if (t<=top1) ans[x]-=(ll)sum(str2[j],((j==top2)?r:str2[j+1]-1))*(top1-t+1);
}
if (rx) {
int t=upper_bound(stl1+1,stl1+top1+1,str1[rx])-stl1;
if (t<=top1) ans[x]-=(ll)sum(y,((rx==top2)?r:str2[rx+1]-1))*(top1-t+1);
}
ans[x]+=lsz;
}
top2=rx+1;
str1[top2]=x;
str2[top2]=y;
}
add(y,n,1);
}
for(int i=l;i<=r;i++) add(i,n,-1);
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&num[i]);
pos[num[i]]=i;
}
solve(1,n);
for(int i=1;i<=n;i++) ans[i]+=ans[i-1];
for(int i=1;i<=n;i++) printf("%lld\n",ans[i]+i);
return 0;
}
F. Making Shapes
首先可以发现答案相当于统计所有可表示出的横纵坐标差
≤
m
\leq m
≤m的凸多边形数目。由于没有平行向量,因此只需要考虑每种向量所用的数目。那么可以发现,这等价于统计这样的集合
(
c
1
,
c
2
,
⋯
,
c
n
)
(c_1,c_2,\cdots,c_n)
(c1,c2,⋯,cn)的数目(
c
i
≥
0
c_i\geq 0
ci≥0且
c
i
c_i
ci不全为
0
0
0),使得:
{
∑
x
i
>
0
c
i
x
i
=
−
∑
x
i
<
0
c
i
x
i
∑
y
i
>
0
c
i
y
i
=
−
∑
y
i
<
0
c
i
y
i
∑
x
i
>
0
c
i
x
i
≤
m
∑
y
i
>
0
c
i
y
i
≤
m
\begin{cases} \sum_{x_i>0}c_ix_i &= -\sum_{x_i<0}c_ix_i\\ \sum_{y_i>0}c_iy_i &= -\sum_{y_i<0}c_iy_i\\ \sum_{x_i>0}c_ix_i &\leq m \\ \sum_{y_i>0}c_iy_i &\leq m \\ \end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧∑xi>0cixi∑yi>0ciyi∑xi>0cixi∑yi>0ciyi=−∑xi<0cixi=−∑yi<0ciyi≤m≤m
原因是这样的每个集合显然能唯一对应一个凸多边形(按方位角排序后依次加入),且不同集合对应的两两不同。
那么转化为一个计数问题,考虑按位DP。令
4
4
4个方向的长度和分别为
s
u
x
,
s
d
x
,
s
u
y
,
s
d
y
sux,sdx,suy,sdy
sux,sdx,suy,sdy,那么令
F
[
i
]
[
u
x
]
[
d
x
]
[
u
y
]
[
d
y
]
[
u
]
[
v
]
F[i][ux][dx][uy][dy][u][v]
F[i][ux][dx][uy][dy][u][v]表示考虑了每种向量数目
c
c
c的最后
i
i
i位,且满足
s
u
x
sux
sux与
s
d
x
sdx
sdx后
i
i
i位相同,
s
u
y
suy
suy与
s
d
y
sdy
sdy后
i
i
i位相同,
s
u
x
,
s
d
x
,
s
u
y
,
s
d
y
sux,sdx,suy,sdy
sux,sdx,suy,sdy分别向前的进位为
u
x
,
d
x
,
u
y
,
d
y
ux,dx,uy,dy
ux,dx,uy,dy,同时
u
u
u与
v
v
v分别表示了
s
u
x
sux
sux和
s
u
y
suy
suy的后
i
i
i位是否不超过
m
m
m的后
i
i
i位。令
max
(
∣
x
i
∣
,
∣
y
i
∣
)
=
w
\max(|x_i|,|y_i|)=w
max(∣xi∣,∣yi∣)=w,题目保证
w
≤
4
w\leq 4
w≤4,那么容易证明任意时刻均有
u
x
,
d
x
,
u
y
,
d
y
≤
n
w
ux,dx,uy,dy\leq nw
ux,dx,uy,dy≤nw。
转移有两种方式,一种是直接
O
(
2
n
)
\mathcal O(2^n)
O(2n)枚举所有
c
c
c的第
i
i
i位转移,一种是依次DP每种
c
c
c的第
i
i
i位。理论复杂度上第二种更优秀,但此时
u
x
,
d
x
,
u
y
,
d
y
ux,dx,uy,dy
ux,dx,uy,dy的上界会翻倍,实际效率可能比第一种慢近百倍。
时间复杂度为
O
(
2
n
n
4
w
4
log
m
)
\mathcal O(2^nn^4w^4\log m)
O(2nn4w4logm)或
O
(
n
5
w
4
log
m
)
\mathcal O(n^5w^4\log m)
O(n5w4logm)。这里的代码是第二种的。
#include <bits/stdc++.h>
#define MOD 998244353
using namespace std;
typedef long long ll;
inline void add(int &x,int y) {
((x+=y)>=MOD)?x-=MOD:0;
}
int f[2][41][41][41][41][4];
int a[10],b[10];
int main() {
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);
int cur=0,up=(n<<3);
f[cur][0][0][0][0][0]=1;
for(int i=0;i<=30;i++) {
for(int j=1;j<=n;j++) {
int ux=((a[j]>0)?a[j]:0),dx=((a[j]<0)?-a[j]:0);
int uy=((b[j]>0)?b[j]:0),dy=((b[j]<0)?-b[j]:0);
cur^=1;
memcpy(f[cur],f[cur^1],sizeof(f[cur]));
for(int t1=0;t1<=up;t1++)
for(int t2=0;t2<=up;t2++)
for(int t3=0;t3<=up;t3++)
for(int t4=0;t4<=up;t4++)
for(int t5=0;t5<4;t5++)
if (f[cur^1][t1][t2][t3][t4][t5])
add(f[cur][t1+ux][t2+dx][t3+uy][t4+dy][t5],f[cur^1][t1][t2][t3][t4][t5]);
}
bool v=((m>>i)&1);
cur^=1;
memset(f[cur],0,sizeof(f[cur]));
for(int t1=0;t1<=up;t1++)
for(int t2=0;t2<=up;t2++)
for(int t3=0;t3<=up;t3++)
for(int t4=0;t4<=up;t4++) {
if (((t1^t2)&1)||((t3^t4)&1)) continue;
for(int t5=0;t5<4;t5++)
if (f[cur^1][t1][t2][t3][t4][t5]) {
if (!v)
add(f[cur][t1>>1][t2>>1][t3>>1][t4>>1][t5|(t1&1)|((t3&1)<<1)],f[cur^1][t1][t2][t3][t4][t5]);
else
add(f[cur][t1>>1][t2>>1][t3>>1][t4>>1][t5&((t1&1)^((t3&1)<<1))],f[cur^1][t1][t2][t3][t4][t5]);
}
}
}
printf("%d\n",(f[cur][0][0][0][0][0]-1+MOD)%MOD);
return 0;
}
/*
5 1000000000
-2 4
2 -3
0 -4
2 4
-1 -3
*/