LGP14989 传送
原题链接:传送
分析
别急,我忘了第一个大于的怎么求了😭
感觉这会是一个很典的 Trick。
跳到一个公共的点 → \rightarrow → LCA 。
第一个大于的,显然是个 单调栈 。
我们贪心地发现,每次往左右两边更小的值跳就行了,因为这个会把中间的东西都集中起来,如果跳更大的很容易跳不动了。然后答案要求尽可能多,所以,跳较小的。
那么,可以建树了!
然后直接做就行了!
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500005;
int n, q, p[N];
stack<int> stk;
int lp[N], rp[N];
int f[N][25], de[N];
int head[N << 1], tot;
struct edge{
int u, to, nxt;
}e[N << 1];
void add(int u, int v){
e[++tot].u = u;
e[tot].nxt = head[u];
e[tot].to = v;
head[u] = tot;
}
void dfs(int u, int fa){
f[u][0] = fa;
de[u] = de[fa] + 1;
for (int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if (v == fa)
continue;
dfs(v, u);
}
}
void init(){
for (int j = 1; j <= 20; j++){
for (int u = 1; u <= n; u++){
f[u][j] = f[f[u][j - 1]][j - 1];
}
}
}
int LCA(int x, int y){
if (de[x] < de[y])
swap(x, y);
int delta = de[x] - de[y];
for (int i = 20; i >= 0; i--){
if (delta & (1 << i)){
x = f[x][i];
}
}
if (x == y)
return x;
for (int i = 20; i >= 0; i--){
if (f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; i++){
cin >> p[i];
}
for (int i = 1; i <= n; i++){
while (!stk.empty() && p[stk.top()] <= p[i]){
stk.pop();
}
if (stk.empty()){
lp[i] = 0x3f3f3f3f3f3f3f3f;
}
else{
lp[i] = p[stk.top()];
}
stk.push(i);
}
while (!stk.empty())
stk.pop();
for (int i = n; i >= 1; i--){
while (!stk.empty() && p[stk.top()] <= p[i]){
stk.pop();
}
if (stk.empty()){
rp[i] = 0x3f3f3f3f3f3f3f3f;
}
else{
rp[i] = p[stk.top()];
}
stk.push(i);
}
for (int i = 1; i <= n; i++){
int lv = lp[i], rv = rp[i];
int minn = min(lv, rv);
if (minn == 0x3f3f3f3f3f3f3f3f)
continue;
add(p[i], minn);
add(minn, p[i]);
}
dfs(n, 0);
init();
for (int cs = 1, k, id; cs <= q; cs++){
cin >> k;
cin >> id;
id = p[id];
for (int i = 2, a; i <= k; i++){
cin >> a;
id = LCA(id, p[a]);
}
cout << de[id] << '\n';
}
return 0;
}
LGP14969 They'll lead me to you
分析
打一个 O ( n 2 V ) O(n^2V) O(n2V) 的暴力,然后拼一下部分分,轻松拿下 27 p t s 27pts 27pts!
好的,暴力打完了。
那么,我们发现一些事情就是,第一,值域 V ≤ 2 n V\le 2n V≤2n,其次,由于 a i = min ( a i , mex ) , i ∈ l , r a_i=\min(a_i,\operatorname{mex}),i\inl,r ai=min(ai,mex),i∈l,r,而且, mex \operatorname{mex} mex 恰好大概就是两块拼起来的,就是一部分是 0 , a j 0,a_j 0,aj, 另一部分是 a t , a k a_t,a_k at,ak,前面是连续的。
我大胆猜测,就是每次选择 1 , n 1,n 1,n 可能是优的。不过这个有点糙,需要一些进一步的思考。好像 0 0 0 把序列分成了一些块,所以,我们......还是优的!所以说,思考一下,除了样例 1 1 1 的 hack,其他的都可能在这个里面实现。
确实骗过了一些点,但是,由于捆包,所以,没有获得更高的分数。
由于以上关键的观察 V ≤ 2 n V\le 2n V≤2n,所以,我们直接枚举 mex \operatorname{mex} mex,对于不含 mex \operatorname{mex} mex 的区间,扔进桶里,树状数组直接做就行了。
这么牛?就是说,因为每次取 min \min min,所以,直接做有正确性。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500005;
int n, a[N];
bool vis[N << 1];
int c1[N], c2[N];
vector<int> pos[N << 1];
void add(int x){
for (int i = x; i <= n; i += i & (-i)){
c1[i]++;
c2[i] += a[x];
}
}
int query1(int x){
int res = 0;
for (int i = x; i; i -= i & (-i)){
res += c1[i];
}
return res;
}
int query2(int x){
int res = 0;
for (int i = x; i; i -= i & (-i)){
res += c2[i];
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
bool subA = true, subB = true;
for (int i = 1; i <= n; i++){
cin >> a[i];
if (a[i] == 0){
subA = false;
}
if (i >= 3 && a[i] == 0){
subB = false;
}
}
if (n <= 300){
int mn = 0;
for (int i = 1; i <= n; i++){
mn += a[i];
}
int resl, resr;
for (int l = 1; l <= n; l++){
for (int r = l; r <= n; r++){
memset(vis, false, sizeof(vis));
for (int i = l; i <= r; i++){
vis[a[i]] = true;
}
int mex = 0;
for (int i = 0; i <= n * 2; i++){
if (!vis[i]){
mex = i;
break;
}
}
int sum = 0;
for (int i = 1; i <= n; i++){
if (i >= l && i <= r){
sum += min(mex, a[i]);
}
else{
sum += a[i];
}
}
if (mn > sum){
mn = sum;
resl = l;
resr = r;
}
}
}
cout << mn;
return 0;
}
if (subA){
cout << 0;
return 0;
}
if (a[2] == 0 && subB){
for (int i = 1; i <= n; i++){
vis[a[i]] = true;
}
int mex;
for (int i = 0; i <= n * 2; i++){
if (!vis[i]){
mex = i;
break;
}
}
cout << min({a[1], mex * n});
return 0;
}
int sum = 0;
for (int i = 1; i <= n; i++){
sum += a[i];
pos[a[i]].push_back(i);
}
int ans = 0x3f3f3f3f3f3f3f3f;
for (int mex = n * 2; mex >= 0; mex--){
if (pos[mex].empty()){
ans = min(ans, sum - query2(n) + query1(n) * mex);
continue;
}
ans = min(ans, sum - query2(pos[mex][0]) + query1(pos[mex][0]) * mex);
for (int i = 0; i < (int)pos[mex].size(); i++){
if (i == (int)pos[mex].size() - 1){
ans = min(ans, sum - (query2(n) - query2(pos[mex][i])) + (query1(n) - query1(pos[mex][i])) * mex);
}
else{
ans = min(ans, sum - (query2(pos[mex][i + 1] - 1) - query2(pos[mex][i])) + (query1(pos[mex][i + 1] - 1) - query1(pos[mex][i])) * mex);
}
add(pos[mex][i]);
}
}
cout << ans;
}
LGP8955 「VUSC」Card Tricks
原题链接:「VUSC」Card Tricks
分析
尝试在半个小时内将其击败......或者我被击败😭
假做法被 LYX 骂了😭
难道说我在一个 d e l t a delta delta 上做吗?
看题解了😭
由于每个数不会变小,所以在时间轴上二分就行了😭
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1000005;
int n, q, P;
int a[N];
int head[N], tot;
struct edge{
int to, nxt, val;
}e[N << 1];
void add(int u, int v, int w){
e[++tot].to = v;
e[tot].nxt = head[u];
e[tot].val = w;
head[u] = tot;
}
int tr[N << 2];
struct segtree{
void pushup(int p){
tr[p] = tr[p << 1] | tr[p << 1 | 1];
}
void modify(int p, int l, int r, int s, int val){
if (l == r){
tr[p] = val;
return ;
}
int mid = (l + r) >> 1;
if (s <= mid)
modify(p << 1, l, mid, s, val);
else
modify(p << 1 | 1, mid + 1, r, s, val);
pushup(p);
}
int query(int p, int l, int r, int val){
if (l == r){
if ((val | tr[p]) <= P)
return -1;
return l;
}
int mid = (l + r) >> 1;
if ((val | tr[p << 1]) > P)
return query(p << 1, l, mid, val);
else
return query(p << 1 | 1, mid + 1, r, val | tr[p << 1]);
}
}T;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q >> P;
for (int i = 1; i <= n; i++){
cin >> a[i];
}
for (int cs = 1, l, r, x; cs <= q; cs++){
cin >> l >> r >> x;
add(l, cs, x);
add(r + 1, cs, 0);
}
for (int u = 1; u <= n; u++){
for (int i = head[u]; i; i = e[i].nxt){
T.modify(1, 1, q, e[i].to, e[i].val);
}
cout << T.query(1, 1, q, a[u]) << " ";
}
}