2024.10 - 做题记录与方法总结
赏赐的是CCF,收回的也是CCF -《CCF圣经》
2024/10/01#
国庆快乐!
P10856 【MX-X2-T5】「Cfz Round 4」Xor-Forces#
题面:
题目描述
给定一个长度为 \(n=2^k\) 的数组 \(a\),下标从 \(0\) 开始,维护 \(m\) 次操作:
- 操作一:给定 \(x\),设数列 \(a'\) 满足 \(a'_i=a_{i\oplus x}\),将 \(a\) 修改为 \(a'\)。其中 \(\oplus\) 表示按位异或运算。
- 操作二:给定 \(l,r\),查询 \(a\) 的下标在 \(l,r\) 之间的子数组有多少颜色段。不保证 \(\bm {l\le r}\),若 \(\bm{l > r}\),请自行交换 \(\bm{l,r}\)。
其中,一个极长的所有数都相等的子数组称为一个颜色段。
部分测试点要求强制在线。
输入格式
第一行三个整数 \(T,k,m\),其中 \(T \in \{ 0, 1 \}\) 为决定是否强制在线的参数。
第二行 \(n\) 个整数 \(a_0, \ldots, a_{n-1}\)。
接下来 \(m\) 行,每行两或三个整数,描述一次操作。第一个整数 \(\mathit{op}\) 表示操作类型。
- 若 \(op=1\),为操作一,接下来一个整数 \(x'\),满足 \(x=x'\oplus(T\times \mathit{lst})\)。
- 若 \(op=2\),为操作二,接下来两个整数 \(l',r'\),满足 \(l=l'\oplus(T\times \mathit{lst})\),\(r=r'\oplus(T\times \mathit{lst})\)。不保证 \(\bm{l \le r}\),若 \(\bm{l > r}\),请自行交换 \(\bm{l, r}\)。
- 其中 \(\mathit{lst}\) 表示上次询问的答案。特别地,如果此前没有询问操作,则 \(\mathit{lst}=0\)。
输出格式
输出若干行,每行包含一个整数,依次表示每个询问的答案。
样例 #1
样例输入 #1
0 3 3 1 2 1 3 2 4 5 1 2 1 5 1 3 2 5 1
样例输出 #1
5 4
样例 #2
样例输入 #2
1 3 3 1 2 1 3 2 4 5 1 2 1 5 1 6 2 0 4
样例输出 #2
5 4
样例 #3
样例输入 #3
1 4 16 12 9 5 9 12 12 9 12 9 16 5 9 12 16 9 5 2 0 4 1 15 2 14 0 1 15 2 6 0 2 4 14 1 0 1 14 2 4 10 2 6 3 1 7 2 4 13 1 3 1 3 2 4 3 2 15 2
样例输出 #3
5 7 4 7 9 5 7 2 11
提示
【样例解释 #1】
此样例允许离线。
初始时 \(a=[1,2,1,3,2,4,5,1]\)。
\(a\) 的下标在 \(1,5\) 之间的子数组为 \([2,1,3,2,4]\),它的颜色段数为 \(5\)。
进行重排操作后,\(a=[3,1,2,1,1,5,4,2]\)。
\(a\) 的下标在 \(5,1\) 之间的子数组为 \([1,2,1,1,5]\),它的颜色段数为 \(4\)。
【样例解释 #2】
此样例除强制在线外,与样例 #1 完全一致。
【数据范围】
对于所有测试数据,\(T \in \{ 0, 1 \}\),\(0\le k\le 18\),\(n=2^k\),\(1\le m\le 2\times 10^5\),\(1\le a_i\le n\),\(\mathit{op} \in \{ 1, 2 \}\),\(0\le x,l,r < n\)。
本题采用捆绑测试。
- Subtask 1(15 points):\(T=1\),\(k\le 10\),\(m\le 10^3\)。
- Subtask 2(15 points):\(T=1\),不存在操作一。
- Subtask 3(20 points):\(T=1\),对于所有操作二,要么 \(l=0,r=n-1\),要么 \(l=n-1,r=0\)。
- Subtask 4(20 points):\(T=0\)。
- Subtask 5(30 points):\(T=1\)。
注意:Subtask 5 依赖前四个 Subtask,只有通过前四个 Subtask 才能尝试获得该 Subtask 的分数。
全局下标异或上一个 \(x\),查询区间极长颜色段数。
下标范围为 \(\in [0,2^m - 1]\),我们可以先用 01-trie 来理解。
当我们将每个位置的下标伴随着颜色插入到 01-trie 中,如同用线段树一样求间极长颜色段数。
我们不难发现 01-trie 和 线段树 —— 本质是一样的,原因是下标范围恰为 \(\in [0,2^m - 1]\)。
当我们在 01-trie 上判断最高位是否为 \(1 / 0\),和线段树上二分区间 \([0\sim 2^i - 1]\ \ /\ \ [2^i \sim 2^i + (2 ^ i - 1)]\) 是一样的范围。
那我们查询区间极长颜色段数,就上线段树的基本操作:
struct node {
int lx,rx; // lx <- 最左侧颜色 || rx <- 最右侧颜色
int ans; // <- 区间极长颜色段数
};
然后,如何合并线段树上区间答案呢?
我们合并区间的时候,只有中间的颜色段会被影响,故:
node merge(node a,node b) {
node c;
c.lx = a.lx,c.rx = b.rx;
c.ans = a.ans + b.ans - (a.rx == b.lx);
return c;
}
关键问题:如何处理全局下标异或?
下标异或交换————旋转 01-trie!
我们发现如果在 01-trie 上做全局异或操作,
当异或到 \(x\) 的某一位为 \(1\) 时,我们就相当于将左右子树互换,
然后继续向下继续异或操作,并且异或具有交换律,我们只有在询问的时候考虑异或操作。
这个做法看起来很 \(cool\),但遗憾的是,如果我们每一次修改都都做一边上述操作,时间复杂度和暴力无异!
问题出在哪?我们想打 \(tag\) —— 很遗憾,对于一个区间打完 tag 后,我们无法知道区间左右颜色!询问是有问题的。
我们从此发现问题的本质:全局 旋转tag 线段树的问题在于,异或操作是自顶向下的,而下面的操作不实行,答案就无法得出!
我们如何能让下面的答案也清晰起来呢?
神之操作:可持久化!
我们只对询问时的异或和感兴趣,而异或和在 \(\in [0,2^m - 1]\) 之中,
能不能我们直接处理出所有不同异或和版本的线段树?\(exactly!\)
我们对于每一个异或和版本的线段树上可持久化,每一个 \(x\) 版本的线段树来自于 \(x\oplus\operatorname{highbit}(x)\) 版本的线段树。
我们因为在 \(x\oplus\operatorname{highbit}(x)\) 版本上建可持久化线段树,所以我们只有异或到 \(x\) 的 \(\operatorname{highbit}\) 就可以交换左右子树并退出(因为 \(\operatorname{highbit}\) 以下的已经在 \(x\oplus\operatorname{highbit}(x)\) 版本上做过了)
(这是用 \(\mathcal{O}(n\log n)\) 建出每一个异或和版本的线段树的保证)
这样就很好的做到了自下而上的线段树更新!
这样本题就基本上解决了!
AC-code:
#include<bits/stdc++.h>
using namespace std;
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 3e5+5;
int T,k,m,n,a[N];
int rt[N];
namespace sgt{
#define mid ((pl + pr) >> 1)
struct node {
int lx,rx,ans;
node(int l,int r,int a) :lx(l),rx(r),ans(a){}
node(){}
friend node operator + (node a,node b) {
node c;
c.lx = a.lx;
c.rx = b.rx;
c.ans = a.ans + b.ans - (a.rx == b.lx);
return c;
}
}t[N * 30];
int ls[N * 30],rs[N * 30],cnt;
int newnode(node x) {
t[++cnt] = x;
ls[cnt] = rs[cnt] = 0;
return cnt;
}
int build(int pl,int pr) {
if(pl == pr) {
int cur = newnode(node(a[pl],a[pl],1));
return cur;
}
int cur = newnode(node());
ls[cur] = build(pl,mid);
rs[cur] = build(mid+1,pr);
t[cur] = t[ls[cur]] + t[rs[cur]];
return cur;
}
int update(int pre,int pl,int pr,int x) {
int cur = newnode(node());
int len = (pr - pl + 1);
if(x & (len >> 1)) {
ls[cur] = rs[pre],rs[cur] = ls[pre];
}else {
ls[cur] = update(ls[pre],pl,mid,x);
rs[cur] = update(rs[pre],mid+1,pr,x);
}
t[cur] = t[ls[cur]] + t[rs[cur]];
return cur;
}
node query(int p,int pl,int pr,int l,int r) {
if(l <= pl && pr <= r) return t[p];
if(r <= mid) return query(ls[p],pl,mid,l,r);
else if(l > mid) return query(rs[p],mid+1,pr,l,r);
else return query(ls[p],pl,mid,l,r) + query(rs[p],mid+1,pr,l,r);
}
void init() {
cnt = 0;
rt[0] = build(0,n - 1);
for(int i = 1;i<n;i++) {
int k = __lg(i);
rt[i] = update(rt[i ^ (1 << k)],0,n - 1,i);
}
}
}
int v,lst;
void Reverse() {
int x = rd();
x ^= T * lst;
v ^= x;
}
void query() {
int l = rd(),r = rd();
l ^= T * lst,r ^= T * lst;
if(l > r) swap(l,r);
lst = sgt::query(rt[v],0,n - 1,l,r).ans;
wt(lst);
putchar('\n');
}
signed main() {
T = rd(),k = rd(),m = rd(),n = (1 << k);
for(int i = 0;i<n;i++) a[i] = rd();
sgt::init();
v = 0,lst = 0;
while(m--) {
int opt = rd();
switch(opt) {
case 1:
Reverse();
break;
case 2:
query();
break;
default:
puts("Error");
exit(0);
break;
}
}
return 0;
}
2024/10/02#
【LGR-197-Div.2】洛谷 10 月月赛 I &「SFMOI」Round I#
打舒服了。
A.Strange Cake Game#
题面:
题目背景
English statement. You must submit your code at the Chinese version of >the statement.
小 \(\mathcal{W}\) 和 小 \(\mathcal{M}\) 在 CF 庄园里分蛋糕。
题目描述
有一块面积为 \(n\times m\) 的矩形蛋糕。记其左上角顶点为 \((0,0)\),右下角顶点为 \((n,m)\),右上角顶点为 \((0,m)\)。
蛋糕上分布着 \(k\) 块巧克力,第 \(i\) 块的位置为 \((x_i-0.5,y_i-0.5)\)。一个点上可能有不止一块巧克力。
小 M 和 小 W 要切蛋糕。蛋糕刀起初在 \((0,0)\),小 W 先手,轮流移动蛋糕刀。设蛋糕刀在 \((x,y)\),则它可以被移动到 \((x,y+1)\) 或 \((x+1,y)\)。
在若干步后,蛋糕会被切割轨迹完全分成两个部分——右上角的部分归小 W,左下角的部分归小 M。小 W 和小 M 都想吃到最多的巧克力,请帮他们计算:如果双方都按照最优策略行动,小 W 能分到几块巧克力。
如下是蛋糕的示例和一种可能的切蛋糕的方式。
输入格式
第一行,两个正整数 \(n,m\),含义见题面。
第二行,一个整数 \(k\) ,表示巧克力块数。
接下来 \(k\) 行,每行两个正整数 \(x_i,y_i\),表示第 \(i\) 块巧克力的坐标为 \((x_i-0.5,y_i-0.5)\)。
注意:第 \(i\) 块巧克力的坐标为 \((x_i-0.5,y_i-0.5)\)。一个格子上可能有多块巧克力。
输出格式
输出一个整数,代表小 W 最多能拿到的巧克力块数。
样例 #1
样例输入 #1
3 3 1 1 3
样例输出 #1
1
提示
数据范围
本题采用捆绑测试。
- Subtask 1(5 pts):\(n=m=1\);
- Subtask 2(10 pts):\(1 \le n \times m \le 60\);
- Subtask 3(15 pts):\(1 \le n \times m \le 10^5\);
- Subtask 4(20 pts):\(k=1\);
- Subtask 5(50 pts):无特殊限制。
对于 \(100\%\) 的数据,保证:
- \(0 \le k \le 2 \times 10^5\);
- \(1 \le n,m \le 10^{18}\);
- \(1 \le x_i \le n\),\(1 \le y_i \le m\)。
简单博弈,划分的面积肯定是越大越好,
所以从原点走一条折线到终点肯定最优,判断是否蛋糕在折线上方。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 2e5+5;
int n,m,k;
signed main() {
n = rd(),m = rd();
k = rd();
int ans = 0;
for(int i = 1;i<=k;i++) {
int x = rd(),y = rd();
if(x > y) ans++;
}
wt(k - ans);
return 0;
}
B1.Strange Madoka Game#
题面:
题目背景
English statement. You must submit your code at the Chinese version of the statement.
いつも君の側にいるから / 无论何时我都与你同在
儚すぎて / 世界如此虚幻
消えて行きそうな世界 / 似乎随时随地都会消失
だけど君がいる / 然而只要有你
それだけで守りたいと思った / 我就心甘情愿将它守护题目描述
本题和 B2 是不同的问题,请分别仔细阅读两题题面。
这是一道交互题。
提示:我们在题目描述的最后提供了一份简要的、形式化描述的题面。
圆和焰在玩游戏。
圆在心中想了一个非负整数 \(m\),让焰猜出 \(m\) 的值。当然,圆不会为难焰,所以 \(\textcolor{red}{0}\le m\le 10^{17}\)。圆不会作弊,也就是说,圆不会悄悄更换想好的数字。
焰可以问圆:对于正整数 \(x\),\(m\bmod x\) 的值是多少。焰需要用最少的询问次数来猜出 \(m\) 的值。(为了得到全部分数,最多只能使用 \(2\) 次询问。)
圆一共和焰玩了 \(T\) 次游戏。然而,焰的数学很差,于是她找来了你,来帮她猜出这 \(T\) 次游戏的答案。
形式化地:有一个隐藏的非负整数 \(m\in [\textcolor{red}{0},10^{17}]\)。每次询问给定正整数 \(x\),回答 \(m\bmod x\)。用最少的询问次数找出 \(m\)。共有 \(T\) 组测试数据。\(m\) 的数值在事先确定,不会根据询问变化,也就是说,交互库是非适应性的。
【实现细节】
对于每个测试点,输入一行一个正整数 \(T\),表示游戏次数。
对于每组测试数据,你可以用以下的格式发起一次询问:
\(\texttt{? }x\):询问 \(m\bmod x\) 的值。你需要保证 \(x\) 是正整数,且 \(x \in \textcolor{red} {[1,4\times 10^8]}\)。
从标准输入流读入一个整数表示答案。特别地,若整数是 \(\texttt{-1}\),你的程序已经被判定为 \(\texttt{Wrong Answer}\),此时你需要立刻终止程序。
你可以用以下的格式回答答案:
- \(\texttt{! }m\):回答 \(m\) 的值。
在发起询问后,需要刷新缓冲区,否则可能导致 TLE。
- 在 C++ 中,使用
fflush(stdout)
或cout.flush()
。 特别地,利用std::endl
来换行也会刷新缓冲区。- 在 Pascal 中,使用
flush(output)
。- 在 Python 中,使用
stdout.flush()
。- 对于其它语言,可自行查阅文档。
输入格式
见【实现细节】。
输出格式
见【实现细节】。
样例 #1
样例输入 #1
1 1 2
样例输出 #1
? 2 ? 3 ! 5
提示
数据范围
对于 \(100\%\) 的数据,保证 \(1\le T\le 100\),\(m\) 为非负整数,\(\textcolor{red}{0}\le m\le 10^{17}\)。
评分方式
记单个测试点中,你的询问次数的最大值为 \(Q\)。则得分 \(\mathrm{score}\) 如下所示:
\[\begin{aligned} \mathrm{score}=\begin{cases} 0, & Q\in [81,+\infty) \\ 10, & Q\in [11,81) \\ 25, & Q\in [3,11) \\ 50, & Q\in [0,3) \\ \end{cases} \end{aligned} \]本题得分为所有测试点得分的最小值。
B1 交互题
考虑用两个相差为一的数 \(x,y\) 去询问,
因为 \(m = ax + by\) 必定成立,那么 \(ax \equiv m (\operatorname{mod}\ y)\)、\(by \equiv m(\operatorname{mod}\ x)\) 也成立。
把 \(m\) 看做未知数,套上 \(\operatorname{CRT}\) 就可以求解了。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define LL long long
int query(int x) {
cout<<"? "<<x<<endl;
int c;
cin>>c;
return c;
}
void answer(int x) {
cout<<"! "<<x<<endl;
}
void exgcd(int a,int b,int &x,int &y) {
if(!b) {
x = 1;
y = 0;
}else {
exgcd(b,a%b,x,y);
int t = x;
x = y;
y = t - a/b * y;
}
}
LL CRT(int k, LL* a, LL* r) {
LL n = 1, ans = 0;
for (int i = 1; i <= k; i++) n = n * r[i];
for (int i = 1; i <= k; i++) {
LL m = n / r[i], b, y;
exgcd(m, r[i], b, y);
ans = (ans + a[i] * m * b % n) % n;
}
return (ans % n + n) % n;
}
void solve() {
int x = query(399999999),y = query(400000000);
int a[3] = {0,x,y},r[3] = {0,399999999,400000000};
if(x == y) {answer(x);return;}
else {
int k = CRT(2,a,r);
answer(k);
return;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T;
cin>>T;
while(T--) solve();
return 0;
}
B2.Strange Homura Game#
题面:
题目背景
English statement. You must submit your code at the Chinese version of >the statement.
伝えたいコトバは たったひとつ / 想要传达的言语 唯有一个
キミがいたから 強くなれた / 因有你在 我变坚强了
2 人ならどんな空も飛べるね / 只要两人一起就能任意飞翔
キミをいつでも 信じてるから / 因为一直都 坚信着你
ずっとずっと夢を見ていよう / 能同做此梦不复醒吧题目描述
本题和 B1 是不同的问题,请分别仔细阅读两题题面。
这是一道交互题。
提示:我们在题目描述的最后提供了一份简要的、形式化描述的题面。
焰和圆在玩游戏。
焰在心中想了一个正整数 \(m\),让圆猜出 \(m\) 的值。当然,焰不会为难圆,所以 \(\textcolor{red}{2}\le m\le 10^{17}\)。焰不会作弊,也就是说,焰不会悄悄更换想好的数字。
圆可以问焰:对于非负整数 \(x\),\(x\bmod m\) 的值是多少。圆需要用最少的询问次数来猜出 \(m\) 的值。(为了得到全部分数,最多只能使用 \(2\) 次询问。)
焰一共和圆玩了 \(T\) 次游戏。圆的数学很好,在 $\varepsilon $ 秒内就秒了这题,但是她在军训,于是她找来了你,来帮她猜出这 \(T\) 次游戏的答案。
形式化地:有一个隐藏的正整数 \(m\in [\textcolor{red}{2},10^{17}]\)。每次询问给定非负整数 \(x\),回答 \(x\bmod m\)。用最少的询问次数找出 \(m\)。共有 \(T\) 组测试数据。\(m\) 的数值在事先确定,不会根据询问变化,也就是说,交互库是非适应性的。
【实现细节】
对于每个测试点,输入一行一个正整数 \(T\),表示游戏次数。
对于每组测试数据,你可以用以下的格式发起一次询问:
\(\texttt{? }x\):询问 \(x\bmod m\) 的值。你需要保证 \(x\) 是非负整数,且 \(x \in \textcolor{red}{ [0,10^{18}]}\)。
从标准输入流读入一个整数表示答案。特别地,若整数是 \(\texttt{-1}\),你的程序已经被判定为 \(\texttt{Wrong Answer}\),此时你需要立刻终止程序。
你可以用以下的格式回答答案:
- \(\texttt{! }m\):回答 \(m\) 的值。
在发起询问后,需要刷新缓冲区,否则可能导致 TLE。
- 在 C++ 中,使用
fflush(stdout)
或cout.flush()
。 特别地,利用std::endl
来换行也会刷新缓冲区。- 在 Pascal 中,使用
flush(output)
。- 在 Python 中,使用
stdout.flush()
。- 对于其它语言,可自行查阅文档。
输入格式
见【实现细节】。
输出格式
见【实现细节】。
样例 #1
样例输入 #1
1 0 1
样例输出 #1
? 5 ? 1 ! 5
提示
数据范围
对于 \(100\%\) 的数据,保证 \(1\le T\le 100\),\(m\) 为正整数,\(\textcolor{red}{2}\le m\le 10^{17}\)。
评分方式
记单个测试点中,你的询问次数的最大值为 \(Q\)。则得分 \(\mathrm{score}\) 如下所示:
\[\begin{aligned} \mathrm{score}=\begin{cases} 0, & Q\in [101,+\infty) \\ 30, & Q\in [3,101) \\ 50, & Q\in [0,3) \\ \end{cases} \end{aligned} \]本题得分为所有测试点得分的最小值。
我们先考虑询问一个数后的信息:
我们假设询问一个数 \(x\),我们可以得到 \(x \equiv d (\operatorname{mod}\ m)\)。
也就是 \(x = d + k m\),那么简单变形 \(x - d = km\)
那么询问 \(x - d\) (\(x > d\)) 肯定是 \(0\) (\(x - d \equiv 0\ (\operatorname{mod}\ m)\))。
我们怎么通过这个信息解出 \(m\) 呢?
发动人类智慧:\(x - d - 1 \equiv m - 1\ (\operatorname{mod}\ m)\)。
我们只需要询问 \(x - d - 1\) 即可得到 \(m - 1\)。
最后将询问的值加一作为答案回答!
因为 \(x \in [0,10^{18}]\) 而 \(m \in [0,10^{17}]\),所以当我们询问 \(10^{18}\) 时必定有 \(x > d\)。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int query(int x) {
cout<<"? "<<x<<endl;
int c;
cin>>c;
return c;
}
void answer(int x) {
cout<<"! "<<x<<endl;
}
void solve() {
const int N = 1e18;
int x = query(N);
int k = N - x;
int y = query(k - 1);
answer(y + 1);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
int T;cin>>T;
while(T--) solve();
return 0;
}
C.Strange Train Game#
题面:
题目背景
English statement. You must submit your code at the Chinese version of the statement.
SFM 团队又断网了,于是玩起了 Mini Metro,结果发现游戏更新了,列车要自己组装,于是有了这题。
题目描述
提示:我们在题目描述的最后提供了一份简要的、形式化描述的题面。
SFM 号列车由 \(n\) 节车厢组成,编号为 \(1\sim n\)。每节车厢有一个舒适度 \(a_i\in \{0,1\}\),\(0\) 代表不舒适,\(1\) 代表舒适。管理组想要让舒适的车厢的编号尽小,也就是说,让 \(a\) 的字典序最大。
为此,管理组运来了一辆 \(n\) 节车厢的备用车,舒适度表示为 \(b_i\in \{0,1\}\)。共有 \(m\) 个可进行的操作,第 \(i\) 个操作的操作参数为 \(l_i,r_i\),表示 \(\forall l_i\le k\le r_i\),交换 \(a_k,b_k\)。
可以从小到大依次决定是否执行每个操作,但是一共有 \(2^m\) 种方案,于是,管理组找来了你,帮忙选出一种最优的方案,最大化 \(a\) 的字典序。只需要输出最终得到的 \(a\) 即可。
形式化地:给定长度为 \(n\) 的 \(01\) 串 \(a,b\),给定 \(2m\) 个正整数 \(l_i,r_i\)。对于 \(i=1,2,\cdots,m\),依次执行以下操作:
- 选择是否执行第 \(i\) 次操作。
- 如果执行,则对于 \(k=l_i,l_{i}+1,\cdots,r_i\),交换 \(a_k,b_k\)。
最大化 \(a\) 的字典序并输出最终的结果。
输入格式
第一行,两个正整数 \(n,m\),表示字符串的长度和操作次数。
第二行,一个长度为 \(n\) 的 \(01\) 串 \(a\)。
第三行,一个长度为 \(n\) 的 \(01\) 串 \(b\)。
接下来 \(m\) 行,每行两个正整数 \(l_i,r_i\),描述第 \(i\) 个操作。
输出格式
输出一行长度为 \(n\) 的 \(01\) 串,表示最优操作后的 \(a\)。
样例 #1
样例输入 #1
10 5 0101011001 0101001110 5 10 2 6 1 10 6 6 3 4
样例输出 #1
0101011110
提示
本题采用捆绑测试。
- Subtask 1(20 pts):\(1\le n,m\le 20\);
- Subtask 2(30 pts):\(l_i\) 互不相同,\(a_i \ne b_i\);
- Subtask 3(30 pts):\(1 \le n ,m \le 10^3\);
- Subtask 4(20 pts):无限制;
对于 \(100\%\) 的数据,保证:
- \(1\le n,m\le 2\times 10^5\);
- \(1\le l_i\le r_i\le n\)。
我是蒟蒻,只想到了 \(\mathcal{O}(n\log n)\) 的做法。
SubTask 1
考虑暴力,枚举每一种选择情况,时间复杂度 \(\mathcal{O}(2^mn\log n)\) 。
code:
namespace baoli{
array<int,2> w[N];
string A,B;
void start() {
auto cmp = [&](string x,string y) -> bool{
for(int i = 0;i<x.size();i++) {
if(x[i] < y[i])
return true;
else if(x[i] > y[i])
return false;
}
return false;
};
for(int i = 0;i<m;i++) cin>>w[i][0]>>w[i][1];
for(int i = 0;i<n;i++) A.push_back('0');
for(int k = 0;k<(1<<m);k++) {
sgt::build(1,1,n);
B.clear();
for(int i = 0;i<m;i++)
if((k >> i) & 1)
sgt::update(1,1,n,w[i][0],w[i][1],1); // <- 区间加单点查线段树的更新操作
for(int i = 0;i<n;i++) {
int re = sgt::query(1,1,n,i + 1) & 1; // <- 区间加单点查线段树的查询操作 (判断目前位是否被交换)
if(re) B.push_back(b[i]);
else B.push_back(a[i]);
}
if(cmp(A,B)) A = B;// 比较答案
}
cout<<A;
}
}
SubTask 2
当每一位 \(a,b\) 字符都不同,且 \(l_i\) 互不相同,我们贪心的使用区间交换。
从第 \(1\) 位到最后 \(1\) 位遍历,\(a\) 中每有一位非 \(1\) ,考虑是否有从当前点出发的区间交换操作并使用。
code:
namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
int s[N<<2],tag[N<<2];
void build(int p,int pl,int pr) {
tag[p] = 0;
s[p] = 0;
if(pl == pr) {s[p] = 0;return;}
build(ls,pl,mid);
build(rs,mid+1,pr);
}
void addtag(int p,int pl,int pr,int d) {
tag[p] += d;
s[p] += (pr - pl + 1) * d;
}
void push_down(int p,int pl,int pr) {
if(tag[p]) {
addtag(ls,pl,mid,tag[p]);
addtag(rs,mid+1,pr,tag[p]);
tag[p] = 0;
}
}
void update(int p,int pl,int pr,int l,int r,int d) {
if(l <= pl && pr <= r) {addtag(p,pl,pr,d);return;}
push_down(p,pl,pr);
if(l <= mid) update(ls,pl,mid,l,r,d);
if(r > mid) update(rs,mid+1,pr,l,r,d);
}
int query(int p,int pl,int pr,int k) {
if(pl == pr) return tag[p];
push_down(p,pl,pr);
if(k <= mid) return query(ls,pl,mid,k);
else return query(rs,mid+1,pr,k);
}
}
signed main() {
cin>>n>>m;
cin>>a;
cin>>b;
for(int i = 1;i<=m;i++) {
int x,y;
cin>>x>>y;
x--,y--;
t[x].emplace_back(y);
}
for(int i = 0;i<n;i++) {
int x = a[i] - '0',y = b[i] - '0';
if(((sgt::query(1,1,n,i) & 1) ? y : x) == 1) {putchar('1');continue;}
else {
for(int v : t[i])
sgt::update(1,1,n,i,v,1);
putchar(((sgt::query(1,1,n,i) & 1) ? y : x) + '0');
}
}
return 0;
}
Subtask 3
神秘做法,不清楚。
\(\mathcal{O}(n\log n)\) 正解
考虑 SubTask 2 给我们带来的启示:
- 只有 \(a_i \not = b_i\) 时,才考虑交换操作。
- 化归为 SubTask 2 的 \(l_i\) 互不相等的情况,就可以求得正解。
针对第一条启示,我们可以挑出 \(a_i \not = b_i\) 的位置 \(st_i\)(加入栈中),同时将交换区间的 \(l_i,r_i\) 改为 \(st\) 中的一段连续区间,只在上面计算答案。
类似于异或线性基,我们可以将 任意选择这 \(n\) 个同起点的区间交换操作 转变为 任意选择 \(n\) 个互不相交的区间交换操作。
大家可以自行模拟一下。
那么,我们只需要将每个同起点的区间排序,去重(因为我们在做第一条启示时,可能会出现相同区间的情况),然后将大区间拆分成小区间,即可化归为 SubTask 2,AC 此题。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
int n,m,st[N],top;
char c[N],d[N];
vector<int> t[N];
namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
int s[N<<2],tag[N<<2];
void addtag(int p,int pl,int pr,int d) {
tag[p] += d;
s[p] += (pr - pl + 1) * d;
}
void push_down(int p,int pl,int pr) {
if(tag[p]) {
addtag(ls,pl,mid,tag[p]);
addtag(rs,mid+1,pr,tag[p]);
tag[p] = 0;
}
}
void update(int p,int pl,int pr,int l,int r,int d) {
if(l <= pl && pr <= r) {addtag(p,pl,pr,d);return;}
push_down(p,pl,pr);
if(l <= mid) update(ls,pl,mid,l,r,d);
if(r > mid) update(rs,mid+1,pr,l,r,d);
}
int query(int p,int pl,int pr,int k) {
if(pl == pr) return tag[p];
push_down(p,pl,pr);
if(k <= mid) return query(ls,pl,mid,k);
else return query(rs,mid+1,pr,k);
}
}
int ans[N];
signed main() {
scanf("%lld %lld",&n,&m);
scanf("%s %s",c + 1,d + 1);
for(int i = 1;i<=n;i++) {
if(c[i] != d[i])
st[++top] = i;
else ans[i] = c[i] - '0';
}
st[++top] = n + 1;
for(int i = 1;i<=m;i++) {
int x,y;
scanf("%lld%lld",&x,&y);
x = lower_bound(st + 1,st + top + 1,x) - st;
y = upper_bound(st + 1,st + top + 1,y) - st - 1;
if(x > y) continue;
t[x].emplace_back(y);
}
for(int i = 1;i<top;i++) {
int x = c[st[i]] - '0',y = d[st[i]] - '0';
int r = i;
sort(t[i].begin(),t[i].end());
int tc = unique(t[i].begin(),t[i].end()) - t[i].begin() - 1;
for(int v = 0;v<=tc;v++) {
if(r^i) t[r].emplace_back(t[i][v]);
else if((sgt::query(1,1,top,i) & 1 ? y : x) != 1)
sgt::update(1,1,top,i,t[i][v],1);
r = t[i][v] + 1;
}
ans[st[i]] = (sgt::query(1,1,top,i) & 1) ? y : x;
}
for(int i = 1;i<=n;i++) printf("%lld",ans[i]);
return 0;
}
复杂度分析:
这里的拆区间操作最坏时间复杂度是 \(O(m \log m)\)。
最坏情况:
考虑对于每一位 \(i\) ,都有 \([i \sim (i +1 + n) / 2]\) 和 \([i\sim i]\) 两个区间交换操作。
在 第 \(1\) 位 额外有一个 \([1 \sim n]\) 的区间交换操作,
这个 \([1 \sim n]\) 区间交换操作会从大区间一路折半直到区间大小为 1 的时候才停止。
而这种区间只能存在一个,故时间复杂度最坏为 \(O(m\log m)\)。
线段树查询复杂度为 \(O(n\log n)\)
故复杂度大概是 \(O(n\log n + m\log m)\) (因为拆区间的复杂度是独立的)
我赛时写代码比较唐,可以用异或前缀和取代线段树降为 \(O(n + m\log m)\)
因为 \(n,m\) 同阶,所以复杂度 \(O(m \log m)\)不变
2024/10/03#
回学校集训!
P6192 【模板】最小斯坦纳树#
题面:
题目描述
给定一个包含 \(n\) 个结点和 \(m\) 条带权边的无向连通图 \(G=(V,E)\)。
再给定包含 \(k\) 个结点的点集 \(S\),选出 \(G\) 的子图 \(G'=(V',E')\),使得:
\(S\subseteq V'\);
\(G'\) 为连通图;
\(E'\) 中所有边的权值和最小。
你只需要求出 \(E'\) 中所有边的权值和。
输入格式
第一行:三个整数 \(n,m,k\),表示 \(G\) 的结点数、边数和 \(S\) 的大小。
接下来 \(m\) 行:每行三个整数 \(u,v,w\),表示编号为 \(u,v\) 的点之间有一条权值为 \(w\) 的无向边。
接下来一行:\(k\) 个互不相同的正整数,表示 \(S\) 的元素。
输出格式
第一行:一个整数,表示 \(E'\) 中边权和的最小值。
样例 #1
样例输入 #1
7 7 4 1 2 3 2 3 2 4 3 9 2 6 2 4 5 3 6 5 2 7 6 4 2 4 7 5
样例输出 #1
11
提示
【样例解释】
样例中给出的图如下图所示,红色点为 \(S\) 中的元素,红色边为 \(E'\) 的元素,此时 \(E'\) 中所有边的权值和为 \(2+2+3+4=11\),达到最小值。
【数据范围】
对于 \(100\%\) 的数据,\(1\leq n\leq 100,\ \ 1\leq m\leq 500,\ \ 1\leq k\leq 10,\ \ 1\leq u,v\leq n,\ \ 1\leq w\leq 10^6\)。
保证给出的无向图连通,但 可能 存在重边和自环。
考虑状态压缩,我们设 \(f_{S,i}\) 为经过了 \(S\) 中的点,以 \(i\) 为结尾的最小代价。
每个点 \(u\) 对应编号 \(id_u\),有 \(f_{2^{id_u},u} = 0\)。
因为答案一定是一颗树,故讨论一下:
- 子节点只有一个,则 \(f_{S,u} \leftarrow f_{S,v} + w(u,v)\)
- 子节点大于一个,则 \(f_{S,u} \leftarrow f_{T,u} + f_{S\oplus T,u}\)
第 \(1\) 中情况,我们可以用最短路解决;第 \(2\) 中情况可以用状态压缩技巧:枚举子集解决!
最后求 \(\min_{i = 0}^{n - 1} f_{2_{k} - 1,i}\) 即可。
AC-code:
#include<bits/stdc++.h>
using namespace std;
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
int n,m,k,dp[1024][101],vis[101];
int head[101],nxt[1005],to[1005],val[1005],cnt;
void init() {memset(head,-1,sizeof(head));cnt = 0;}
void add(int u,int v,int w) {
nxt[cnt] = head[u];
to[cnt] = v;
val[cnt] = w;
head[u] = cnt++;
}
void spfa(int S) {
queue<int> q;
for(int i = 0;i<n;i++)
if(dp[S][i] != 0x3f3f3f3f)
q.emplace(i),vis[i] = true;
while(!q.empty()) {
int c = q.front();
q.pop();
vis[c] = false;
for(int i = head[c];~i;i = nxt[i]) {
int y = to[i],w = val[i];
if(dp[S][y] > dp[S][c] + w) {
dp[S][y] = dp[S][c] + w;
if(vis[y]) continue;
q.emplace(y);vis[y] = true;
}
}
}
}
signed main() {
memset(dp,0x3f,sizeof(dp));
init();
n = rd(),m = rd(),k = rd();
for(int i = 1;i<=m;i++) {
int u = rd(),v = rd(),w = rd();
u--,v--;
add(u,v,w);add(v,u,w);
}
for(int i = 0;i<k;i++) {
int x = rd();x--;
dp[1<<i][x] = 0;
}
for(int S = 1;S < (1<<k);S++) {
for(int T = S & (S - 1);T;T = S & (T - 1)) {
if(T >= (S ^ T))
for(int i = 0;i<n;i++)
dp[S][i] = min(dp[S][i],dp[T][i] + dp[S ^ T][i]);
}
spfa(S);
}
wt(*min_element(dp[(1<<k) - 1],dp[(1<<k) - 1] + n));
return 0;
}
P7450 [THUSCH2017] 巧克力#
题面:
题目描述
「人生就像一盒巧克力,你永远不知道吃到的下一块是什么味道。」
明明收到了一大块巧克力,里面有若干小块,排成 \(n\) 行 \(m\) 列。每一小块都有自己特别的图案 ,它们有的是海星,有的是贝壳,有的是海螺……其中还有一些因为挤压,已经分辨不出是什么图案了。明明给每一小块巧克力标上了一个美味值 \(a_{i,j}\)(\(0\le a_{i,j}\le 10^6\)),这个值越大,表示这一小块巧克力越美味。
正当明明咽了咽口水,准备享用美味时,舟舟神奇地出现了。看到舟舟恳求的目光,明明决定从中选出一些小块与舟舟一同分享。
舟舟希望这些被选出的巧克力是连通的(两块巧克力连通当且仅当他们有公共边),而且这些巧克力要包含至少 \(k\)(\(1\le k\le 5\))种。而那些被挤压过的巧克力则是不能被选中的。
明明想满足舟舟的愿望,但他又有点「抠」,想将美味尽可能多地留给自己。所以明明希望选出的巧克力块数能够尽可能地少。如果在选出的块数最少的前提下,美味值的中位数(我们定义 \(n\) 个数的中位数为第 \(\left\lfloor\frac{n+1}{2}\right\rfloor\) 小的数)能够达到最小就更好了。
你能帮帮明明吗?
输入格式
每个测试点包含多组测试数据。
输入第一行包含一个正整数 \(T\)(\(1\le T\le 5\)),表示测试数据组数。
对于每组测试数据:
输入第一行包含三个正整数 \(n,m\) 和 \(k\);
接下来 \(n\) 行,每行 \(m\) 个整数,表示每小块的图案 \(c_{i,j}\)。若 \(c_{i,j}=-1\) 表示这一小块受到过挤压,不能被选中;
接下来 \(n\) 行,每行 \(m\) 个整数,表示每个小块的美味值 \(a_{i,j}\)。
输出格式
输出共包括 \(T\) 行,每行包含两个整数,用空格隔开,即最少的块数和最小的美味值中位数。
若对于某组测试数据,不存在任意一种合法的选取方案,请在对应行输出两个 \(-1\)。
样例 #1
样例输入 #1
1 5 4 5 3 4 3 4 5 5 -1 5 -1 4 5 5 5 5 4 2 1 -1 2 4 1 3 1 1 3 2 3 3 4 4 4 5 8 9 9 5 7 2 6 3
样例输出 #1
9 5
提示
测试点编号 \(n,m\) 的限制 \(c_{i,j}\) 的限制 部分分说明 1 \(n=1,1\le m\le233\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le n\times m\) \(\text{A}\) 2 \(1\le n\times m\le 20\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le n\times m\) \(\text{A}\) 3~4 \(n=2,m=15\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le n\times m\) \(\text{A}\) 5~6 \(1\le n\times m\le 30\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le n\times m\) \(\text{A}\) 7~9 \(1\le n\times m\le 50\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le8\) \(\text{A}\) 10 \(1\le n\times m\le 233\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le8\) \(\text{A}\) 11~12 \(1\le n\times m\le 233\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le8\) \(\text{B}\) 13~15 \(1\le n\times m\le 233\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le14\) \(\text{B}\) 16~20 \(1\le n\times m\le 233\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le n\times m\) \(\text{B}\) 21 \(1\le n\times m\le 233\) \(c_{i,j}=-1\) 或 \(1\le c_{i,j}\le n\times m\) 该测试点不计分。 \(\text{A}\):若输出的最少块数均正确,但最小中位数存在错误,选手可以获得该测试点 \(80\%\) 的分数。
\(\text{B}\):若输出的最少块数均正确,但最小中位数存在错误,选手可以获得该测试点 \(60\%\) 的分数。
很好很好的 \(\text{color-coding}\),让我的随机化旋转!
我们很难去描述 \(k\) 中不同的颜色,但我们发现 \(k \in [1\sim 5]\)。
这意味着我们能乱搞(随机化大法)!
我们将每种颜色随机映射到 \([0,k)\) 中,我们就自欺欺人的在上面跑最小斯坦纳树。
当最优解包含的 \(k\) 个点被分配到不同的颜色中即可正确,一次成功的概率大概是 \(P=k!/k\)。
这个的出错概率很高,怎么办呢? ——答案是 多随几次
考虑随机 \(200\) 次,正确性就已经可以接受了。
我们用斯坦纳树先求出最小的块数,如果最小的块数都无法满足要求,直接输出 0 0
最小化中位数:这个也不好办,不好办的时候,考虑二分总是不错的选择!
我们二分中位数,如果大于二分值,记为 \(1\),否则记为 \(-1\)。
我们在去跑多次随机化最小斯坦纳树,与 \(0\) 比较,如果大,说明这个二分值偏小,否则说明二分值偏大。
最后就可以得到最优解!
AC-code:
#include<bits/stdc++.h>
using namespace std;
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
int dx[4] = {1,-1,0,0};
int dy[4] = {0,0,1,-1};
const int inf = 0x3f3f3f3f,N = 300;
mt19937 rnd(20080623);
int c[N][N],a[N][N],f[N][N][1<<6],w[N][N],col[N],to[N],vis[N][N];
void solve() {
int n = rd(),m = rd(),k = rd(),top = 0;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=m;j++)
col[++top] = c[i][j] = rd();
sort(col + 1,col + top + 1);
top = unique(col + 1,col + top + 1) - col - 1;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=m;j++)
a[i][j] = rd(),w[i][j] = 1;
auto check = [&](int x,int y) -> bool {
return (x >= 1 && x <= n && y >= 1 && y <= m);
};
auto spfa = [&](int S) -> void {
queue<array<int,2>> q;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=m;j++)
if(f[i][j][S] ^ inf)
q.emplace(array<int,2>{i,j});
while(!q.empty()) {
array<int,2> p = q.front();
q.pop();
vis[p[0]][p[1]] = false;
for(int i = 0;i<4;i++) {
int tx = p[0] + dx[i];
int ty = p[1] + dy[i];
int W = w[tx][ty];
if(c[tx][ty] == -1 || !check(tx,ty)) continue;
if(f[tx][ty][S] > f[p[0]][p[1]][S] + W) {
f[tx][ty][S] = f[p[0]][p[1]][S] + W;
if(vis[tx][ty]) continue;
q.emplace(array<int,2>{tx,ty});
vis[tx][ty] = true;
}
}
}
};
auto work = [&]() -> int{
int ans = inf;
for(int P = 1;P <= 233;P++) {
shuffle(col + 1,col + top + 1,rnd);
for(int i = 1;i<=top;i++)
to[col[i]] = i % k;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=m;j++){
for(int s = 0;s < (1 << k);s++) f[i][j][s] = inf;
if(~c[i][j]) f[i][j][1 << to[c[i][j]]] = w[i][j];
}
for(int S = 1;S < (1<<k);S++) {
for(int i = 1;i<=n;i++)
for(int j = 1;j<=m;j++)
if(~c[i][j])
for(int T = S & (S - 1);T;T = (T - 1) & S)
if(T >= (S ^ T))
f[i][j][S] = min(f[i][j][T] + f[i][j][S ^ T] - w[i][j],f[i][j][S]);
spfa(S);
}
for(int i = 1;i<=n;i++)
for(int j = 1;j<=m;j++)
ans = min(ans,f[i][j][(1<<k) - 1]);
}
return ans;
};
int re = work();
if(re == inf) {puts("-1 -1");return;}
int l = 0,r = 1e6;
while(l < r) {
int mid = (l + r) >> 1;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=m;j++)
w[i][j] = ((a[i][j] <= mid) ? 9999 : 10001);
int ce = work();
if(ce > re * 10000) l = mid + 1;
else r = mid;
}
wt(re);putchar(' ');wt(l);putchar('\n');
}
signed main() {
int T = rd();
while(T--) solve();
return 0;
}
【MX-X6】梦熊 X 组 · 回归线赛#
A.【MX-X6-T2】もしも#
题面:
题目背景
もしも\(\\\)
数字がない世界だったら\(\\\)
生きる期限なんて\(\\\)
なかったのかな\(\\\)
もしもの話なら良かった\(\\\)
また出逢えるからって\(\\\)
言うんだ\(\\\)
またね。除法能够帮我们消除这个世界上的数字吗?
如果不能,又能否让我们再次相见?
题目描述
假设有正整数序列 \(a_1, a_2, \ldots, a_n\),其中:
- 对于 \(i\geq 3\),满足 \(a_i\) 等于 \(\dfrac{a_{i-2}}{a_{i-1}}\) 上取整;
- 对于任意 \(1\leq i\leq n\),满足 \(1\leq a_i\leq 10^9\)。
现在给定 \(n\) 和 \(a_n\),求任意一组可能的 \(a_1,a_2\)。
其中一个数 \(x\) 上取整等于最小的 \(\geq x\) 的整数。例如 \(\dfrac{7}{3}\) 上取整等于 \(3\),\(4\) 上取整等于 \(4\)。
输入格式
单个测试点包含多组测试数据,第一行一个整数 \(T\) 表示数据组数。
接下来 \(T\) 行,每行两个空格分隔的整数表示该组数据的 \(n,a_n\)。
输出格式
对于每一组数据输出一行两个整数表示一组可能的 \(a_1,a_2\)。如有多种解可任意输出一种。可以证明本题数据范围下一定有解。
样例 #1
样例输入 #1
3 3 1 3 2 6 3
样例输出 #1
114 514 2005 1130 59001 897
提示
【样例解释】
对于三组数据,序列分别为:
- \(a=[114,514,1]\);
- \(a=[2005,1130,2]\);
- \(a=[59001,897,66,14,5,3]\)。
【数据范围】
对于所有数据,满足 \(1\leq T\leq 1000\),\(3\leq n\leq 10^9\),\(1\leq a_n\leq 10^9\)。
共 \(10\) 组数据:
- 对于前 \(2\) 组数据,额外满足 \(n\leq 6\),\(a_n\leq 10\);
- 对于前 \(5\) 组数据,额外满足 \(n\leq 1000\);
- 对于第 \(6,7\) 组数据,额外满足 \(a_n=1\)。
诈骗 Ad-hoc。
我们可以发现 序列可以长成 \(\{x,1,x,1,\ldots\}\) 或 \(\{1,x,1,x,\ldots\}\)
直接判断奇偶构造即可。
#include<bits/stdc++.h>
using namespace std;
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
void solve() {
int n = rd(),t = rd();
if(n & 1) wt(t),putchar(' '),wt(1);
else wt(1),putchar(' '),wt(t);
putchar('\n');
}
signed main() {
int T = rd();
while(T--) solve();
return 0;
}
B.【MX-X6-T3】さよならワンダーランド#
题面:
题目背景
ほら行くよって\(\\\)
手を引いてくれた君は\(\\\)
綺麗な目して言うんだ\(\\\)
僕らあの頃と\(\\\)
何も変わらないから\(\\\)
せめて\(\\\)
二人で夢を見させて能够带你进入孩童时期的梦境中的那个人,该在哪里寻找呢?
题目描述
给定序列 \(a_1, a_2, \dots, a_n\),请对于每一个 \(1\sim n\) 的整数 \(i\) 求任意一个整数 \(j\) 使得以下条件同时成立,或判断不存在这样的 \(j\):
- \(1\leq i+j\leq n\);
- \(a_i \leq j \leq a_{i+j}\)。
输入格式
第一行一个整数 \(n\)。
接下来一行 \(n\) 个空格分隔的整数 \(a_1,a_2,\dots,a_n\)。
输出格式
输出 \(n\) 行。
对于第 \(i\) 行,如果能够找到 \(j\) 使得 \(1\leq i+j\leq n\) 且 \(a_i >\leq j \leq a_{i+j}\) 成立,则先输出一个 \(1\),再输出你找到的 \(j\),空格分隔。如果有多个合法 \(j\) 可输出任意一个。如果不存在这样的 \(j\),则仅输出一个 \(0\)。
样例 #1
样例输入 #1
3 -1 1 4
样例输出 #1
1 2 1 1 0
样例 #2
样例输入 #2
5 1 -1 0 2 -3
样例输出 #2
0 1 -1 1 0 0 1 -3
提示
【样例解释 #1】
\(i=1,j=2\) 时,\(a_i=-1\),\(a_{i+j}=4\),满足 \(a_i\leq j\leq a_{i>+j}\)。
\(i=2,j=1\) 时,\(a_i=1\),\(a_{i+j}=4\),满足 \(a_i\leq j\leq a_{i>+j}\)。
\(i=3\) 时可以证明不存在符合条件的 \(j\)。
【数据范围】
对于所有数据,保证 \(1\leq n \leq 3\times 10^5\),\(-10^9\leq a_i\leq 10^9\)。
捆绑测试,共 3 个 Subtask,具体限制如下所示:
- Subtask 1(17 pts):\(n\leq 1000\)。
- Subtask 2(39 pts):对所有 \(1\leq i\leq n\) 保证 \(a_i\leq >-i\)。
- Subtask 3(44 pts):无特殊限制。
这个 \(j\) 很烦人,不好求。
我们考虑一些更方便的值 \(x\),
我们令 \(x = i + j\),这样 \(x\) 就是下标,很舒服。
让我们代换掉 \(j\),\(j = x - i\),
那么条件就是 \(a_i \leq x - i \leq a_x\),
我们来考究考究:
- \(a_i + i \leq x\)
- \(a_x - x \geq -i\)
那么,我们的目标就已经很明确了,
先在线段树上插入 \(a_i - i\).
在 \(\[a_i + i,n\]\) 中,找到一个 \(w\) 满足 \(a_w - w \geq -i\).
考虑线段树求最大值。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 3e5+5,inf = 0x3f3f3f3f3f3f3f3fLL;
int rt;
namespace sgt{
#define mid ((pl + pr) >> 1)
int ls[N * 50],rs[N * 50];
int cnt;
array<int,2> x[N * 50];
void push_up(int p) {
if(x[ls[p]][0] >= x[rs[p]][0]) {
x[p][0] = x[ls[p]][0];
x[p][1] = x[ls[p]][1];
}else {
x[p][0] = x[rs[p]][0];
x[p][1] = x[rs[p]][1];
}
}
void update(int &p,int pl,int pr,int k,int d) {
if(!p) p = ++cnt;
if(pl == pr) {x[p][0] = d,x[p][1] = pl;return;}
if(k <= mid) update(ls[p],pl,mid,k,d);
else update(rs[p],mid+1,pr,k,d);
push_up(p);
}
int query(int p,int pl,int pr,int l,int r,int d) {
if(l <= pl && pr <= r) {
if(x[p][0] >= d) return x[p][1];
else return inf;
}
if(r <= mid) return query(ls[p],pl,mid,l,r,d);
else if(l > mid) return query(rs[p],mid+1,pr,l,r,d);
else {
int re = query(ls[p],pl,mid,l,r,d);
if(re != inf) return re;
else return query(rs[p],mid+1,pr,l,r,d);
}
}
}
int ans[N];
signed main() {
int n = rd();
vector<int> a(n + 5);
for(int i = 1;i<=n;i++) a[i] = rd();
fill(ans + 1,ans + n + 1,-inf);
for(int i = 1;i<=n;i++)
sgt::update(rt,1,n,i,a[i] - i);
for(int i = n;i >= 1;i--) {
if(a[i] + i > n) {ans[i] = -inf;continue;}
int re = sgt::query(rt,1,n,a[i] + i,n,-i);
if(re != inf) ans[i] = re - i;
else ans[i] = -inf;
}
for(int i = 1;i<=n;i++)
if(ans[i] ^ -inf)
wt(1),putchar(' '),wt(ans[i]),putchar('\n');
else wt(0),putchar('\n');
return 0;
}
D.【MX-X6-T5】再生#
题面:
题目背景
このまま\(\\\)
らったった\(\\\)
音に乗って\(\\\)
今きっと世界で僕だけだ\(\\\)
後ろ向きな歌を聴いて\(\\\)
少しだけ\(\\\)
前向きに生きていく破碎的点依照破碎的规则进行重组,如此再生的一个结构将会是什么样的呢?
题目描述
现有一棵 \(n\) 个点的有标号有根树,给定其长链剖分得到的 top 数组,请你输出有多少种不同的树可以在长链剖分之后得到该 top 数组。答案对 \(20051131\)>(质数)取模。
具体来说,对于一棵树 \(T\),对所有点 \(u\) 定义其树高 \(h_u\):
- 如果 \(u\) 是叶子,则 \(h_u=1\)。
- 否则设 \(u\) 的孩子集合为 \(S_u\),则 \(h_u=\max\limits_{v\in S_u}h_v + 1\)。
给定数组 \(t_{1\cdots n}\),你需要计算有多少种树满足:
- 对于根节点 \(r\),满足 \(t_r=r\)。
- 对于每一个不是叶子的节点 \(u\),存在恰好一个孩子 \(v\) 满足 \(h_v+1=h_u\) 并且 \(t_v=t_u\),其他孩子满足 \(t_v=v\)。
模 \(20051131\)(质数)。
两棵树不同当且仅当它们的根不同或它们的边集不同。
保证答案不为 \(\bf 0\),但是不保证答案在模意义下不为 \(\bf 0\)。
输入格式
第一行一个正整数 \(n\)。
接下来一行,\(n\) 个空格分隔的正整数 \(t_{1\cdots n}\),表示 top 数组。
输出格式
一行一个整数表示答案对 \(20051131\) 取模的值。
样例 #1
样例输入 #1
5 1 1 1 4 4
样例输出 #1
2
样例 #2
样例输入 #2
16 1 2 1 4 1 4 1 4 9 1 1 12 1 1 12 1
样例输出 #2
7181107
提示
【样例解释 #1】
仅有图中的两种树满足条件。
【数据范围】
对于所有数据,保证 \(1\leq n\leq 5\times 10^5\),\(1\leq t_i\leq i\),保证取模前答案不为 \(0\)。
捆绑测试,共 5 个 Subtask,具体限制如下所示:
- Subtask 1(11 pts):\(t_i=1\)。
- Subtask 2(24 pts):\(n\leq 5\)。
- Subtask 3(17 pts):\(n\leq 16\)。
- Subtask 4(22 pts):\(n\leq 2\times 10^3\)。
- Subtask 5(26 pts):无特殊限制。
我们通过题面信息,可以剖出多条链。
然后每条链的 \(siz\) 我们也可以求出来,
因为链除了链头,剩下的序号可以任意排列:\(\prod_{i} (siz_i - 1)!\)
我们考虑每一条链 \(x\),
我们直接找能接上去的点去接,而这样的点数量为 \(\sum{siz_i > siz_x} (siz_i - siz_x)\)
所以 \(ans = \prod_{i} (siz_i - 1)! \cdot \sum{siz_i > siz_x} (siz_i - siz_x)\)
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 5e5 + 5,mod = 20051131;
int fac[N],s[N],siz[N],sum[N];
signed main() {
int n = rd();
fac[0] = 1;
for(int i = 1;i<=n;i++) fac[i] = fac[i - 1] * i % mod;
for(int i = 1;i<=n;i++)
s[i] = rd();
for(int i = 1;i<=n;i++)
siz[s[i]]++;
vector<int> a;
int ans = 1;
for(int i = 1;i<=n;i++)
if(s[i] == i)
a.emplace_back(siz[i]),
ans = (ans * fac[siz[i] - 1]) % mod;
sort(a.begin(),a.end());
for(int i = 0;i<a.size();i++)
if(!i) sum[i] = a[i];
else sum[i] = sum[i - 1] + a[i];
for(auto it = a.begin();it != a.end();it++) {
int c = *it;
int k = lower_bound(a.begin(),a.end(),c + 1) - a.begin();
if(k >= a.size()) break;
ans = (ans * (sum[a.size() - 1] - sum[k - 1] - c * (a.size() - k) % mod + mod) % mod) % mod;
}
wt(ans % mod);
return 0;
}
2024/10/04#
P6623 [省选联考 2020 A 卷] 树#
题面:
题目描述
给定一棵 \(n\) 个结点的有根树 \(T\),结点从 \(1\) 开始编号,根结点为 \(1\) 号结点,每个结点有一个正整数权值 \(v_i\)。
设 \(x\) 号结点的子树内(包含 \(x\) 自身)的所有结点编号为 \(c_1,c_2,\dots,c_k\),定义 \(x\) 的价值为:
\( val(x)=(v_{c_1}+d(c_1,x)) \oplus (v_{c_2}+d(c_2,x)) \oplus \cdots \oplus (v_{c_k}+d(c_k, x)) \)
其中 \(d(x,y)\) 表示树上 \(x\) 号结点与 \(y\) 号结点间唯一简单路径所包含的边数,\(d(x, x) = 0\)。\(\oplus\) 表示异或运算。
请你求出 \(\sum\limits_{i=1}^n val(i)\) 的结果。
输入格式
第一行一个正整数 \(n\) 表示树的大小。
第二行 \(n\) 个正整数表示 \(v_i\)。
接下来一行 \(n-1\) 个正整数,依次表示 \(2\) 号结点到 \(n\) 号结点,每个结点的父亲编号 \(p_i\)。
输出格式
仅一行一个整数表示答案。
样例 #1
样例输入 #1
5 5 4 1 2 3 1 1 2 2
样例输出 #1
12
提示
【样例解释 \(1\)】
\(val(1)=(5+0)\oplus(4+1)\oplus(1+1)\oplus(2+2)\oplus(3+2)=3\)。
\(val(2)=(4+0)\oplus(2+1)\oplus(3+1) = 3\)。
\(val(3)=(1+0)=1\)。
\(val(4)=(2+0)=2\)。
\(val(5)=(3+0)=3\)。
和为 \(12\)。
【数据范围】
对于 \(10\%\) 的数据:\(1\leq n\leq 2501\);
对于 \(40\%\) 的数据:\(1\leq n\leq 152501\);
另有 \(20\%\) 的数据:所有 \(p_i=i-1\)(\(2\leq i\leq n\));
另有 \(20\%\) 的数据:所有 \(v_i=1\)(\(1\leq i\leq n\));
对于 \(100\%\) 的数据:\(1\leq n,v_i \leq 525010\),\(1\leq p_i\leq n\)。
01-trie 的 合并!
考虑整体加 \(1\),在 01-trie 上模拟进位。
整体加 \(1\) 就是在交换 \(0,1\) 两边的树,然后往进位的方向继续进行交换操作。
求解的时候,对 Trie 上每个点分别计算贡献。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 525015;
int v[N],head[N],nxt[N<<1],to[N<<1],cnt;
void init() {memset(head,-1,sizeof(head));cnt = 0;}
void add(int u,int v) {
nxt[cnt] = head[u];
to[cnt] = v;
head[u] = cnt++;
}
int rt[N];
namespace sgt{
int ls[N * 30],rs[N * 30];
int t[N * 30],cnt,s[N * 30];
void push_up(int p,int dep) {
s[p] = s[ls[p]] + s[rs[p]];
t[p] = t[ls[p]] ^ t[rs[p]] ^ ((s[rs[p]] & 1) << dep);
}
void insert(int &p,int x,int dep) {
if(!p) p = ++cnt;
if(dep >= 22) {t[p] = 0;s[p]++;return;}
(x >> dep) & 1 ? insert(rs[p],x,dep + 1) : insert(ls[p],x,dep + 1);
push_up(p,dep);
}
void reverse(int p,int dep) {
if(dep >= 22) return;
reverse(rs[p],dep + 1);
swap(ls[p],rs[p]);
push_up(p,dep);
}
int merge(int x,int y,int dep) {
if(!x || !y) return x + y;
if(dep >= 22) {
s[x] += s[y];
return x;
}
ls[x] = merge(ls[x],ls[y],dep + 1);
rs[x] = merge(rs[x],rs[y],dep + 1);
push_up(x,dep);
return x;
}
int query(int p) {return t[p];}
}
signed main() {
init();
int n = rd();
for(int i = 1;i<=n;i++)
v[i] = rd();
for(int i = 2,k;i<=n;i++)
add(i,k = rd()),add(k,i);
int ans = 0;
auto dfs = [&](auto self,int x,int f) -> void {
for(int i = head[x];~i;i = nxt[i]) {
int y = to[i];
if(y ^ f){
self(self,y,x);
rt[x] = sgt::merge(rt[x],rt[y],0);
}
}
sgt::reverse(rt[x],0);
sgt::insert(rt[x],v[x],0);
ans += sgt::query(rt[x]);
};
dfs(dfs,1,0);
wt(ans);
return 0;
}
P11160 【MX-X6-T6】機械生命体#
题面:
题目背景
許してよ、もう\(\\\)
分かってよ\(\\\)
此処の正体を\(\\\)
僕ですら僕を\(\\\)
愛せないんだって\(\\\)
感情を持った代償をくれよ\(\\\)
狂っている二进制的运算和记忆,能够在机械生命体中还原出人类的情感吗?
题目描述
维护一个可重集 \(S\),初始为空。支持如下操作:
1 x
,你需要在 \(S\) 中加入一个数 \(x\)。2 x
,你需要在 \(S\) 中删除一个数 \(x\)。保证此时 \(S\) 中存在至少一个 \(x\)。如果存在多个 \(x\),则仅删除一个。3 x k v
,你需要对 \(S\) 中所有满足 \(\operatorname{lowbit}(x\oplus y)\geq 2^k\) 的 \(y\) 增加 \(v\) 并对 \(2^{32}\) 取模。4 x
,你需要求出 \(\max\limits_{y\in S} \operatorname{lowbit}(x\oplus y)\)。保证此时 \(S\) 不为空。其中 \(\operatorname{lowbit}(x)\) 表示最大的整数 \(k\),使得 \(k\) 是 \(2\) 的整数次幂并且整除 \(x\)。\(\oplus\) 代表按位异或。
特殊的,我们在本题定义 \(\boldsymbol{\textbf{lowbit}(0)=2^{32}}\)。
输入格式
第一行一个整数 \(q\),代表询问数量。
接下来 \(q\) 行,每行首先输入一个整数 \(opt\) 表示操作类型;如果 \(opt=3\),则接下来依次输入三个整数 \(x,k,v\),否则接下来输入一个整数 \(x\)。
输出格式
对每一次
4
操作,输出一个整数代表答案。样例 #1
样例输入 #1
11 1 1 1 2 1 2 1 3 1 4 4 10 3 2 1 2 2 4 4 16 2 4 4 16
样例输出 #1
8 4 2
提示
【样例解释】
第 \(6\) 次操作时,集合为 \(\{1,2,2,3,4\}\),查询 \(10\) 时,\(\operatorname{lowbit}(10\oplus 2)=\operatorname{lowbit}(8)=8\) 为最大值。
第 \(7\) 次操作后,所有 \(\operatorname{lowbit}(x\oplus 2)\geq 2^1\) 的数增加 \(2\),集合中满足条件的数有 \(2,2,4\),于是集合变为 \(\{1,3,4,4,6\}\)。
第 \(8\) 次操作删去一个 \(4\),集合变为 \(\{1,3,4,6\}\)。
第 \(9\) 次操作查询 \(16\),\(\operatorname{lowbit}(16\oplus 4)=\operatorname{lowbit}(20)=4\) 为最大值。
第 \(10\) 次操作再次删去一个 \(4\),集合变为 \(\{1,3,6\}\)。
第 \(11\) 次操作查询 \(16\),\(\operatorname{lowbit}(16\oplus 6)=\operatorname{lowbit}(22)=2\) 为最大值。
【数据范围】
对于所有数据,\(1\leq q\leq 5\times 10^5\),\(0\leq x,y,v\leq 2^{32}-1\),\(0\leq k\leq 32\)。
捆绑测试,共 5 个 Subtask,具体限制如下所示:
- Subtask 1(7 pts):\(q\leq 10^3\)。
- Subtask 2(16 pts):不存在 3 操作。
- Subtask 3(21 pts):对于 3 操作,\(k=0\)。
- Subtask 4(28 pts):对于 3 操作,\(v=1\)。
- Subtask 5(28 pts):无特殊限制。
01-trie 的合并 \(\rightarrow\) 整体加 \(1\) 写过了,就得写一写 01-trie 的分裂与合并 \(\rightarrow\) 整体加 \(v\)
考虑将需要整体加的部分用 01trie 分裂,裂出来,整体加 \(\text{tag}\)
在 \(y\) 上 继承符合要求的 \(x\) 的子树,然后整体打 \(\text{tag}\),放回去。
询问的时候,如果没有能匹配上 \(x\) 的 \(y \in S\),就返回当前的 \(dep\)
讲起来很简单,实现难的一批
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 5e5+5;
namespace sgt{
int ls[N * 30],rs[N * 30];
int s[N * 30],cnt,tag[N * 30];
void push_up(int p) {s[p] = s[ls[p]] + s[rs[p]];}
void addtag(int p,int v) {tag[p] += v;}
void push_down(int p) {
if(tag[p]) {
if(ls[p]) addtag(ls[p],tag[p] >> 1);
if(rs[p]) addtag(rs[p],(tag[p] + 1) >> 1);
if(tag[p] & 1) swap(ls[p],rs[p]);
tag[p] = 0;
}
}
void insert(int &p,int x,int v,int dep) {
if(!p) p = ++cnt;
if(dep >= 32) {s[p] += v;return;}
push_down(p);
(x >> dep) & 1 ? insert(rs[p],x,v,dep + 1) : insert(ls[p],x,v,dep + 1);
push_up(p);
}
#define t(p,x) (x ? rs[p] : ls[p])
int merge(int x,int y,int dep) {
if(!x || !y) return x + y;
if(dep >= 32) {
s[x] += s[y];
return x;
}
push_down(x);push_down(y);
ls[x] = merge(ls[x],ls[y],dep + 1);
rs[x] = merge(rs[x],rs[y],dep + 1);
push_up(x);
return x;
}
void spilt(int p,int y,int fa,int x,int k,int v,int dep) {
if(dep == k) {
tag[p] += v;
if(p ^ y) {
merge(y,p,dep);
if(ls[fa] == p) ls[fa] = 0;
else rs[fa] = 0;
}
return;
}
push_down(p);push_down(y);
int _x = x & 1,_dx;
v += _x;_dx = v & 1;
if(!t(p,_x)) t(p,_x) = ++cnt;
if(!t(y,_dx)) t(y,_dx) = ++cnt;
spilt(t(p,_x),t(y,_dx),p,x >> 1,k,v >> 1,dep + 1);
push_up(y);push_up(p);
}
void add(int &rt,int x,int k,int v) {spilt(rt,1,0,x,k,v,0);}
int query(int &p,int x,int dep) {
if(!p) {p = ++cnt;}
if(dep >= 32) return dep;
push_down(p);
if((x >> dep) & 1) {
if(s[rs[p]]) return query(rs[p],x,dep + 1);
else return dep;
}else {
if(s[ls[p]]) return query(ls[p],x,dep + 1);
else return dep;
}
}
}
int rt;
void ins() {
int x = rd();
sgt::insert(rt,x,1,0);
}
void del() {
int x = rd();
sgt::insert(rt,x,-1,0);
}
void change() {
int x = rd(),k = rd(),v = rd();
sgt::add(rt,x,k,v);
}
void get() {
int x = rd();
int ans = (sgt::query(rt,x,0));
wt(1LL << ans);
putchar('\n');
}
signed main() {
int q = rd();
while(q--) {
int opt = rd();
switch(opt) {
case 1:
ins();
break;
case 2:
del();
break;
case 3:
change();
break;
case 4:
get();
break;
default:
puts("Error");
exit(0);
break;
}
}
return 0;
}
P4765 [CERC2014] The Imp#
题面:
题面翻译
题目描述
你带着一些来之不易的金币来到了 Ye Olde 魔法商店,想要购买一些妙不可言的魔术物品。商店里有 \(n\) 个魔术实体,每个实体都锁在一个特殊的魔术宝箱中。第 \(i\) 个宝箱(和其中的实体)的售价为 \(c_i\)个金币,而其中实体的价值相当于 \(v_i\) 个金币。你作为曾经完整钻研了《Ye Olde 魔法目录》的顶级做题家,当然毫无疑问地记住了每个盒子和其中实体的售价和价值。
然而像你这样的凡人,只能安全地携带一件魔法实体。因此,你想要得到最宝贵的一个。你本可以直接得到它的——如果不是因为调皮而又神奇的小恶魔的话。
小恶魔可以使用魔法,从而将某一个魔术宝箱内的实体转化为毫无价值的灰尘。当然,他会在你购买一个魔术宝箱后立即对其使用该魔法,这样你就为这个宝箱付了>钱而没能得到里面的实体。因此,你被迫另买一个,再买一个……
小恶魔拥的魔力最多可以用来使用 \(k\) 次魔法。当然,他可以不用完这 \(k\) 次魔法,而你也可以随时空手走开(尽管这是一个奇耻大辱)。但是,如果你成功地买到了到一个实体(而没有被变成灰尘),则你必须保留该实体并离开商店。
你的目标是最大化你的收益(所购实体的价值减去支付的所有费用(包括购买当前实体和之前的灰尘)),而小恶魔则希望将其最小化。如果你和小恶魔都使用最佳策略,那么你的收益将会相当于多少金币?
输入格式
本题每个测试点包含多组数据。
第一行包含一个正整数 \(T\) 表示测试数据组数。
每组数据的第一行包括两个数 \(n\) 和 \(k\) ,分别表示魔术实体个数和小恶魔使用魔法的最大次数。
接下来 \(n\) 行,第 \(i\) 行包括两个数 \(v_i\) 和 \(c_i\),意义如【题目描述】所述。同一行的数用空格隔开。输出格式
对于每组数据,输出一行一个数表示答案。
数据范围与提示
\(1\le n\le1.5\times10^5,0\le k\le9,0\le v_i,c_i\le10^6\)。
样例 #1
样例输入 #1
1 3 1 10 5 8 1 20 12
样例输出 #1
7
很好很好的线性 \(dp\),让我的贪心旋转。
我们考虑了收益,损失,但是我们不知道到底怎么动态规划!
我们得找找性质:
很难发现,我们按 \(v_i\) 从小到大排序为购买顺序最优。
证明:
假设有 \(v_i \leq v_j\),交换前收益为 \(\min(v_j - c_i - c_j,v_i - c_i)\),交换后为 \(\min(v_i - c_i - c_j,v_j - c_j)\)。
而我们知道,在 \(v_i \leq v_j\) 时,\(v_i - c_i - c_j \leq \min(v_i - c_i,v_j - c_i - c_j)\)。
所以不交换购买顺序更优!
而小恶魔的功能就是你可以挑 \(x\) 个物品,ta 会给你删去 \(x - 1\) 个物品的收益。
所以我们考虑从大到小 \(dp\) 删除的方案,外面选择最大收益,内部取出小恶魔进行删除的价值。
AC-code:
#include<bits/stdc++.h>
using namespace std;
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
void solve() {
int n = rd(),k = rd();
vector<array<int,2>> c(n + 5);//chest
for(int i = 1;i<=n;i++)
c[i][0] = rd(),c[i][1] = rd();
vector<array<int,10>> f(n + 5);
int loser = 0;
for(int i = 1;i<=n;i++) loser += c[i][1];
if(k >= n) {wt(-loser);return;}
sort(c.begin() + 1,c.begin() + n + 1,[&](array<int,2> x,array<int,2> y) {return x[0] > y[0];});
for(int i = 1;i<=n;i++) f[i][0] = max(f[i - 1][0],c[i][0] - c[i][1]);
for(int i = 1;i<=n;i++) {
for(int j = 1;j<=k;j++) {
f[i][j] = max(f[i - 1][j],min(f[i - 1][j - 1] - c[i][1],c[i][0] - c[i][1]));
}
}
wt(f[n][k]);
putchar('\n');
}
signed main() {
int T = rd();
while(T--) solve();
return 0;
}
2024/10/05#
P5787 二分图 /【模板】线段树分治#
题面:
题目描述
神犇有一个 \(n\) 个节点的图。
因为神犇是神犇,所以在 \(k\) 时间内有 \(m\) 条边会出现后消失。
神犇要求出每一时间段内这个图是否是二分图。
这么简单的问题神犇当然会做了,于是他想考考你。
原 BZOJ4025。
输入格式
第一行三个整数 \(n,m,k\)。
接下来 \(m\) 行,每行四个整数 \(x,y,l,r\),表示有一条连接 \(x,y\) 的边在 \(l\) 时刻出现 \(r\) 时刻消失。
输出格式
\(k\) 行,第 \(i\) 行一个字符串
Yes
或No
,表示在第 \(i\) 时间段内这个图是否是二分图。样例 #1
样例输入 #1
3 3 3 1 2 0 2 2 3 0 3 1 3 1 2
样例输出 #1
Yes No Yes
提示
样例说明
\(0\) 时刻,出现两条边 \((1,2)\) 和 \((2,3)\)。
第 \(1\) 时间段内,这个图是二分图,输出
Yes
。\(1\) 时刻,出现一条边 \((1,3)\)。
第 \(2\) 时间段内,这个图不是二分图,输出
No
。\(2\) 时刻,\((1,2)\) 和 \((1,3)\) 两条边消失。
第 \(3\) 时间段内,只有一条边 \((2,3)\),这个图是二分图,输出
Yes
。数据范围
\(n,k = 10^5\),\(m = 2\times 10^5\)。\(1 \le x,y \le n\),\(0 \le l \le r \le k\)。
注意
本题设有 hack 数据(Subtask \(2\)),计 \(0\) 分,但若没有通过 hack 数据则不算通过本题。
线段树分治!
我们考虑建出 \(1\sim k\) 的时间层,使用可撤销并查集。
因为二分图没有奇环,所以我们用扩展域并查集,查询是否存在奇环。
具体方法就是一条边的 \(u,v\),我们让 \(u \rightarrow (v + n)\),\(v \rightarrow (u + n)\),如果 \(u \rightleftarrows v\),说明同侧的点相连,也就是出现了奇环。
每次插入线段,只放在节点的父节点,因为当我们分治时,会进入并查集(相当于树上差分)。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 4e5+5;
int n,m,s[N],h[N],k,top;
array<int,3> st[N];
vector<array<int,2>> t[N];
bool ans[N];
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
int find(int x) {
while(s[x] ^ x) x = s[x];
return s[x];
}
void merge(int x,int y) {
x = find(x),y = find(y);
if(h[x] > h[y]) swap(x,y);
st[++top] = array<int,3>{x,y,h[y]};
s[x] = y;
h[y] = h[y] + (h[x] == h[y]);
}
void insert(int p,int pl,int pr,int l,int r,array<int,2> w) {
if(l > pr || r < pl) return;
if(l <= pl && pr <= r) {t[p].emplace_back(w);return;}
insert(ls,pl,mid,l,r,w);
insert(rs,mid+1,pr,l,r,w);
}
void solve(int p,int pl,int pr) {
int flag = 0;
int now = top;
for(auto w : t[p]) {
merge(w[0],w[1] + n);
merge(w[1],w[0] + n);
if(find(w[1]) == find(w[0])) {
flag = 1;
break;
}
}
if(!flag) {
if(pl == pr) ans[pl] = 1;
else solve(ls,pl,mid),solve(rs,mid+1,pr);
}
while(top > now) {
array<int,3> T = st[top--];
s[T[0]] = T[0];
h[T[1]] = T[2];
}
}
signed main() {
n = rd(),m = rd(),k = rd();
for(int i = 1;i<=n * 2;i++) s[i] = i;
for(int i = 1;i<=m;i++) {
int x = rd(),y = rd(),l = rd(),r = rd();
insert(1,1,k,l + 1,r,array<int,2>{x,y});
}
solve(1,1,k);
for(int i = 1;i<=k;i++) puts(ans[i] ? "Yes":"No");
return 0;
}
2024/10/14#
内容要素:历史和线段树、线段树分治#
loj193 历史和线段树#
实现一个数据结构,可以实现:
- 区间加
- 查询区间历史和
这个历史和线段树,可以使用矩阵来帮助理解。
我们如何得到历史和呢?
我们可以构造一个向量:
\(his\) 用来记录历史和,\(sum\) 记录目前区间和,\(len\) 记录区间长度。
先考虑如何更新区间和(做区间加操作),
我们可以构造这样一个矩阵:
那么,
区间和还是比较简单的。
难点在于:我们要手动更新历史和 \(his\)。
我们考虑构造这样一个矩阵:
那么,
因为是区间更新历史和和区间加操作,所以要记得打 \(tag\)
!注意:因为矩阵乘法只有交换律(方阵也是一样),打 \(tag\) 的时候不要把顺序弄反了!
\(tag = \mathcal{newMatrix} \times tag\)
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
template<int N,int M,class T = long long>
struct matrix {
int m[N][M];
matrix(){memset(m,0,sizeof(m));}
void init(){for(int i = 0;i < N;i++) m[i][i] = 1;}
friend bool operator != (matrix<N,M> x,matrix<N,M> y) {
for(int i = 0;i<N;i++)
for(int j = 0;j<M;j++)
if(x[i][j] != y[i][j])
return true;
return false;
}
int* operator [] (const int pos) {return m[pos];}
void print(string s) {
cout<<'\n';
string t = "test for " + s + " matrix:";
cout<<t<<'\n';
for(int i = 0;i<N;i++)
for(int j = 0;j<M;j++)
cout<<m[i][j]<<" \n"[j == M - 1];
cout<<'\n';
}
};
template<int N,int M,int R,class T = long long>
matrix<N,R,T> operator * (matrix<N,M,T> a,matrix<M,R,T> b) {
matrix<N,R,T> c;
for(int i = 0;i<N;i++)
for(int j = 0;j<M;j++)
for(int k = 0;k<R;k++)
c[i][k] = c[i][k] + a[i][j] * b[j][k];
return c;
}
template<int N,int M,class T = long long>
matrix<N,M,T> operator + (matrix<N,M,T> a,matrix<N,M,T> b) {
for(int i = 0;i<N;i++)
for(int j = 0;j<M;j++)
a[i][j] += b[i][j];
return a;
}
const int N = 1e5+5;
int n,m,a[N];
namespace sgt{
matrix<3,1> h[N<<2];
matrix<3,3> tag[N<<2];
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
void push_up(int p) {
h[p] = h[ls] + h[rs];
}
void addtag(int p,matrix<3,3> c) {
h[p] = c * h[p] ;
tag[p] = c * tag[p];
}
void push_down(int p) {
matrix<3,3> c;
c.init();
if(tag[p] != c) {
addtag(ls,tag[p]);
addtag(rs,tag[p]);
tag[p] = c;
}
}
void build(int p,int pl,int pr) {
matrix<3,3> c;
c.init();
tag[p] = c;
if(pl == pr) {
h[p][0][0] = h[p][1][0] = a[pl];
h[p][2][0] = 1;
return;
}
build(ls,pl,mid);
build(rs,mid+1,pr);
push_up(p);
}
void update(int p,int pl,int pr,int l,int r,matrix<3,3> v) {
if(l <= pl && pr <= r) {addtag(p,v);return;}
push_down(p);
if(l <= mid) update(ls,pl,mid,l,r,v);
if(r > mid) update(rs,mid+1,pr,l,r,v);
push_up(p);
}
int query(int p,int pl,int pr,int l,int r) {
if(l <= pl && pr <= r) return h[p][0][0];
push_down(p);
int ans = 0;
if(l <= mid) ans += query(ls,pl,mid,l,r);
if(r > mid) ans += query(rs,mid+1,pr,l,r);
return ans;
}
}
signed main() {
n = rd(),m = rd();
for(int i = 1;i<=n;i++) a[i] = rd();
sgt::build(1,1,n);
auto upd = [&]() -> void {
int l = rd(),r = rd(),x = rd();
matrix<3,3> c;
c.init();
c[1][2] = x;
sgt::update(1,1,n,l,r,c);
};
auto qry = [&]() -> void {
int l = rd(),r = rd();
wt(sgt::query(1,1,n,l,r));
putchar('\n');
};
while(m--) {
int opt = rd();
switch(opt) {
case 1:
upd();
break;
case 2:
qry();
break;
default:
puts("Error");
exit(0);
break;
}
matrix<3,3> v;
v.init();
v[0][1] = 1;
sgt::update(1,1,n,1,n,v);
}
return 0;
}
到这里就结束了吗?
还不行!虽然说矩阵乘法也可以过此题,但往往比赛中会出现类似于 \(\text{NOIP2022 比赛}\) 卡矩阵做法的题目。
那么我们需要优化做法!然而这个做法的最严重的问题在于常数!
我们在维护矩阵的时候,有好多的位置是根本用不到的(往往是一直保持 \(0\) 或 \(1\))
我们可以另写一个程序来观察我们矩阵会变动的位置,然后只维护这些会变动的位置,来优化常数。
比如我写了个这样的程序:
#include<bits/stdc++.h>
using namespace std;
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
template<int N,int M,class T = long long>
struct matrix {
int m[N][M];
matrix(){memset(m,0,sizeof(m));}
void init(){for(int i = 0;i < N;i++) m[i][i] = 1;}
friend bool operator != (matrix<N,M> x,matrix<N,M> y) {
for(int i = 0;i<N;i++)
for(int j = 0;j<M;j++)
if(x[i][j] != y[i][j])
return true;
return false;
}
int* operator [] (const int pos) {return m[pos];}
void print(string s) {
cout<<'\n';
string t = "test for " + s + " matrix:";
cout<<t<<'\n';
for(int i = 0;i<N;i++)
for(int j = 0;j<M;j++)
cout<<m[i][j]<<" \n"[j == M - 1];
cout<<'\n';
}
};
template<int N,int M,int R,class T = long long>
matrix<N,R,T> operator * (matrix<N,M,T> a,matrix<M,R,T> b) {
matrix<N,R,T> c;
for(int i = 0;i<N;i++)
for(int j = 0;j<M;j++)
for(int k = 0;k<R;k++)
c[i][k] = c[i][k] + a[i][j] * b[j][k];
return c;
}
template<int N,int M,class T = long long>
matrix<N,M,T> operator + (matrix<N,M,T> a,matrix<N,M,T> b) {
for(int i = 0;i<N;i++)
for(int j = 0;j<M;j++)
a[i][j] += b[i][j];
return a;
}
template<int N,class T = long long>
matrix<N,N,T> qpow(matrix<N,N,T> x,int k) {
matrix<N,N,T> re;
re.init();
while(k) {
if(k & 1) re = re * x;
x = x * x;
k >>= 1;
}
return re;
}
matrix<3,3> re,b;
signed main() {
re.init();
while(1) {
int c = rd();
if(c == 0) return 0;
if(c == 1) {
b.init();
int x = rd();
b[1][2] = x;
re = b * re;
re.print("result:");
}else if(c == 2) {
b.init();
b[0][1] = 1;
re = b * re;
re.print("result:");
}
}
return 0;
}
这样来模拟 \(tag\) 矩阵中需要维护的位置。
最后,我们发现,我们需要维护的位置为:
这六个位置(其实是 \(2,3,5\) 这三个位置,但下面代码写的时候其实也只维护了三个位置)
那么我们就单独对这些位置进行计算。
我们考虑写一个 \(vet\) 和 \(matrix\) 结构体,只用计算 \(vet \times matrix\) 和 \(matrix \times matrix\) 这两种操作。
关于怎么写这两种操作,以矩阵乘矩阵为例:
有两个矩阵 \(A,B\)
这样这个题面的细节我们就都讲完了,本题,你已经可以成功 \(AC\) 了。
AC-code:
#include<bits/stdc++.h>
using namespace std;
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
struct tag{
int x[7];
void init() {
x[1] = x[4] = x[6] = 1;
x[2] = x[3] = x[5] = 0;
}
int& operator [](const int pos) {return x[pos];}
friend tag operator * (tag& A,tag& B) {
tag c;c.init();
c[2] = A[2] + B[2];
c[3] = B[3] + A[2] * B[5] + A[3];
c[5] = B[5] + A[5];
return c;
}
friend bool operator != (tag A,tag B) {
for(int i = 0;i<7;i++)
if(A[i] != B[i])
return true;
return false;
}
void print(string s) {
cout<<"test for "<<s<<" matrix\n";
cout<<x[1]<<' '<<x[2]<<' '<<x[3]<<'\n';
cout<<0<<' '<<x[4]<<' '<<x[5]<<'\n';
cout<<0<<' '<<0<<' '<<x[6]<<'\n';
}
};
struct vet{
int y[4];
void init() {y[1] = y[2] = y[3] = 0;}
int& operator [](const int pos) {return y[pos];}
friend vet operator + (vet a,vet b) {
vet c;c.init();
c[1] = a[1] + b[1];
c[2] = a[2] + b[2];
c[3] = a[3] + b[3];
return c;
}
void print(string s) {
cout<<'\n';
cout<<"test for "<<s<<" vector\n";
cout<<y[1]<<'\n';
cout<<y[2]<<'\n';
cout<<y[3]<<'\n';
cout<<'\n';
}
};
vet operator * (tag A,vet B) {
vet c;c.init();
c[1] = B[1] + B[2] * A[2] +B[3] * A[3];
c[2] = B[2] + A[5] * B[3];
c[3] = B[3];
return c;
}
const int N = 1e5+5;
int n,m,a[N];
namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
tag T[N<<2];
vet t[N<<2];
void push_up(int p) {
t[p] = t[ls] + t[rs];
}
void addtag(int p,tag x) {
T[p] = x * T[p];
t[p] = x * t[p];
}
void push_down(int p) {
tag c;c.init();
if(T[p] != c) {
addtag(ls,T[p]);
addtag(rs,T[p]);
T[p].init();
}
}
void build(int p,int pl,int pr) {
T[p].init();
if(pl == pr) {
t[p][2] = t[p][1] = a[pl];
t[p][3] = 1;
return;
}
build(ls,pl,mid);
build(rs,mid+1,pr);
push_up(p);
}
void update(int p,int pl,int pr,int l,int r,tag x) {
if(l <= pl && pr <= r) {
addtag(p,x);
// t[p].print("upd");
// T[p].print("upd");
return;
}
push_down(p);
if(l <= mid) update(ls,pl,mid,l,r,x);
if(r > mid) update(rs,mid+1,pr,l,r,x);
push_up(p);
}
int query(int p,int pl,int pr,int l,int r) {
if(l <= pl && pr <= r) return t[p][1];
push_down(p);
if(r <= mid) return query(ls,pl,mid,l,r);
else if(l > mid) return query(rs,mid+1,pr,l,r);
else return query(ls,pl,mid,l,r) + query(rs,mid+1,pr,l,r);
}
}
signed main() {
n = rd(),m = rd();
for(int i = 1;i<=n;i++) a[i] = rd();
sgt::build(1,1,n);
while(m--) {
int opt = rd();
if(opt == 1) {
int l = rd(),r = rd(),x = rd();
tag c;c.init();
c[5] = x;
sgt::update(1,1,n,l,r,c);
}else {
int l = rd(),r = rd();
wt(sgt::query(1,1,n,l,r));
putchar('\n');
}
tag c;c.init();
c[2] = 1;
sgt::update(1,1,n,1,n,c);
}
return 0;
}
P2056 [ZJOI2007] 捉迷藏#
题面:
题目描述
Jiajia 和 Wind 是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind 和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由 \(N\) 个屋子和 \(N-1\) 条双向走廊组成,这 \(N-1\) 条走廊的分布使得任意两个屋子都互相可达。
游戏是这样进行的,孩子们负责躲藏,Jiajia 负责找,而 Wind 负责操纵这 \(N\) 个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia 希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。
我们将以如下形式定义每一种操作:
- C(hange) i 改变第 \(i\) 个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。
- G(ame) 开始一次游戏,查询最远的两个关灯房间的距离。
输入格式
第一行包含一个整数 \(N\),表示房间的个数,房间将被编号为 \(1,2,3…N\) 的整数。
接下来 \(N-1\) 行每行两个整数 \(a\), \(b\),表示房间 \(a\) 与房间 \(b\) 之间有一条走廊相连。
接下来一行包含一个整数 \(Q\),表示操作次数。接着 \(Q\) 行,每行一个操作,如上文所示。
输出格式
对于每一个操作 Game,输出一个非负整数,表示最远的两个关灯房间的距离。若只有一个房间是关着灯的,输出
0
;若所有房间的灯都开着,输出-1
。样例 #1
样例输入 #1
8 1 2 2 3 3 4 3 5 3 6 6 7 6 8 7 G C 1 G C 2 G C 1 G
样例输出 #1
4 3 3 4
提示
对于\(20\%\)的数据, \(N \leq 50\), \(M\leq 100\);
对于\(60\%\)的数据, \(N \leq 3000\), \(M \leq 10000\);
对于\(100\%\)的数据, \(N \leq 100000\), \(M \leq 500000\)。
出现 & 消失!
太棒了,线段树分治!
我们建立时间线段树,将每个点的出现到消失时间插入线段树里面。
然后是结论时间!
结论:当树中新加入一个节点 \(x\),原先的 \(u \leadsto v\) 直径,只会变成 \(u \leadsto x\) 或 \(x \leadsto v\)
证明:若插入一个 \(x\),如果原直径是 \(u\leadsto v\),而新直径是 \(x\leadsto y\),则 \(u \leadsto y\) 或 \(v\leadsto y\) 肯定比 \(u\leadsto v\) 更长,故矛盾!
那么我们要在栈中记录 \(u,v\),然后通过上面的结论,更新 \(u,v\),退出线段树节点时撤回退栈。
然后对每个时间点,记录答案。
求两点距离,考虑树剖 + 树上差分。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 5e5+5;
int head[N],nxt[N<<1],to[N<<1],cnt;
void init() {memset(head,-1,sizeof(head));cnt = 0;}
void add(int u,int v) {
nxt[cnt] = head[u];
to[cnt] = v;
head[u] = cnt++;
}
int lst[N],n,q;
int fa[N],siz[N],top[N],son[N],dep[N];
void dfs1(int x,int f) {
fa[x] = f;
siz[x] = 1;
dep[x] = dep[f] + 1;
for(int i = head[x];~i;i = nxt[i]) {
int y = to[i];
if(y ^ f) {
dfs1(y,x);
siz[x] += siz[y];
if(siz[son[x]] < siz[y]) son[x] = y;
}
}
}
void dfs2(int x,int topx) {
top[x] = topx;
if(!son[x]) return;
dfs2(son[x],topx);
for(int i = head[x];~i;i = nxt[i]) {
int y = to[i];
if(y ^ fa[x] && y ^ son[x])
dfs2(y,y);
}
}
int lca(int x,int y) {
while(top[x] ^ top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x,y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
bitset<N> col,qb;
vector<int> t[N<<2];
stack<pii> st;
int u,v,ans[N];
int dis(int x,int y) {
return dep[x] + dep[y] - 2 * dep[lca(x,y)];
}
#define mid ((pl + pr) >> 1)
#define ls (p << 1)
#define rs (ls | 1)
void insert(int p,int pl,int pr,int l,int r,int x) {
if(l <= pl && pr <= r) {t[p].emplace_back(x);return;}
if(l <= mid) insert(ls,pl,mid,l,r,x);
if(r > mid) insert(rs,mid+1,pr,l,r,x);
}
void solve(int p,int pl,int pr) {
auto now = st.size();
for(int x : t[p]) {
st.push({u,v});
if(!u && !v) u = v = x;
else {
int d0 = dis(u,v),d1 = dis(u,x),d2 = dis(v,x);
int d = max(d0,max(d1,d2));
if(d == d1) v = x;
else if(d == d2) u = x;
}
}
if(pl == pr) ans[pl] = (!u || !v) ? -1 : dis(u,v);
else solve(ls,pl,mid),solve(rs,mid+1,pr);
while(st.size() != now) {
auto t = st.top();
u = t.first,v = t.second;
st.pop();
}
}
signed main() {
init();
n = rd();
for(int i = 1;i<n;i++) {
int u = rd(),v = rd();
add(u,v);add(v,u);
}
q = rd();
dfs1(1,0);dfs2(1,1);
for(int i = 1;i<=n;i++) lst[i] = 1;
for(int i = 1;i<=q;i++) {
char op = getchar();
while(op != 'C' && op != 'G') op = getchar();
if(op == 'C') {
int s = rd();
if(!col[s]) {
col[s] = 1;
insert(1,1,q,lst[s],i,s);
}else col[s] = 0,lst[s] = i;
}else qb[i] = 1;
}
for(int i = 1;i<=n;i++)
if(!col[i])
insert(1,1,q,lst[i],q,i);
solve(1,1,q);
for(int i = 1;i<=q;i++)
if(qb[i])
wt(ans[i]),putchar('\n');
return 0;
}
2024/10/19#
校内模拟赛(最惨的一集)#
Highest : \(60\)
我 : \(20\)
没有大样例,评价是唐完了。
P8945 Inferno#
题面:
题目背景
我是幽灵。
穿过悲惨之城,我落荒而逃。
穿过永世凄苦,我远走高飞。沿着阿尔诺河的堤岸,我夺路狂奔,气喘吁吁……左转上了卡斯特拉尼大街,一直朝北而行,始终隐蔽在乌菲兹美术馆的阴影之下。
但他们还是穷追不舍。
他们的脚步声越来越响,这些追捕者冷酷无情,不达目的绝不善罢甘休。
这么多年来,他们一直尾随着我。他们锲而不舍,是的我只能活在地下……被迫呆在炼狱之中……就像冥府的恶魔,时刻忍受地狱的煎熬。
我是幽灵。
如今浮生尘世,我举目北望,却看不到通往救赎的捷径——那高耸的亚平宁山脉挡住了黎明的第一缕阳光。
题目描述
罗伯特 · 兰登在洗下但丁死亡面具上的丙烯石膏后,在背面发现了一行字:
哦,有着稳固智慧的人啊,
请注意这里的含义
就藏在晦涩的序列面纱之下。下面有一行由 \(1,-1\) 组成的长度为 \(n\) 的序列。面具经受了岁月的侵蚀,序列中有一些数已经模糊不清。幸运的是,面具下面有给出两条线索:
你只得往空缺的位置填 \(k\) 个 \(1\),其余填入 \(-1\),需要最大化这个序列的最大子段和。
一个序列的最大子段和定义为,其在一段连续长度的区间内的最大和。形式化地,一个序列 \(a\) 的最大子段和即为 \(\max\limits_{l=1}^n\max\limits_{r=l}^n\left(\sum\limits_{i=l}^r a_i\right)\)。
罗伯特 · 兰登希望在瘟疫扩散之前找到有关的线索。于是他找到了你。
【形式化题意】
给定一个只包含 \(-1,0,1\) 的序列,求出往 \(0\) 的位置上填 \(k\) 个 \(1\),其余填 \(-1\) 后最大子段和的最大值。
输入格式
第一行两个正整数 \(n,k\)。
接下来一行 \(n\) 个整数 \(a_i\in\{-1,0,1\}\),其中 \(0\) 表示数字模糊不清。
输出格式
一行一个正整数,表示可能的最大子段和。
样例 #1
样例输入 #1
5 2 1 0 -1 0 0
样例输出 #1
2
提示
【样例解释】
一种可行的方案是填入 \(\{1,1,-1\}\),最大子段和为 \(2\)。
【数据范围】
本题开启捆绑测试。
\(\text{SubTask}\) 分值 $n,k\le $ \(0\) \(4\) \(20\) \(1\) \(6\) \(200\) \(2\) \(10\) \(5\times 10^3\) \(3\) \(30\) \(5\times 10^5\) \(4\) \(50\) \(10^7\) 对于 \(100\%\) 的数据,\(1\le n,k\le10^7\),\(a_i\in \{-1,0,1\}\)。保证 \(k\le\) 序列中 \(0\) 的个数。
本题标程使用优化后的输入输出,在 O2 优化下最大点用时约 \(350\) ms,足以通过此题。如果您自认为您的程序复杂度正确,却超出时间限制,请使用更优的输入输出方式,或者优化常数。
这道题赛时已经想出来了,但是没时间写,细节有点多,自己的对拍程序还写错了。
我们很容易可以写出 \(O(n ^ 2)\) 暴力(计算每个可能区间 \(0\) 的个数,区间和,然后求区间的 \(0\) 尽量填满的值的最大值)
但是,我们观察到数据范围来到了 \(1e7\),也就是说这道题只能用 \(O(n)\) 做法过。
不妨考虑固定一个点 \(i\),快速定位最优策略点 \(j\) 的位置。
我们回顾暴力做法,我们要知道区间 \(0\) 的个数 \(cnt\),区间和 \(s\)。
容易想到前缀和。
我们这时就要想到看式子,拆贡献。
我们可以得到,对于以 \(i\) 结尾的最大字段和最大值 \(f_i\),有:
考虑拆掉绝对值讨论:
其中 \(cnt_j\) 还具有单调性,自然可以想到用单调队列维护。
考虑维护 \(q1,q2\),其中 \(q1\) 维护 \((-cnt_j - s_j)\) 的最大值单调队列,\(q2\) 维护 \(\Delta cnt \leq k\) 的被 \(q1\) 弹出的值。
因为 \(cnt\) 是单调的,所以 \(q2\) 会以下标大小依次弹出,所以 \(q2\) 需要维护 \(q1\) 弹出的值的下标单调性。
那些 \(\Delta cnt > k\) 的下标直接计算 \(\max(cnt_j - s_j) \rightarrow mx\)
记得维护 \(q1.front()\) 的 \(\Delta cnt\) 是否仍小于等于 \(k\)。
那么,关于 \(i\) 结尾的答案就是
实现时多注意就好了。
AC-code:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e7+5;
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
int a[N],n,k,cnt[N],s[N],mx;
deque<int> q1,q2;
stack<int> q;
const int inf = 1e9;
signed main() {
// freopen("low.txt","r",stdin);
n = rd(),k = rd();
mx = -inf;
for(int i = 1;i<=n;i++) a[i] = rd();
for(int i = 1;i<=n;i++) s[i] = s[i-1] + a[i];
for(int i = 1;i<=n;i++) cnt[i] = cnt[i-1] + (a[i] == 0);
int ans = -inf;
q1.emplace_front(0);//q1 -cnt[j]-s[j] -> q2 outnum -> cnt[j] - s[j]
for(int i = 1;i<=n;i++) {
while(!q1.empty() && (cnt[i] - cnt[q1.front()]) > k) mx = max(mx,cnt[q1.front()] - s[q1.front()]),q1.pop_front();
while(!q1.empty() && (-cnt[q1.back()] - s[q1.back()]) < (-cnt[i] - s[i])) {
q.emplace(q1.back());
q1.pop_back();
}
while(!q.empty()) q2.emplace_back(q.top()),q.pop();
while(!q2.empty() && cnt[i] - cnt[q2.front()] > k) mx = max(mx,cnt[q2.front()] - s[q2.front()]),q2.pop_front();
ans = max(ans,max(s[i] + cnt[i] - cnt[q1.front()] - s[q1.front()],s[i] + (k << 1) - cnt[i] + mx));
q1.emplace_back(i);
}
wt(ans);
return 0;
}
2024/10/24#
1024
P5304 [GXOI/GZOI2019] 旅行者#
题面:
题目描述
J 国有 \(n\) 座城市,这些城市之间通过 \(m\) 条单向道路相连,已知每条道路的长度。
一次,居住在 J 国的 Rainbow 邀请 Vani 来作客。不过,作为一名资深的旅行者,Vani 只对 J 国的 \(k\) 座历史悠久、自然风景独特的城市感兴趣。
为了提升旅行的体验,Vani 想要知道他感兴趣的城市之间「两两最短路」的最小值(即在他感兴趣的城市中,最近的一对的最短距离)。也许下面的剧情你已经猜到了—— Vani 这几天还要忙着去其他地方游山玩水,就请你帮他解决这个问题吧。
输入格式
每个测试点包含多组数据,第一行是一个整数 \(T\),表示数据组数。注意各组数据之间是互相独立的。
对于每组数据,第一行包含三个正整数 \(n,m,k\),表示 J 国的 \(n\) 座城市(从 \(1 \sim n\) 编号),\(m\) 条道路,Vani 感兴趣的城市的个数 \(k\)。
接下来 \(m\) 行,每行包括 \(3\) 个正整数 \(x,y,z\),表示从第 \(x\) 号城市到第 \(y\) 号城市有一条长度为 \(z\) 的单向道路。注意 \(x,y\) 可能相等,一对 \(x,y\) 也可能重复出现。
接下来一行包括 \(k\) 个正整数,表示 Vani 感兴趣的城市的编号。
输出格式
输出文件应包含 \(T\) 行,对于每组数据,输出一个整数表示 \(k\) 座城市之间两两最短路的最小值。
样例 #1
样例输入 #1
2 6 7 3 1 5 3 2 3 5 1 4 3 5 3 2 4 6 5 4 3 7 5 6 4 1 3 6 7 7 4 5 3 10 6 2 7 1 2 6 5 4 2 4 3 4 1 7 3 7 2 4 1 2 5 3
样例输出 #1
5 6
提示
样例解释
对于第一组数据,\(1\) 到 \(3\) 最短路为 \(5\);\(1\) 到 \(6\) 最短路为 \(7\);\(3,6\) 无法到达,所以最近的两点为 \(1,3\),最近的距离为 \(5\)。
对于第二组数据,\(1\) 到 \(2\) 最短路为 \(6\);\(5\) 到 \(3\) 最短路为 \(6\);其余的点均无法互相达,所以最近的两点为 \(1,2\) 和 \(5,3\),最近的距离为 \(6\)。
数据范围
\(2 \le k \le n\),\(1 \le x,y \le n\),\(1 \le z \le 2 \times 10^9\),\(T \leq 5\)。
测试点编号 \(n\) 的规模 \(m\) 的规模 约定 \(1\) \(\le 1,000\) \(\le 5,000\) 无 \(2\) \(\le 1,000\) \(\le 5,000\) 无 \(3\) \(\le 100,000\) \(\le 500,000\) 保证数据为有向无环图 \(4\) \(\le 100,000\) \(\le 500,000\) 保证数据为有向无环图 \(5\) \(\le 100,000\) \(\le 500,000\) 保证数据为有向无环图 \(6\) \(\le 100,000\) \(\le 500,000\) 无 \(7\) \(\le 100,000\) \(\le 500,000\) 无 \(8\) \(\le 100,000\) \(\le 500,000\) 无 \(9\) \(\le 100,000\) \(\le 500,000\) 无 \(10\) \(\le 100,000\) \(\le 500,000\) 无
感兴趣的地点太多了,直接跑的话,复杂度是 \(\mathcal{O}(n^3\log n)\) 的。
这个时候,二进制分组就是一个非常厉害的优化。
我们考虑将感兴趣的地点按数位分成两类,
那么,我们在枚举数位的时候,将该位为 \(0\) 的地点放在 \(A\) 中,反之放在 \(B\) 中。
我们从 \(A\) 往 \(B\) 跑一个最短路,记录一下答案。
直到枚举到 \(18\) 位,后面便超出了数据范围。
在这中取最小值,便是答案。
因为二进制分组的关系,我们一定会在某一次分组中将一对数放在 \(A\),\(B\) 中,
所以答案一定能枚举到。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int M = 500050,N = 100050;
int n,m,k;
int head[N],to[M << 1],nxt[M<<1],val[M<<1],cnt,h2[N];
void init() {memset(head,-1,sizeof(head));cnt = 0;}
void add(int u,int v,int w) {
nxt[cnt] = head[u];
to[cnt] = v;
val[cnt] = w;
head[u] = cnt++;
}
const int inf = 0x3f3f3f3f3f3f3f3fLL;
int dis[N],vis[N];
void dij(int s) {
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
priority_queue<array<int,2>> q;
q.emplace(array<int,2>{0,s});
dis[s] = 0;
while(!q.empty()) {
int x = q.top()[1];
q.pop();
if(vis[x]) continue;
vis[x] = true;
for(int i = head[x];~i;i = nxt[i]) {
int y = to[i],w = val[i];
if(dis[y] > dis[x] + w) {
dis[y] = dis[x] + w;
q.emplace(array<int,2>{-dis[y],y});
}
}
}
}
int o[N];
void solve() {
n = rd(),m = rd(),k = rd();
init();
for(int i = 1;i<=m;i++) {
int u = rd(),v = rd(),w = rd();
add(u,v,w);
}
for(int i = 1;i<=k;i++) o[i] = rd();
int ans = inf;
memcpy(h2,head,sizeof(h2));
for(int i = 0;i<=17;i++) {
int now = cnt;
for(int j = 1;j<=k;j++) {
if(o[j] & (1<<i)) add(0,o[j],0);
else add(o[j],n + 1,0);
}
dij(0);
ans = min(dis[n + 1],ans);
memcpy(head,h2,sizeof(head));
cnt = now;
for(int j = 1;j<=k;j++) {
if(o[j] & (1<<i)) add(o[j],0,0);
else add(n + 1,o[j],0);
}
dij(n + 1);
ans = min(ans,dis[0]);
memcpy(head,h2,sizeof(head));
cnt = now;
}
wt(ans),putchar('\n');
}
signed main() {
int T = rd();
while(T--) solve();
return 0;
}
[ARC159D] LIS 2#
题面:
题面翻译
给定 \(n\) 个操作,每次操作给出 \(l,r\),并在 \(a\) 序列里依次添加 \(i\in[l,r]\)。
求最后 \(a\) 的最长上升子序列。
题目描述
数列 $ X $ があります。初め、$ X $ は空です。
高橋君は $ i=1,2,\ldots,N $ の順に次の操作をしました。
- $ X $ の末尾に $ l_i,l_i+1,\ldots,r_i $ をこの順番で追加する。
操作後の $ X $ の狭義単調増加部分列の長さの最大値を求めてください。
输入格式
入力は以下の形式で標準入力から与えられる。
$ N $ $ l_1 $ $ r_1 $ $ \vdots $ $ l_{N} $ $ r_{N} $
输出格式
答えを出力せよ。
样例 #1
样例输入 #1
4 1 1 2 4 10 11 7 10
样例输出 #1
8
样例 #2
样例输入 #2
4 1 1 1 1 1 1 1 1
样例输出 #2
1
样例 #3
样例输入 #3
1 1 1000000000
样例输出 #3
1000000000
提示
制約
- $ 1\ \leq\ N\ \leq\ 2\ \times\ 10^5 $
- $ 1\ \leq\ l_i\ \leq\ r_i\ \leq\ 10^9 $
- 入力はすべて整数
Sample Explanation 1
操作後の $ X $ は $ (1,2,3,4,10,11,7,8,9,10) $ です。 この数列の $ 1,2,3,4,7,8,9,10 $ 項目からなる部分列は狭義単調増加であり、かつこれが長>さが最大のものです。
Sample Explanation 2
操作後の $ X $ は $ (1,1,1,1) $ です。
线段树优化 \(dp\) 入门。
我们来具象化这个求得最长上升子序列的过程:
首先,我们都会基础最长上升子序列的 \(dp\): \(f_i = \max\limits_{j = 1}^{i - 1}{f_{j - 1}} + 1\)
换成一段一段的区间就长成这样:
很好发现:\(f_{i} = \max\limits_{r_j < r_i}{f_j + r_i - r_j}\)
我们可以把答案存在区间右端点上,然后用动态开点线段树求前缀最大值,就可以转移了。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 2e5+5;
const int inf = 0x3f3f3f3f3f3f3f3fLL;
int n,f[N],rt[2];
struct sgt{
#define mid ((pl + pr) >> 1)
int mx[N * 50],cnt,ls[N * 50],rs[N * 50];
sgt(){memset(mx,0xcf,sizeof(mx));}
void push_up(int p) {
mx[p] = max(mx[ls[p]],mx[rs[p]]);
}
void update(int &p,int pl,int pr,int k,int d) {
if(!p) p = ++cnt;
if(pl == pr) {mx[p] = max(mx[p],d);return;}
if(k <= mid) update(ls[p],pl,mid,k,d);
else update(rs[p],mid+1,pr,k,d);
push_up(p);
}
int query(int p,int pl,int pr,int l,int r) {
if(!p) return -inf;
if(l <= pl && pr <= r) return mx[p];
if(r <= mid) return query(ls[p],pl,mid,l,r);
else if(l > mid) return query(rs[p],mid+1,pr,l,r);
else return max(query(ls[p],pl,mid,l,r),query(rs[p],mid+1,pr,l,r));
}
};
sgt T[2];
signed main() {
n = rd();int ans = 0;
T[0].update(rt[0],0,1e9,0,0);
T[1].update(rt[1],0,1e9,0,0);
for(int i = 1;i<=n;i++) {
int l = rd(),r = rd();
f[i] = max(T[0].query(rt[0],0,1e9,l,r) + r,T[1].query(rt[1],0,1e9,0,l - 1) + r - l + 1);
T[0].update(rt[0],0,1e9,r,f[i] - r);
T[1].update(rt[1],0,1e9,r,f[i]);
ans = max(ans,f[i]);
}
wt(ans);
return 0;
}
P4735 最大异或和#
题面:
题目描述
给定一个非负整数序列 \(\{a\}\),初始长度为 \(N\)。
有 \(M\) 个操作,有以下两种操作类型:
A x
:添加操作,表示在序列末尾添加一个数 \(x\),序列的长度 \(N\) 加 \(1\)。Q l r x
:询问操作,你需要找到一个位置 \(p\),满足 \(l \le p \le r\),使得:\(a[p] \oplus a[p+1] \oplus ... \oplus a[N] \oplus x\) 最大,输出最大值。输入格式
第一行包含两个整数 \(N, M\),含义如问题描述所示。
第二行包含 \(N\) 个非负整数,表示初始的序列 \(A\)。
接下来 \(M\) 行,每行描述一个操作,格式如题面所述。输出格式
假设询问操作有 \(T\) 个,则输出应该有 \(T\) 行,每行一个整数表示询问的答案。
样例 #1
样例输入 #1
5 5 2 6 4 3 6 A 1 Q 3 5 4 A 4 Q 5 7 0 Q 3 6 6
样例输出 #1
4 5 6
提示
- 对于所有测试点,\(1\le N,M \le 3\times 10 ^ 5\),\(0\leq a_i\leq 10 ^ 7\)。
首先,动态加数?平衡 trie?
显然不可做。
我们得考虑异或的性质——自反性!
转换一下题面: \(a[p] \oplus a[p+1] \oplus \ldots \oplus a[N] = (\oplus_{i = 1}^N a[i]) \oplus (\oplus_{i = 1}^{p - 1} a[i])\)
\((\oplus_{i = 1}^N a[i])\),我们用一个全局tag就可以搞定。
\((\oplus_{i = 1}^{p - 1} a[i])\),我们用 trie 来做定位。
其中 \(l \leq p \leq r\),我们用 可持久化01-trie + 01-trie上二分 就可以解决!
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 3e5+5;
int rt[N << 2],n,m,a[N<<2];
namespace zo_trie{
int siz[N * 50],ls[N * 50],rs[N * 50],cnt;
int update(int pr,int x,int dep) {
int re = ++cnt;
siz[re] = siz[pr] + 1;
ls[re] = ls[pr];rs[re] = rs[pr];
if(dep < 0) return re;
((x >> dep) & 1) ? rs[re] = update(rs[pr],x,dep - 1) : ls[re] = update(ls[pr],x,dep - 1);
return re;
}
int query(int x,int y,int k,int dep){
if(dep < 0) return 0;
int lsz = siz[ls[y]] - siz[ls[x]];
int rsz = siz[rs[y]] - siz[rs[x]];
if((k >> dep) & 1) {
if(lsz)
return (int)(1<<(int)dep) + query(ls[x],ls[y],k,dep - 1);
else
return query(rs[x],rs[y],k,dep - 1);
}else {
if(rsz)
return (int)(1<<(int)dep) + query(rs[x],rs[y],k,dep - 1);
else
return query(ls[x],ls[y],k,dep - 1);
}
}
}
signed main() {
n = rd(),m = rd();
for(int i = 1;i<=n;i++) a[i] = rd();
for(int i = 1;i<=n;i++) a[i] ^= a[i - 1];
rt[0] = zo_trie::update(rt[0],0,25);
for(int i = 1;i<=n;i++) rt[i] = zo_trie::update(rt[i - 1],a[i],25);
while(m--) {
char opt = getchar();
while(opt != 'A' && opt != 'Q') opt = getchar();
int A,B,C;
switch(opt) {
case 'A':
A = rd();
n++;
a[n] = a[n - 1] ^ A;
rt[n] = zo_trie::update(rt[n - 1],a[n],25);
break;
case 'Q':
A = rd(),B = rd(),C = rd();
A--,B--;
C ^= a[n];
if(A > 0) wt(zo_trie::query(rt[A - 1],rt[B],C,25));
else wt(zo_trie::query(0,rt[B],C,25));
putchar('\n');
break;
default:
puts("Error");
exit(0);
break;
}
}
return 0;
}
P8819 [CSP-S 2022] 星战#
题面:
题目描述
在这一轮的星际战争中,我方在宇宙中建立了 \(n\) 个据点,以 \(m\) 个单向虫洞连接。我们把终点为据点 \(u\) 的所有虫洞归为据点 \(u\) 的虫洞。
战火纷飞之中这些虫洞很难长久存在,敌人的打击随时可能到来。这些打击中的有效打击可以分为两类:
- 敌人会摧毁某个虫洞,这会使它连接的两个据点无法再通过这个虫洞直接到达,但这样的打击无法摧毁它连接的两个据点。
- 敌人会摧毁某个据点,由于虫洞的主要技术集中在出口处,这会导致该据点的所有还未被摧毁的虫洞被一同摧毁。而从这个据点出发的虫洞则不会摧毁。
注意:摧毁只会导致虫洞不可用,而不会消除它的存在。
为了抗击敌人并维护各部队和各据点之间的联系,我方发展出了两种特种部队负责修复虫洞:
- A 型特种部队则可以将某个特定的虫洞修复。
- B 型特种部队可以将某据点的所有损坏的虫洞修复。
考虑到敌人打击的特点,我方并未在据点上储备过多的战略物资。因此只要这个据点的某一条虫洞被修复,处于可用状态,那么这个据点也是可用的。
我方掌握了一种苛刻的空间特性,利用这一特性我方战舰可以沿着虫洞瞬移到敌方阵营,实现精确打击。
为了把握发动反攻的最佳时机,指挥部必须关注战场上的所有变化,为了寻找一个能够进行反攻的时刻。总指挥认为:
- 如果从我方的任何据点出发,在选择了合适的路线的前提下,可以进行无限次的虫洞穿梭(可以多次经过同一据点或同一虫洞),那么这个据点就可以实现反击。
- 为了使虫洞穿梭的过程连续,尽量减少战舰在据点切换虫洞时的质能损耗,当且仅当只有一个从该据点出发的虫洞可用时,这个据点可以实现连续穿梭。
- 如果我方所有据点都可以实现反击,也都可以实现连续穿梭,那么这个时刻就是一个绝佳的反攻时刻。
总司令为你下达命令,要求你根据战场上实时反馈的信息,迅速告诉他当前的时刻是否能够进行一次反攻。
输入格式
输入的第一行包含两个正整数 \(n,m\)。
接下来 \(m\) 行每行两个数 \(u,v\),表示一个从据点 \(u\) 出发到据点 \(v\) 的虫洞。保证 \(u \ne v\),保证不会有两条相同的虫洞。初始时所有的虫洞和据点都是完好的。
接下来一行一个正整数 \(q\) 表示询问个数。
接下来 \(q\) 行每行表示一次询问或操作。首先读入一个正整数 \(t\) 表示指令类型:
- 若 \(t = 1\),接下来两个整数 \(u, v\) 表示敌人摧毁了从据点 \(u\) 出发到据点 \(v\) 的虫洞。保证该虫洞存在且未被摧毁。
- 若 \(t = 2\),接下来一个整数 \(u\) 表示敌人摧毁了据点 \(u\)。如果该据点的虫洞已全部被摧毁,那么这次袭击不会有任何效果。
- 若 \(t = 3\),接下来两个整数 \(u, v\) 表示我方修复了从据点 \(u\) 出发到据点 \(v\) 的虫洞。保证该虫洞存在且被摧毁。
- 若 \(t = 4\),接下来一个整数 \(u\) 表示我方修复了据点 \(u\)。如果该据点没有被摧毁的虫洞,那么这次修复不会有任何效果。
在每次指令执行之后,你需要判断能否进行一次反攻。如果能则输出
YES
否则输出NO
。输出格式
输出一共 \(q\) 行。对于每个指令,输出这个指令执行后能否进行反攻。
样例 #1
样例输入 #1
3 6 2 3 2 1 1 2 1 3 3 1 3 2 11 1 3 2 1 2 3 1 1 3 1 1 2 3 1 3 3 3 2 2 3 1 3 1 3 1 3 4 2 1 3 2
样例输出 #1
NO NO YES NO YES NO NO NO YES NO NO
提示
【样例解释 #1】
虫洞状态可以参考下面的图片, 图中的边表示存在且未被摧毁的虫洞:
【样例 #2】
见附件中的
galaxy/galaxy2.in
与galaxy/galaxy2.ans
。【样例 #3】
见附件中的
galaxy/galaxy3.in
与galaxy/galaxy3.ans
。【样例 #4】
见附件中的
galaxy/galaxy4.in
与galaxy/galaxy4.ans
。【数据范围】
对于所有数据保证:\(1 \le n \le 5 \times {10}^5\),\(1 \le m \le 5 \times {10}^5\),\(1 \le q \le 5 \times {10}^5\)。
测试点 \(n \le\) \(m \le\) \(q \le\) 特殊限制 \(1 \sim 3\) \(10\) \(20\) \(50\) 无 \(4 \sim 8\) \({10}^3\) \({10}^4\) \({10}^3\) 无 \(9 \sim 10\) \(5 \times {10}^5\) \(5 \times {10}^5\) \(5 \times {10}^5\) 保证没有 \(t = 2\) 和 \(t = 4\) 的情况 \(11 \sim 12\) \(5 \times {10}^5\) \(5 \times {10}^5\) \(5 \times {10}^5\) 保证没有 \(t = 4\) 的情况 \(13 \sim 16\) \({10}^5\) \(5 \times {10}^5\) \(5 \times {10}^5\) 无 \(17 \sim 20\) \(5 \times {10}^5\) \(5\times 10^5\) \(5 \times {10}^5\) 无
连续穿梭~~~每个点只能有一条出边。
简化题意就是:给定一个图,支持 删边 ~ 删点 ~ 加(被删的)边 ~ 加(被删的)点 操作,询问是否可以构成一个内向基环树。
一看就很不好做,但是询问只有 \(YES\) 和 \(NO\)。
这个时候,非确定性算法就很可能隐藏其中。
我们知道一个图是内向基环树的必要条件是 \(\deg_{\text{out}} = n\)
那么,非确定性算法就是用必要条件来保证充分性!
本题就是一个重要非确定性算法——和哈希(sumhash)
我们求 \(\sum\limits_{i}\deg_{i} = n\),但这样很容易出问题,很多情况可以 \(\sum\limits_{i}\deg_{i} = n\)。
那么,我们就不要让每个点的出度为 \(1\) 了,我们把出度随机变宽。
也就是我们将每个点的出度随机赋值,一个图是内向基环树的必要条件就是 \(\sum \deg_{out} = \sum w_i\)
然鹅,出度不易维护,所以我们换成入度!入度和出度在内向基环树是显然等价的
那么,我们就只用判断这个 \(\sum \deg_{in} = \sum w_i\) 条件就可以了。
我们用三个数组维护操作:
\(r_i\) 表示 \(i\) 当前入度,\(g_i\) 表示 \(i\) 原始入度,有:
-
\((u,v)\) 失活,\(r_v \leftarrow r_u - 1\)
-
\(v\) 失活,\(r_v \leftarrow 0\)
-
\((u,v)\) 复活,\(r_v \leftarrow r_u + 1\)
-
\(v\) 复活, \(r_v \leftarrow g_v\)
只要实现上面的操作,就可以通过此题。
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 5e5+5;
int n,m,g[N],r[N],w[N];
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
template<typename T>
T rnr(T l, T r){
return l+(unsigned long long)rng()%(r-l+1);
}
signed main() {
n = rd(),m = rd();
for(int i = 1;i<=n;i++) w[i] = rnr((int)1,(int)(1e10));
int tar = accumulate(w + 1,w + n + 1,0LL);
int now = 0;
for(int i = 1;i<=m;i++) {
int u = rd(),v = rd();
r[v] += w[u];
g[v] = r[v];
now += w[u];
}
int q = rd();
while(q--) {
int op = rd(),u,v;
switch(op) {
case 1:
u = rd(),v = rd();
r[v] -= w[u];
now -= w[u];
break;
case 2:
v = rd();
now -= r[v];
r[v] = 0;
break;
case 3:
u = rd(),v = rd();
r[v] += w[u];
now += w[u];
break;
case 4:
v = rd();
now += g[v] - r[v];
r[v] = g[v];
break;
default:
puts("Error");
exit(0);
break;
}
puts((now == tar) ? "YES" : "NO");
}
return 0;
}
2024/10/30#
dp 专题总结#
Coins#
设 \(f_{i,j}\) 为 考虑前 \(i\) 个硬币,正面向上的个数有 \(j\) 个 的概率。
有转移:
然后将 \(\sum_{i = n / 2 + 1}^n f_{n,i}\) 输出即可。
Sushi#
很轻松可以设出 \(f_{i,j,k,p}\) 盘子数量的四维状态,但题目只能开三维。
发现 \(i + j + k + p = n\) 所以用 \(n - j - k - p\) 来表示 \(i\),
然后算概率就可以了。
Candies#
设 \(f_{i,j}\) 为第 \(i\) 个人,用了 \(j\) 颗糖果。
有转移:
考虑前缀和优化。
Independent Set#
树上 \(dp\)
求独立集,\(f_{x,0} = \prod_{y \in \operatorname{subtree}_x}(f_{y,0} + f_{y,1}),f_{x,1} = \prod_{y \in \operatorname{subtree}_x} f_{y,0}\)
Flowers#
最长上升子序列 的 BIT 优化。
Walk#
矩阵快速幂即可。
Permutation#
不考虑具体大小,只考虑相对大小,有转移:
套上前缀和优化。
Grouping#
先状压出每一个物品在连其他的物品可能产生的贡献。
然后状压出每种分组的值,最后将每个状态的值通过枚举子集求最优解。
Intervals#
梦回天天爱打卡。
定义 \(f_i\) 为枚举到 \(i\) 时的答案。
有转移,
然后发现,我们优化不了。
考虑转换状态,定义 \(f_i\) 为枚举到 \(i\) 且 所有区间右端点 \(\leq i\) 时的答案。
那么,我们只需要在转移时,遇到区间右端点时,在线段树上区间加上权值即可。
Tower#
发现 \(s_i - w_j < s_j - w_i\) 时,选择 \(j\) 显然更优。
变形 \(s_i + w_i < s_j + w_j\)。
按 \(s_i + w_i\) 排序,跑 \(01\) 背包
Frog 3#
斜率优化板题
有转移:
对于最优决策点 \(j\),有
那么,\(k = 2h_i,b = f_i + h_i^2 - c\)
\(h_i\) 单调,直接上单调队列。
Grid 2#
定义 \(f_i\) 为 仅经过 \((x_i,y_i)\) (此为障碍) 的 所以路线。
有转移:
然后将 \((h,w)\) 视为最后一个障碍,\(f_{n + 1}\) 就是答案。
P11233 [CSP-S 2024] 染色#
题面:
题目描述
给定一个长度为 \(n\) 的正整数数组 \(A\),其中所有数从左至右排成一排。
你需要将 \(A\) 中的每个数染成红色或蓝色之一,然后按如下方式计算最终得分:
设 \(C\) 为长度为 \(n\) 的整数数组,对于 \(A\) 中的每个数 \(A_i\)(\(1 \leq i \leq n\)):
- 如果 \(A_i\) 左侧没有与其同色的数,则令 \(C_i = 0\)。
- 否则,记其左侧与其最靠近的同色数为 \(A_j\),若 \(A_i = A_j\),则令 \(C_i = A_i\),否则令 \(C_i = 0\)。
你的最终得分为 \(C\) 中所有整数的和,即 \(\sum \limits_{i=1}^n C_i\)。你需要最大化最终得分,请求出最终得分的最大值。
输入格式
本题有多组测试数据。
输入的第一行包含一个正整数 \(T\),表示数据组数。
接下来包含 \(T\) 组数据,每组数据的格式如下:
第一行包含一个正整数 \(n\),表示数组长度。
第二行包含 \(n\) 个正整数 \(A_1, A_2, \dots, A_n\),表示数组 \(A\) 中的元素。
输出格式
对于每组数据:输出一行包含一个非负整数,表示最终得分的最大可能值。
样例 #1
样例输入 #1
3 3 1 2 1 4 1 2 3 4 8 3 5 2 5 1 2 1 4
样例输出 #1
1 0 8
提示
【样例 1 解释】
对于第一组数据,以下为三种可能的染色方案:
- 将 \(A_1, A_2\) 染成红色,将 \(A_3\) 染成蓝色(\(\red{1}\red{2}\blue{1}\)),其得分计算方式如下:
- 对于 \(A_1\),由于其左侧没有红色的数,所以 \(C_1 = 0\)。
- 对于 \(A_2\),其左侧与其最靠近的红色数为 \(A_1\)。由于 \(A_1 \neq A_2\),所以 \(C_2 = 0\)。
- 对于 \(A_3\),由于其左侧没有蓝色的数,所以 \(C_3 = 0\)。
该方案最终得分为 \(C_1 + C_2 + C_3 = 0\)。
- 将 \(A_1, A_2, A_3\) 全部染成红色(\(\red{121}\)),其得分计算方式如下:
- 对于 \(A_1\),由于其左侧没有红色的数,所以 \(C_1 = 0\)。
- 对于 \(A_2\),其左侧与其最靠近的红色数为 \(A_1\)。由于 \(A_1 \neq A_2\),所以 \(C_2 = 0\)。
- 对于 \(A_3\),其左侧与其最靠近的红色数为 \(A_2\)。由于 \(A_2 \neq A_3\),所以 \(C_3 = 0\)。
该方案最终得分为 \(C_1 + C_2 + C_3 = 0\)。
- 将 \(A_1, A_3\) 染成红色,将 \(A_2\) 染成蓝色(\(\red{1}\blue{2}\red{1}\)),其得分计算方式如下:
- 对于 \(A_1\),由于其左侧没有红色的数,所以 \(C_1 = 0\)。
- 对于 \(A_2\),由于其左侧没有蓝色的数,所以 \(C_2 = 0\)。
- 对于 \(A_3\),其左侧与其最靠近的红色数为 \(A_1\)。由于 \(A_1 = A_3\),所以 \(C_3 = A_3 = 1\)。
该方案最终得分为 \(C_1 + C_2 + C_3 = 1\)。可以证明,没有染色方案使得最终得分大于 \(1\)。
对于第二组数据,可以证明,任何染色方案的最终得分都是 \(0\)。
对于第三组数据,一种最优的染色方案为将 \(A_1, A_2, A_4, A_5, A_7\) 染为红色,将 \(A_3, A_6, A_8\) 染为蓝色(\(\red{35}\blue{2}\red{51}\blue{2}\red{1}\blue{4}\)),其对应 \(C = [0, 0, 0, 5, 0, 1, 2, 0]\),最终得分为 \(8\)。
【样例 2】
见选手目录下的 color/color2.in 与 color/color2.ans。
【数据范围】
对于所有测试数据,保证:\(1\leq T\leq 10\),\(2\leq n\leq 2\times 10^5\),\(1\leq A_i\leq 10^6\)。
测试点 \(n\) \(A_i\) \(1\sim 4\) \(\leq 15\) \(\leq 15\) \(5\sim 7\) \(\leq 10^2\) \(\leq 10^2\) \(8\sim 10\) \(\leq 2000\) \(\leq 2000\) \(11,12\) \(\leq 2\times 10^4\) \(\leq 10^6\) \(13\sim 15\) \(\leq 2\times 10^5\) \(\leq 10\) \(16\sim 20\) \(\leq 2\times 10^5\) \(\leq 10^6\)
定义 \(f_i\) 为枚举到 \(i\) 时的答案,\(s_i\) 为相邻两项相同的前缀和。
有转移:
发现只有相同元素会产生贡献,于是用桶维护相同元素的最优决策点。
即: \(buc_{a_i} = \max\{f_j - s_{j + 1}\}\)
有转移:
(虽然但是,我用的是一种更难理解,但是更好写的代码)
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
namespace work{
const int N = 2e5+5,M = 1e6 + 5;
int n,a[M],s[M],f[M],t[M];
void solve() {
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
memset(f,0,sizeof(f));
n = rd();
for(int i = 1;i<=n;i++) a[i] = rd();
for(int i = 2;i<=n;i++) s[i] = s[i - 1] + (a[i] == a[i - 1]) * a[i];
for(int i = 1;i<=n;i++) {
f[i] = f[i - 1];
if(t[a[i]]) f[i] = max(f[i],f[t[a[i]] + 1] + a[i] + s[i] - s[t[a[i]] + 1]);
t[a[i]] = i;
}
wt(f[n]);putchar('\n');
}
}
signed main() {
int T = rd();
while(T--) work::solve();
return 0;
}
作者:MingJunYi
出处:https://www.cnblogs.com/WG-MingJunYi/p/18444727
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!