MX 暑假集训 7.21
省流:\(100+100+35+100\)。
神秘大样例。
T1 两个大样例总共给了 \(7\) 行。
T2 序列题,答案级别为逆序对数,大样例答案 \(0,1,1,0\),其中第一个和第四个大样例 \(n=1\)。
T3 字符串题,\(1\le T\le 10^5\),字符串长度 \(2\times 10^6\),结果最大的样例在题面内,字符串长度最长为 \(9\),答案最大为 \(2\)。
T4 树论,大样例给了一个菊花和一个链,然后输出了一个数加上 \(n-1\) 个 \(0\)。
T1: Akari
题意
给定一个 \(R\times C\) 的网格图,图上有 \(n\) 个灯和 \(m\) 个障碍,保证每个网格上不会同时存在灯和障碍,灯会向上下左右四个方向发出光,光按照方向一直走并照亮走到的格子,直到碰到障碍为止,求最后有多少个格子被照亮。
\(1\le R,C\le 1500,1\le n\le 5\times 10^5,1\le m\le 10^5\)。
solution
简单题,暴力遍历被照亮的格子即可,标记每个格子是否被横向或纵向的光经过,被同样方向的光经过则不需要再更新,时间复杂度 \(O(RC)\)。
Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1500+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n,m,ans;
bitset<N> vis[N],vx[N],vy[N],did[N];
struct Node{int x,y;};
vector<Node> tmp;
inline void bfs() {
for (Node k:tmp) {
int x=k.x,y=k.y;
F(i,x,1) {
if (vis[i][y]||vx[i][y]) break;
vx[i].set(y);
ans+=(!did[i][y]);
did[i].set(y);
}
f(i,x+1,n) {
if (vis[i][y]||vx[i][y]) break;
vx[i].set(y);
ans+=(!did[i][y]);
did[i].set(y);
}
F(i,y,1) {
if (vis[x][i]||vy[x][i]) break;
vy[x].set(i);
ans+=(!did[x][i]);
did[x].set(i);
}
f(i,y+1,m) {
if (vis[x][i]||vy[x][i]) break;
vy[x].set(i);
ans+=(!did[x][i]);
did[x].set(i);
}
}
}
int main() {
read(n);read(m);
int L1,L2;read(L1);read(L2);
f(i,1,L1) {
int x,y;read(x);read(y);
tmp.push_back({x,y});
}
f(i,1,L2) {
int x,y;read(x);read(y);
vis[x].set(y);
}
bfs();
printf("%d\n",ans);
return 0;
}
T2: Fast XORting
题意
给定一个长度为 \(n\) 的排列,排列的值域是 \([0,n-1]\),保证 \(n\) 是 \(2\) 的幂,你可以进行以下两种操作。
- 交换序列相邻两项;
- 选定 \(x(0\le x< n)\) 并将序列中所有数异或上 \(x\)。
求将这个排列排序所需最小操作次数。
\(1\le n\le 2^{18}\)。
solution
首先第二种操作最多使用一次,因为多次异或可以将所有异或上的数异或起来进行一次异或即可。
若不进行第二种操作,则答案为逆序对数,考虑进行第二种操作后,最少还需要多少次第一种操作。
从高到低考虑每一位,只考虑高于当前位的位都相同的一组数(因为更高位不同那么它们的大小关系已经在更高位确定,不需要在当前位计算),则逆序对数可以分成这一位为 \(0\) 与这一位为 \(1\) 之间的逆序对数与这一位相同的数之间的逆序对数。
全体异或上一个数可以将这一位反转,而不影响这些数的相对顺序,所以影响的只是这一位为 \(0\) 与这一位为 \(1\) 之间的逆序对数,那么考虑这一位是否被反转,两种情况产生的逆序对数取较小值即为这一位的答案,所有位的答案加起来即为总共需要操作一的次数。
时间复杂度 \(O(n\log n)\)。
Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n,k;
int a[N],b[N],c[N];
struct BIT {
int v[N];
inline void clear(int n) {f(i,1,n)v[i]=0;}
inline void add(int p) {for(;p<=n;p+=(p&-p))v[p]++;}
inline int qry(int p) {int r=0;for(;p;p-=(p&-p))r+=v[p];return r;}
}tr;
inline ll calc() {
ll res=0;
tr.clear(n);
F(i,n,1) {
res+=tr.qry(a[i]);
tr.add(a[i]+1);
}
return res;
}
inline ll work(int len) {
int c1=0;ll res=0;
f(i,1,len) {
if (b[i]==0) res+=c1;
else c1++;
}
return res;
}
inline ll solve(int d,int len) {
f(i,1,n) c[i]=((a[i]>>d)&1);
ll v1=0,v2=0;
for (int i=1;i<=n;i+=len) {
f(j,1,len) b[j]=c[j+i-1];
v1+=work(len);
f(j,1,len) b[j]=(c[j+i-1]^1);
v2+=work(len);
}
if (v1>v2) f(i,1,n) a[i]^=(1<<d),c[i]^=1;
for (int i=1;i<=n;i+=len) {
vector<int> c0,c1;
f(j,1,len) {
if (c[i+j-1]) c1.push_back(a[i+j-1]);
else c0.push_back(a[i+j-1]);
}
int now=i;
for (int x:c0) a[now++]=x;
for (int x:c1) a[now++]=x;
}
return min(v1,v2)+(d?solve(d-1,len/2):0);
}
int main() {
read(n);
f(i,1,n) read(a[i]);
if (n==1) return puts("0"),0;
ll ans=calc();
f(i,0,18) if ((1<<i)==n) {k=i;break;}
printf("%lld\n",min(ans,solve(k-1,n)+1));
return 0;
}
T3: Pattern Search
T4: [ICPC 2023 Nanjing R] 红黑树
Close Vertices
题意
给定一棵 \(n\) 个节点的树,边有边权,求多少条简单路径满足边数不超过 \(L\),边权和不超过 \(w\)。
\(1\le n\le 10^5\)。
solution
比较考验点分治基础的题,因为我第一次知道可以容斥规避跨子树的三维偏序,所以记录一下。
树上路径,考虑点分治,然后将每个点到分治中心的边数与边权和看作一个二元组。
如果没有边权和的限制,那么这题就是 Tree,我是用树状数组搞过去的,时间 \(O(n\log^2 n)\),但是实际上可以利用双指针来做到 \(O(n\log n)\)。
实际上这两种做法的区别在于如何统计答案,树状数组实际上是每次暴力地进行更新与查询,依照每个子树依次加入,从而规避同个子树的节点被错误计算,本质上增加了子树编号这个维度,是一个二维偏序,如果此题也同样引入子树编号,那么就变成了一个三维偏序问题,套上外层分治,时间复杂度是 \(O(n\log^3 n)\) 的,不可接受。
而双指针法提供了一个更巧妙的思路,如果我们不关心是否跨子树,那么就是一个一维偏序问题,可以 \(O(n)\) 求解,但是如何去掉同子树的贡献呢,我们对每个子树再做一遍双指针,统计出记重的数量,容斥掉即可。
回到本题,不考虑子树编号,实际上就是一个二维偏序问题,用树状数组扫描线可以做到 \(O(n\log n)\) 求解,同理,对每个子树同样求解,将记重的贡献减去即可。
最终时间复杂度 \(O(n\log^2 n)\)。
Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n,mxL,mxw;
ll ans;
struct Edge{int v,w;};
vector<Edge> e[N];
int sum,rt;
int siz[N],wgh[N];
bitset<N> vis;
inline void get_rt(int now,int fa) {
siz[now]=1;wgh[now]=0;
for (Edge E:e[now]) {
int v=E.v;
if (vis[v]||v==fa) continue;
get_rt(v,now);
siz[now]+=siz[v];
wgh[now]=max(wgh[now],siz[v]);
}
wgh[now]=max(wgh[now],sum-siz[now]);
if (wgh[now]<wgh[rt]) rt=now;
}
struct Point{int x,y,op;};
inline bool cmp(Point a,Point b) {return (a.x==b.x)?((a.y==b.y)?(a.op<b.op):(a.y<b.y)):(a.x<b.x);}
struct BIT {
int v[N],mx;
inline void clear(int len) {mx=len;f(i,1,mx)v[i]=0;}
inline void add(int p) {for(;p<=mx;p+=(p&-p))v[p]++;}
inline int qry(int p) {int r=0;for(;p;p-=(p&-p))r+=v[p];return r;}
}tr;
inline ll work(vector<Point> tmp) {
static int mp[N];
int len=0;
for (Point k:tmp) mp[++len]=k.y;
sort(mp+1,mp+1+len);
len=unique(mp+1,mp+1+len)-mp-1;
sort(tmp.begin(),tmp.end(),cmp);
tr.clear(len);
ll ret=0;
for (Point k:tmp) {
int y=lower_bound(mp+1,mp+1+len,k.y)-mp;
if (k.op) ret+=tr.qry(y);
else tr.add(y);
}
return ret;
}
vector<Point> tmp;
inline void dfs(int now,int fa,int dis,int len) {
tmp.push_back({len,dis,0});
tmp.push_back({mxL-len,mxw-dis,1});
for (Edge E:e[now]) {
int v=E.v,w=E.w;
if (vis[v]||v==fa) continue;
dfs(v,now,dis+w,len+1);
}
}
inline void calc(int now) {
vector<Point> tot;
tot.push_back({0,0,0});
tot.push_back({mxL,mxw,1});
ll n_ans=ans;
for (Edge E:e[now]) {
int v=E.v,w=E.w;
if (vis[v]) continue;
dfs(v,now,w,1);
ans-=work(tmp);
for (Point k:tmp) tot.push_back(k);
tmp.clear();
}
ans+=work(tot)-1;
}
inline void solve(int now) {
vis.set(now);
calc(now);
for (Edge E:e[now]) {
int v=E.v;
if (vis[v]) continue;
sum=siz[v];rt=0;
get_rt(v,now);
solve(rt);
}
}
int main() {
read(n);read(mxL);read(mxw);
f(i,2,n) {
int fa,w;
read(fa);read(w);
int u=i,v=fa;
e[u].push_back({v,w});
e[v].push_back({u,w});
}
wgh[0]=inf;sum=n;
get_rt(1,0);solve(rt);
printf("%lld\n",ans>>1);
return 0;
}

浙公网安备 33010602011771号