MX 暑假集训 7.9
感觉题目正常了许多,但分数还是那么低。。
T1: 合成小丹
题意
给定 \(n\) 个值域为 \([0,2^w]\) 的整数,你需要进行 \(n-1\) 次操作,每次操作在以下两种方案中任选一种:
-
在剩下的数中指定一个数并将其删除;
-
在剩下的数中指定两个数 \(x,y\),将它们删除,再加入 \(\left\lfloor \frac{x|y}{2} \right\rfloor\)。
你需要最小化 \(n-1\) 次操作后的剩下的数,输出最小值。
\(1\le T\le 100,1\le n\le 10^5,1\le w\le 60\)。
solution
考虑将合并的过程看作一棵满二叉树,叶子节点为初始值,非叶子节点为合并操作,令 \(a_i\) 对应的深度为 \(d_i\),对于所有参与合并的点有 \(\sum{\frac{1}{2^{d_i}}}=1\)。
由按位或的交换律和结合律容易得到,根节点的值即为 \(\bigoplus \left\lfloor \frac{a_i}{2^{d_i}}\right\rfloor\),其中 \(\oplus\) 为按位或操作。
由于有些节点是不参与合并的,所以我们只需保证 \(\sum\limits_{i=1}^{n}{\frac{1}{2^{d_i}}} \ge 1\) 即可做到抓出一棵合法的树来,最终的权值为 \(\bigoplus\limits_{i=1}^{n} \left\lfloor \frac{a_i}{2^{d_i}}\right\rfloor\)。
到这里就好做了,我们从高到低按位考虑答案的这一位是否可能为 \(0\)。
对于每个 \(a_i\),我们肯定希望它的 \(d_i\) 尽可能小,这样 \(\frac{1}{2^{d_i}}\) 才能尽可能大,而 \(d_i\) 可以怎么取取决于答案处理出来的数。
如果答案的某一位是 \(0\),那么 \(\left\lfloor \frac{a_i}{2^{d_i}}\right\rfloor\) 的这一位就不可能是 \(1\),于是我们可以暴力地枚举 \(a_i\) 对应的 \(d_i\),判断它是不是 \(ans\) 已经处理完的位的子集,这样做是 \(O(nw^2)\) 的。
我们考虑对每个 \(i\) 维护一个 \(b\) 数组表示 \(a_i\) 对应的 \(d_i\) 可能的值,\(b_k=1\) 则表示 \(d_i\ne k\)。
将每个 \(b\) 数组压成一个 ull,取反之后 lowbit 即为最小可能的 \(d_i\),维护时若 \(ans\) 第 \(d\) 位为 \(0\) 则 \(a_i \mathbin{>\!>} k\) 的第 \(d\) 位不为 \(1\),所以只需要将 \(b\) 或上 \(a_i \mathbin{>\!>} d\) 即可 \(O(1)\) 维护。
时间复杂度 \(O(Tnw)\),用 builtin_ctzll 会比除法快许多。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ull unsigned 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--)
const int N=1e5+10;
const 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();
}
inline void readl(ull &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;
ull a[N],b[N],c[N];
inline void solve() {
read(n);read(m);
f(i,1,n) readl(a[i]),b[i]=0;
ull ans=0,mx=(1<<m);
F(d,m,0) {
int flag=0;
ull sum=0;
f(i,1,n) {
ull val=(~(b[i]|(a[i]>>d)));
int k=__builtin_ctzll(val&-val);
sum+=(mx>>k);
if (sum>=mx) {flag=1;break;}
}
if (flag) f(i,1,n) b[i]|=(a[i]>>d);
else ans|=(1ll<<d);
}
printf("%llu\n",ans);
}
int main() {
freopen("happy.in","r",stdin);
freopen("happy.out","w",stdout);
int C,T;
read(C);read(T);
while (T--) solve();
return 0;
}
T2: Snacks Scheduling
题意
给定长度为 \(n\) 的序列 \(a\),你需要找出逆序对数最小的排列 \(p\) 使得对于任意 \(1\le i\le n\) 有 \(p_i\ne a_i\),判断是否有解,有解则输出最小逆序对数。
\(1\le n\le 10^5,\sum n \le 2\times 10^7\)。
solution
赛时写了个乱搞过了 pretest \(8,9\),但是没过前面 \(5,6,7\) 的小点,果然正式数据挂了。
神秘观察结论题。
显然若 \(a_i\) 全部相同则无解,否则必然有解。
首先注意到答案不超过 \(n-1\),证明如下。
考虑数学归纳法,找到第一个 \(k\) 满足 \(a_k\ne 1\),那么如果 \(a_k,a_{k+1},\cdots ,a_{n}\) 不全部相同,那么在 \(k\) 的位置放上 \(1\),\(1\sim {k-1}\) 放上 \(2\sim k\),那么在 \(k\) 之前产生了 \(k-1\) 个逆序对,\(k+1\sim n\) 由归纳构造最多产生 \(n-k-1\) 个逆序对,总和为 \(n-2\) 个逆序对。
那么 \(k\) 之后必然为同一个数了,同理从右向左找 \(a_{k'} \ne n\) 的第一个 \(k'\),那么 \(k\) 之后的数只能为 \(n\),否则右边和左边类似地构造就产生 \(n-2\) 个逆序对了。
所以序列一定是 \(1,1,\cdots ,1,k,n,n,\cdots ,n\) 的形式,在 \(1\sim k-1\) 位置放上 \(2\sim k\),\(k\) 位置放上 \(n\),\(k+1\) 位置放上 \(1\),\(k+2\sim n\) 位置放上 \(k+1\sim n-1\),这样的构造逆序对数是 \(n-1\) 的,符合条件。
一些边界情况也很好构造,在这里就不说明了。
同时,如果一个排列 \(p\) 满足不存在 \(1\le x<n\) 使得 \({p_1,p_2,\cdots ,p_x}={1,2,3,\cdots ,x}\),则 \(p\) 的逆序对数量至少为 \(n-1\),证明如下。
每个点 \(i\) 向 \(p_i\) 连边,最后形成若干个环,令每个环的最大值为 \(M_i\),最小值为 \(m_i\),则逆序对数 \(\ge \frac{1}{2}\sum\limits_{i=1}^{n}{|p_i-i|} \ge \sum{(M_i-m_i)}\)。
由上述条件可得,对于任意 \(x\) 必然存在 \(m_i\le x<M_i\),所以所有环 \([m_i,M_i)\) 的并集为 \([1,n)\),长度和大于 \(n-1\)。
所以问题就变成了将 \(a\) 分成若干段 \([l_i,r_i]\),使得每一段不交且首尾相接为 \(1\sim n\),并且段内 \(a_j\) 不能全部相同或者 \(a_j\) 不在 \([l_i,r_i]\) 内,那么由上面的两个条件可得,每段的逆序对数最小为 \(r_i-l_i\),若分成 \(m\) 段则总逆序对数为 \(n-m\)。
问题就变成了尽量分成尽可能多的段,满足每段 \([l_i,r_i]\) 内 \(a_j\) 不全部相同或者 \(a_j\) 不在 \([l_i,r_i]\) 内。
其实可以看作一个二维平面,平面上有若干个正方形,第一个正方形左下角点为 \((1,1)\),后面每个正方形左下角点都为上一个正方形右上角点横纵坐标都加 \(1\) 所得到的点,最后一个正方形的右上角点为 \((n,n)\),使得能构造 \(p\) 使每个点 \((i,p_i)\) 都在一个正方形内,最大化正方形的数量。
这个问题可以 dp 求解,令 \(dp_i\) 表示 \((i,i)\) 为正方形右上角端点时,\((1,1)\) 到 \((i,i)\) 最多形成多少个正方形。
根据上面的几何意义容易写出这个 \(O(n^2)\) 的式子:
f(i,1,n) {
flag|=(i!=1&&a[i]!=a[i-1]);
dp[i]=-inf;
if (flag||a[i]>i) dp[i]=1;
if (a[i]!=a[i-1])lst=i;
f(j,1,i-1) if (j>=a[i]||j<lst-1||a[i]>i) dp[i]=max(dp[i],dp[j]+1);
}
\(a_i>i\) 是好转移的,直接取前面最大值即可,剩下的分成三个部分维护,分别是 \(j<lst-1\),\(j=lst\),\(j\ge lst\land j\ge a_i\) 即可做到 \(O(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--)
const int N=1e5+10;
const 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;
int a[N],dp[N];
inline void solve() {
read(n);
int flag=1;
f(i,1,n) read(a[i]),flag&=(i==1||a[i]==a[i-1]);
if (flag) return puts("-1"),void();
int mx1=-inf,mx2=-inf,lst=1;
f(i,1,n) {
flag|=(i!=1&&a[i]!=a[i-1]);
dp[i]=-inf;
if (flag||a[i]>i) dp[i]=1;
if (i!=1&&a[i]!=a[i-1]) {
f(j,lst-1,i-2) mx2=max(mx2,dp[j]+1);
lst=i;
}
if (a[i]>i) dp[i]=max(dp[i],mx1);
else {
dp[i]=max(dp[i],mx2);
if (lst!=1&&lst-1>=a[i]) dp[i]=max(dp[i],dp[lst-1]+1);
}
mx1=max(mx1,dp[i]+1);
if (i>=a[i]&&a[i+1]==a[i]) mx2=max(mx2,dp[i]+1);
}
printf("%d\n",n-dp[n]);
}
int main() {
freopen("poem.in","r",stdin);
freopen("poem.out","w",stdout);
int C,T;
read(C);read(T);
while (T--) solve();
return 0;
}
T3: [KTSC 2026] 通信网络 2 / Communication Network 2
题意
给定 \(n\) 个节点的无向图,每个时间可以加或删一些边,时间轴为 \([0,m]\),加边删边数量总和不超过 \(S\),\(q\) 次询问,给定 \(x,l,r\),询问有多少个点 \(y\) 满足其在 \([l,r]\) 时刻都与 \(x\) 连通。
\(1\le n,m,S\le 10^5,1\le q\le 3\times 10^5\)。
solution
不会。大概是线段树分治套字符串状物。
Least Cost Bracket Sequence
题意
给定一个长度为 \(n\),仅包含 "(",")" 和 "?" 的字符串,将每个 "?" 替换为 "(" 或 ")" 都有相应的代价,求将这个字符串变成合法括号序列的最小代价并输出方案或报告无解。
\(2\le n\le 5\times 10^4\),且 \(n\) 为偶数。
solution
其实可以将每个非问号字符也变成问号,如将左括号变成问号,其变成左括号代价为 \(0\),变成右括号代价为极大值。
一个合法括号序列的充要条件是有 \(\frac{n}{2}\) 个左括号,且对于 \(1\le 2i+1\le n\) 都有 \([1,2i+1]\) 内至少有 \(i+1\) 个左括号。
先钦定全是右括号,从左往右扫,用堆维护变成左括号的代价,在每个 \(2i+1\) 的位置加上最小代价再弹出堆顶即可。
时间复杂度 \(O(n\log n)\)。
Code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<bitset>
#include<queue>
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--)
const int N=2e5+10;
const ll inf=1e15+10;
inline void readl(ll &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
bitset<N> vis;
char s[N];
ll a[N],b[N];
struct Node {
ll val;int id;
inline bool operator <(const Node &x)const {return val>x.val;}
};
priority_queue<Node> q;
int main() {
scanf("%s",s+1);
int n=strlen(s+1);
ll ans=0;
f(i,1,n) {
if (s[i]=='?') readl(a[i]),readl(b[i]);
else if (s[i]=='(') a[i]=0,b[i]=inf;
else a[i]=inf,b[i]=0;
ans+=b[i];
}
q.push({a[1]-b[1],1});
for (int i=1;i<=n;i+=2) {
ans+=q.top().val;
vis.set(q.top().id);
q.pop();
q.push({a[i+1]-b[i+1],i+1});
q.push({a[i+2]-b[i+2],i+2});
}
if (ans>inf) puts("-1");
else {
printf("%lld\n",ans);
f(i,1,n) putchar(vis[i]?'(':')');
}
return 0;
}

浙公网安备 33010602011771号