省选模拟测试16
没啥好说的,正解想不出来暴力还打挂了。
T1 星际穿越
题意描述
有 \(n\) 个星球,它们的编号是 \(1\) 到 \(n\),它们坐落在同一个星系内,这个星系可以抽象为一条数轴,每个星球都是数轴上的一个点,特别地,编号为 \(i\) 的星球的坐标是 \(i\)。
一开始,由于科技上的原因,这 \(n\) 个星球的居民之间无法进行交流,因此他们也不知道彼此的存在。现在,这些星球独立发展出了星际穿越与星际交流的工具。对于第 \(i\)个星球,他通过发射强力信号,成功地与编号在 \([l_i,i-1]\) 的所有星球取得了联系(编号为 \(1\) 的星球没有发出任何信号),取得联系的两个星球会建立 双向 的传送门,对于建立了传送门的两个星球 \(u,v\),\(u\) 上的居民可以花费 \(1\) 单位时间传送到 \(v\),\(v\) 上的居民也可以花费 \(1\) 单位时间传送到 \(u\) ,我们用 \(dist(x,y)\) 表示从编号为 \(x\) 的星球出发,通过一系列星球间的传送门,传送到编号为 \(y\) 的星球最少需要花费的时间。
现在有 \(q\) 个星际商人,第 \(i\) 个商人初始所在的位置是 \(x_i\), 他的目的地是 \([l_i,r_i]\) 中的其中一个星球,保证 \(l_i<r_i<x_i\) 。他会在这些星球中等概率挑选一个星球 \(y\) (每个星球都有一样的概率被选中作为目的地),然后通过一系列星球的传送门,花费最少的时间到达星球 \(y\) 。商人想知道他花费的期望时间是多少?也就是计算 \(\displaystyle {\frac{1}{r_i-l_i+1}}\sum_{y=l_i}^{r_i} dis(x_i,y)\) 。
数据范围:\(1\leq n,q\leq 3\times 10^5\)
solution
倍增/主席树。
这个口胡起来太麻烦了,看这篇题解 领悟一下吧。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int N = 3e5+10;
int n,q,l,r,x,tot,rt[N],L[N],suf[N];
struct Tree
{
int lc,rc,sum,tag;
}tr[N*30];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
int gcd(int a,int b)
{
if(b == 0) return a;
else return gcd(b,a%b);
}
void insert(int &o,int last,int l,int r,int L,int R)
{
o = ++tot;
tr[o].lc = tr[last].lc;
tr[o].rc = tr[last].rc;
tr[o].tag = tr[last].tag;
tr[o].sum = tr[last].sum + min(r,R)-max(l,L)+1;
int mid = (l+r)>>1;
if(L <= l && R >= r)
{
tr[o].tag++;
return;
}
if(L <= mid) insert(tr[o].lc,tr[last].lc,l,mid,L,R);
if(R > mid) insert(tr[o].rc,tr[last].rc,mid+1,r,L,R);
}
int query(int o,int l,int r,int L,int R)
{
if(!o || l > r) return 0;
if(L <= l && R >= r) return tr[o].sum;
int ans = tr[o].tag * (min(r,R)-max(l,L)+1);
int mid = (l+r)>>1;
if(L <= mid) ans += query(tr[o].lc,l,mid,L,R);
if(R > mid) ans += query(tr[o].rc,mid+1,r,L,R);
return ans;
}
signed main()
{
n = read();
for(int i = 2; i <= n; i++) L[i] = read();
suf[n] = L[n];
for(int i = n-1; i >= 1; i--) suf[i] = min(suf[i+1],L[i]);
for(int i = 2; i <= n; i++) insert(rt[i],rt[suf[i]],1,n,1,i-1);
q = read();
for(int i = 1; i <= q; i++)
{
l = read(); r = read(); x = read();
int ans1 = (r-l+1);
ans1 += query(rt[L[x]],1,n,l,min(r,L[x]));
int ans2 = r-l+1;
int g = gcd(ans1,ans2);
printf("%lld/%lld\n",ans1/g,ans2/g);
}
return 0;
}
T2 树
题意描述
给定一棵有根树,令根节点为 \(1\),有 \(n\) 个节点。
每个节点有五种形式:AND 表示对两个子节点进行与运算,OR 表示对两个子节点进行或运算,XOR 表示对两个子节点进行异或运算,NOT 表示对子节点(只有一个)进行非运算。特殊地,IN 表示这个节点是叶子节点,它的初值(0/1)由读入决定。
现在,你要依次对每个叶子节点进行改变,改变其 0/1 状态,并按叶节点编号顺序,分别输出改变叶节点的状态后,根节点的值是 0 还是 1。
数据范围:\(1\leq n\leq 2\times 10^6\)
solution
位运算+dfs。
如果只有 \(\text{xor}\) 运算和 \(\text{not}\) 运算的话,不难发现当叶子节点的状态改变的时候,根节点的状态一定会取反。
加上 \(\text{or}\) 和 \(\text{and}\) 运算的话,有一些情况叶子节点状态改变是不会影响到根节点状态的。
具体来说,如果运算为 \(\text{or}\) 运算,且左子树的答案为 \(1\), 那么无论右子树中的叶子节点的状态怎么改变都不会影响到根节点的状态,对于右子树答案为 \(1\) 的情况同理。
如果运算为 \(\text{and}\) 运算,左子树答案为 \(0\) 的时候,右子树中叶子节点的状态无论怎么改变也不会影响到根节点的状态,右子树的情况同理。
考虑对于不会影响到根节点状态的这些节点打个 \(\text{tag}\) 标记,在 \(dfs\) 一遍下传标记即可。
最后如果一个叶子节点有标记,那么这个叶子节点的状态不会影响到根节点的状态,否则根节点的初始状态取反一下就是答案。
至于根节点的初始状态,一遍 \(dfs\) 就可以求出来。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e6+10;
int n,son[N][2],tag[N],val[N],opt[N];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
void dfs1(int x,int fa)
{
if(son[x][0]) dfs1(son[x][0],x);
if(son[x][1]) dfs1(son[x][1],x);
if(opt[x] == 1) val[x] = val[son[x][0]] & val[son[x][1]];
if(opt[x] == 2) val[x] = val[son[x][0]] | val[son[x][1]];
if(opt[x] == 3) val[x] = val[son[x][0]] ^ val[son[x][1]];
if(opt[x] == 4) val[x] = !val[son[x][0]];
if(opt[x] == 1 && val[son[x][0]] == 0) tag[son[x][1]] = 1;
if(opt[x] == 1 && val[son[x][1]] == 0) tag[son[x][0]] = 1;
if(opt[x] == 2 && val[son[x][0]] == 1) tag[son[x][1]] = 1;
if(opt[x] == 2 && val[son[x][1]] == 1) tag[son[x][0]] = 1;
}
void dfs2(int x,int fa)
{
if(son[x][0])
{
tag[son[x][0]] |= tag[x];
dfs2(son[x][0],x);
}
if(son[x][1])
{
tag[son[x][1]] |= tag[x];
dfs2(son[x][1],x);
}
}
int main()
{
n = read();
for(int i = 1; i <= n; i++)
{
string ch; cin>>ch;
if(ch == "AND") opt[i] = 1, son[i][0] = read(), son[i][1] = read();
if(ch == "OR") opt[i] = 2, son[i][0] = read(), son[i][1] = read();
if(ch == "XOR") opt[i] = 3, son[i][0] = read(), son[i][1] = read();
if(ch == "NOT") opt[i] = 4, son[i][0] = read();
if(ch == "IN") opt[i] = 5, val[i] = read();
}
dfs1(1,0); dfs2(1,0);
for(int i = 1; i <= n; i++) if(opt[i] == 5) printf("%d",!tag[i] ? !val[1] : val[1]);
return 0;
}
T3 最优选择
题意描述
给你 \(N\) 个物品,其中选择小于等于 \(M\) 个,每个物品有两个属性,\(a_i,b_i\),而选择的物品的权值为
\(\displaystyle \sum_{} b_1^{s1} - \left(\max(a_i)-\min(a_i)\right)^{s_2}\),求最大权值.
数据范围:\(1\leq n\leq 2\times 10^5,1\leq m\leq \min(n,50),1\leq s_1,s_2\leq 2\) 。
solution
贪心+链表。
有一个很 \(\text{naive}\) 的想法就是先把 \(a_i\) 从小到大排一下序,然后枚举两个区间 \(L,R\) ,找这一段区间内 \(b_i\) 最大的 \(m\) 个即可。
这样的复杂度是 \(O(n^2)\) 的,显然会 \(\text{TLE}\) 。
优化一下,可以考虑从小到大枚举 \(b_i\), 对于包含 \(b_i\) 的长度为 \(m\) 的区间,不难发现此时区间内的元素一定是这一段区间内 \(b_i\) 最大的 \(m\) 个,对这些区间统计一下答案,然后在把 \(i\) 这个位置删除,拿链表维护一下相邻的元素即可。
由于我们需要枚举 \(n\) 次,每次最多会对 \(2m\) 个区间统计答案,所以复杂度是 \(O(nm)\) 的。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n,m,s1,s2,ans;
int head[N],net[N],id[N],sum[N];
struct node
{
int a,b,id;
}e[N],sta[N];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
bool cmp(node x, node y)
{
if(x.a == y.a) return x.b < y.b;
return x.a < y.a;
}
bool comp(node x, node y)
{
if(x.b == y.b) return x.a < y.a;
return x.b < y.b;
}
int calc(long long x, int k)
{
int res = 1;
for(int j = 1; j <= k; j++) res = res * x;
return res;
}
signed main()
{
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
n = read(); m = read(); s1 = read(); s2 = read();
for(int i = 1; i <= n; i++) e[i].a = read(), e[i].b = read();
sort(e+1,e+n+1,cmp);
for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + e[i].b, e[i].id = i;
for(int i = m, j = 1; i <= n; i++, j++)//一开始先统计连续的长度为m的区间的答案
{
ans = max(ans,calc(sum[i]-sum[j - 1],s1)-calc(e[i].a-e[j].a,s2));
}
for(int i = 1; i <= n; i++) head[i] = i-1, net[i] = i+1;
sort(e+1,e+n+1,comp);
for(int i = 1;i <= n; i++) id[i] = e[i].id;
sort(e+1,e+n+1,cmp);
for(int i = 1; i <= n; i++) //从小到大枚举bi
{
int p = id[i];
head[net[p]] = head[p];//删除i这个位置。
net[head[p]] = net[p];
int l = p, top = 0;
for(int j = 1; j < m; j++)
{
if(!head[l]) break;
l = head[l]; sta[++top] = e[l];
}
reverse(sta+1,sta+top+1);
int r = p;
for(int j = 1; j < m; j++)
{
if(net[r] == n+1) break;
r = net[r]; sta[++top] = e[r];
}
for(int j = 1; j <= top; j++) sum[j] = sum[j - 1] + sta[j].b;
for(int j = m, k = 1; j <= top; j++, k++)//取出包含bi的长度为m的区间,并统计答案
{
ans = max(ans,calc(sum[j]-sum[k - 1],s1)-calc(sta[j].a-sta[k].a,s2));
}
}
printf("%lld\n",ans);
fclose(stdin); fclose(stdout);
return 0;
}