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,我们计算对答案的贡献: