F x。查询大于等于 x 的第一个未标记的
a
i
a_i
ai;若没有,则输出
1
0
12
10^{12}
1012
R x。清除小于等于 x 的所有标记;若没有,则不操作。本操作次数不超过10次。
C x。查询小于等于 x 的所有未标记数之和;若没有,则输出0。
我们先排序,这样子可以很快定位到x。
因为单点查询修改,都是大于等于x的第一个
a
i
a_i
ai,因此我们可以用并查集往后连。p[i] 表示从 i 开始往后第一个未标记的结点的下标。一开始都是未标记的,所以是 p[i] = i。标记了 i 之后,unite(i, i + 1),把 i 开始的未标记下一个结点连到 p[i + 1] 上面。
至于 R, C 操作,区间操作用线段树。线段树一开始都是零,只保存标记的节点。每标记一个结点,就把对应的地方 += a[i]。线段树结点保留三个属性:l, r, sum.
易错点:
找下一个未标记节点的时候:pos = find(id),而不是 pos = find(id)
query 函数一定要小心 if (r <= mid) return query(2 * u, l, r);,递归的时候不是 query(2 * u, l, mid)!
每个结尾千万不要忘记输出空格!
#include<iostream>#include<algorithm>#include<cstring>typedefunsignedlonglong ll;usingnamespace std;constint maxn =1000010;const ll M =1e12;int p[maxn];
ll sum[maxn];intfind(int x){if(p[x]== x)return x;return p[x]=find(p[x]);}voidunite(int a,int b){if(find(a)==find(b))return;
p[a]=find(b);}unsignedlonglong k1, k2;int N;longlong a[1000001];unsignedlonglongxorShift128Plus(){unsignedlonglong k3 = k1, k4 = k2;
k1 = k4;
k3 ^= k3 <<23;
k2 = k3 ^ k4 ^(k3 >>17)^(k4 >>26);return k2 + k4;}voidgen(){scanf("%d %llu %llu",&N,&k1,&k2);for(int i =1; i <= N; i++){
a[i]=xorShift128Plus()%999999999999+1;}}struct node {int l, r;
ll sum;}tr[maxn *4];voidpushup(int u){
tr[u].sum = tr[2* u].sum + tr[2* u +1].sum;}voidbuild(int u,int l,int r){if(l == r) tr[u]={ l, l,0};else{
tr[u].l = l, tr[u].r = r;int mid =(l + r)/2;build(2* u, l, mid),build(2* u +1, mid +1, r);//pushup(u);}}
node query(int u,int l,int r){if(l <= tr[u].l && tr[u].r <= r)return tr[u];int mid =(tr[u].l + tr[u].r)/2;if(r <= mid)returnquery(2* u, l, r);elseif(l > mid)returnquery(2* u +1, l, r);else{
node left =query(2* u, l, r);
node right =query(2* u +1, l, r);
node res;
res.sum = left.sum + right.sum;return res;}}voidmodify(int u,int x, ll v){// a[x] = v;if(tr[u].l == x && tr[u].r == x) tr[u].sum = v;else{int mid =(tr[u].l + tr[u].r)/2;if(x <= mid)modify(2* u, x, v);elsemodify(2* u +1, x, v);pushup(u);}}intmain(){gen();sort(a +1, a + N +1);//for (int i = 1; i <= N; i++) printf("%llu ", a[i]);for(int i =1; i <= N; i++){
sum[i]= sum[i -1]+ a[i];}int Q;scanf("%d",&Q);build(1,1, N);for(int i =1; i <= N +1; i++){
p[i]= i;}while(Q--){char op[5];
ll x;scanf("%s%llu", op,&x);if(op[0]=='D'){// 标记>=x的第一个未被标记的a[i]int id =lower_bound(a +1, a + N +1, x)- a;int pos =find(id);if(pos == N +1)continue;unite(pos, pos +1);modify(1, pos, a[pos]);}if(op[0]=='F'){// 查询>=x的第一个未被标记的a[i]int id =lower_bound(a +1, a + N +1, x)- a;int pos =find(id);if(pos == N +1)printf("%llu\n", M);else{printf("%llu\n", a[pos]);}}if(op[0]=='R'){// <=x的标记全部清零int id =upper_bound(a +1, a + N +1, x)- a -1;if(id ==0)continue;for(int i =1; i <= id; i++){
p[i]= i;modify(1, i,0);}}if(op[0]=='C'){//查询小于等于 x 的所有未标记数之和;若没有,则输出0。int id =upper_bound(a +1, a + N +1, x)- a -1;if(id ==0)printf("0\n");else{printf("%llu\n", sum[id]-query(1,1, id).sum);}}}return0;}
C. Death by Thousand Cuts
题意:一个平面
A
x
+
B
y
+
C
z
=
D
Ax + By + Cz = D
Ax+By+Cz=D,D变化的时候,与一个长方体的几个棱有交点的概率。其实就是求,一个平面的D不断变化,在哪些范围与长方体有几个棱有交点。
我们回忆原点到平面距离公式。
d
=
∣
D
∣
A
2
+
B
2
+
C
2
d = \frac{|D|}{\sqrt{A^2+B^2+C^2}}
d=A2+B2+C2∣D∣. 因此,D从小到大变化,就模拟了平面从第七卦限到第一卦限的一个平移的过程(当 A, B, C > 0 的时候)。
那么交点的个数怎么知道呢?接着观察发现,只要A, B, C均不为零,那么与长方形有交点时最少是三个焦点。而且,我们发现平面一定是沿着某一个体对角线的方向移动。因为我们只关注与几个棱交点的概率。那么根据对称性,我们发现,和从第七卦限到第一卦限的移动,结果是一样的。因此每次只关注
∣
A
∣
,
∣
B
∣
,
∣
C
∣
|A|, |B|, |C|
∣A∣,∣B∣,∣C∣ 即可。
这样子,我们观察,一定先经过
(
0
,
0
,
0
)
(0, 0, 0)
(0,0,0)。最后经过
(
a
,
b
,
c
)
(a, b, c)
(a,b,c)。经过
(
a
,
0
,
0
)
,
(
0
,
b
,
0
)
,
(
0
,
0
,
c
)
(a, 0, 0), (0, b, 0), (0, 0, c)
(a,0,0),(0,b,0),(0,0,c)这三个点时,我们发现棱数+1(画画图)。经过
(
a
,
b
,
0
)
,
(
a
,
0
,
c
)
,
(
0
,
b
,
c
)
(a, b, 0), (a, 0, c), (0, b, c)
(a,b,0),(a,0,c),(0,b,c)一定会棱数-1。
#include<iostream>#include<algorithm>#include<cstring>usingnamespace std;typedeflonglong ll;typedef pair<ll, ll> P;const ll mod =1e9+7;
ll mod_pow(ll x, ll n){
ll res =1;while(n){if(n &1) res = res * x % mod;
x = x * x % mod;
n >>=1;}return res;}
ll gcd(ll a, ll b){if(b ==0)return a;returngcd(b, a % b);}
ll p[7];voidcalc(ll a, ll b, ll c){//相当于把八个点带入直线方程,求D的值
P Ds[8]={P(0,0),P(a,1),P(b,1),P(c,1),P(a + b,-1),P(b + c,-1),P(a + c,-1),P(a + b + c,0)};sort(Ds, Ds +8);
ll edges =3, last_D = Ds[0].first;for(int i =1; i <8; i++){
p[edges]+=(Ds[i].first - last_D);
last_D = Ds[i].first;
edges += Ds[i].second;}}intmain(){int T;scanf("%d",&T);while(T--){memset(p,0,sizeof p);
ll a, b, c, A, B, C;scanf("%lld%lld%lld%lld%lld%lld",&a,&b,&c,&A,&B,&C);
A =abs(A), B =abs(B), C =abs(C);if(A && B && C)calc(a * A, b * B, c * C);//只要 A, B, C 有一个0,那么一定是与四个棱交点(与某个棱重合的概率可以认为是0,因为点的长度是0嘛)。else{
p[4]=1;}
ll sum =0;for(int i =3; i <=6; i++){
sum += p[i];}for(int i =3; i <=6; i++){
ll d =gcd(p[i], sum);
ll ans =(p[i]/ d)*mod_pow(sum / d, mod -2)% mod;printf("%lld%c", ans, i ==6?'\n':' ');}}return0;}
D. False God
拓扑图最长路径
小心数组的范围,maxm 设为
n
2
n^2
n2
做法一:
#include<iostream>#include<algorithm>#include<cstring>#include<queue>usingnamespace std;constint maxn =1010, maxm =1000010;int h[maxn], e[maxm], ne[maxm], idx;int x[maxn], y[maxn], N, d[maxn];int din[maxn];voidadd(int a,int b){
e[idx]= b, ne[idx]= h[a], h[a]= idx++;}voidtoposort(){
d[0]=0;
queue<int> que;for(int i =0; i <= N; i++){if(din[i]==0) que.push(i);}while(que.size()){int u = que.front(); que.pop();for(int i = h[u]; i !=-1; i = ne[i]){int v = e[i];
d[v]=max(d[v], d[u]+1);if(--din[v]==0) que.push(v);//printf("### %d %d\n", u, v);}}}intmain(){int T;scanf("%d",&T);while(T--){memset(h,-1,sizeof h);memset(d,-0x3f,sizeof d);memset(din,0,sizeof d);
idx =0;scanf("%d%d",&x[0],&y[0]);scanf("%d",&N);for(int i =1; i <= N; i++){scanf("%d%d",&x[i],&y[i]);}for(int i =1; i <= N; i++){for(int j =0; j <= N; j++){if(i == j)continue;if(abs(x[i]- x[j])<= y[i]- y[j]+1){add(j, i);
din[i]++;}}}toposort();int ans =0;for(int i =0; i <= N; i++){
ans =max(ans, d[i]);}printf("%d\n", ans);}return0;}
做法二
#include<iostream>#include<algorithm>#include<cstring>usingnamespace std;constint maxn =1010, maxm =1000010;int h[maxn], e[maxm], ne[maxm], idx;int x[maxn], y[maxn], N, d[maxn];int din[maxn];bool vis[maxn];voidadd(int a,int b){
e[idx]= b, ne[idx]= h[a], h[a]= idx++;}intdp(int u){if(vis[u])return d[u];
d[u]=0;
vis[u]=true;for(int i = h[u]; i !=-1; i = ne[i]){int v = e[i];
d[u]=max(d[u],dp(v)+1);}return d[u];}intmain(){int T;scanf("%d",&T);while(T--){memset(vis,false,sizeof vis);memset(h,-1,sizeof h);memset(d,-0x3f,sizeof d);memset(din,0,sizeof d);
idx =0;scanf("%d%d",&x[0],&y[0]);scanf("%d",&N);for(int i =1; i <= N; i++){scanf("%d%d",&x[i],&y[i]);}for(int i =1; i <= N; i++){for(int j =0; j <= N; j++){if(i == j)continue;if(abs(x[i]- x[j])<= y[i]- y[j]+1){add(j, i);
din[i]++;}}}
d[0]=0;printf("%d\n",dp(0));}return0;}
G. InkBall FX
挖坑(只有两个人过题啊)
H. Jingle Bells
在一棵树上的节点上依次挂铃铛,第一个铃铛规定挂在根节点上,S是已经选择的结点。选下一个结点挂铃铛时都必须
(
u
,
v
)
∈
E
(
G
)
,
u
∈
S
,
v
∉
S
(u,v)∈E(G),u∈S,v∉S
(u,v)∈E(G),u∈S,v∈/S,增加的点数是
b
i
×
∑
j
∉
S
a
j
.
b_i×∑_{j∉S}a_j.
bi×∑j∈/Saj.
首先我们发现,选下一个结点时,
b
b
b 越大越好,
a
a
a 越小越好。因此 一个很自然的想法是 按照
b
/
a
b/a
b/a 来贪心选取。确实是对的,但是我不会严格证明。
接下来的问题,怎么样找到上述 v?用并查集。我们发现,选中的结点,需要加上当前没有选中的所有结点的
a
a
a 之和。其实可以拆开来求。每选择一个v,我们计算对答案的贡献:
2021 BNU Winter Training 5 (The 15th Heilongjiang Provincial Collegiate Programming Contest)
2021 BNU Winter Training 4 (The 15th Heilongjiang Provincial Collegiate Programming Contest)训练网址A. Bills of ParadiseC. Death by Thousand CutsD. False God拓扑图最长路径小心数组的范围,maxm 设为 n2n^2n2做法一:#include<iostream>#include<algorithm>#include