2024.10.23

First Post:

Last Update:

Word Count:
3.3k

Read Time:
17 min

Day -3

最近都不知道写什么题···

啥也不会啥也不想写···

请注意,以下有大量口胡内容。

总结一下最近写的杂题

[湖南集训] Clever Rabbit

首先手玩样例,可以发现答案非常少。

然后打了一个小表,发现答案确实非常少。

因为 ,所以考虑打表。

这里有一个性质:如果两个数字构成的可重集相同,则 值一样。

所以可以直接枚举集合,一共有 个。

然后高精算一下答案,把表打出来即可。

分数统计

无聊题。

询问一前缀和,询问二离线下来回滚莫队,询问三 ST 表。

没删 define int long long 被卡空间,导致没一遍过,气。

[HEOI2014] 大工程

看到保证关键点总数想到虚树。

建完虚树以后就是个简单树形 dp。

询问一就是经典的

做法是把贡献拆成每条边的贡献,统计每条边的经过次数。

询问二三就是树上最短路径长和树的直径。

注意虚树上边的边权是什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=1e6+10,inf=1e18;
int n,m,cnt,tim,num,ans,mn,mx,len;
int head[maxn];
int key[maxn],a[maxn<<1];
int f[maxn][3];
bitset<maxn> vis;
vector<int> g[maxn];
struct edge{
int to,nxt;
}e[maxn<<1];
struct node{
int fa,siz,dep,top,hson,dfn;
}t[maxn];
inline void add(int u,int v){
e[++cnt]={v,head[u]};
head[u]=cnt;
}
inline void dfs1(int u,int fa){
t[u].fa=fa,t[u].dep=t[fa].dep+1,t[u].siz=1;
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue;
dfs1(v,u);
t[u].siz+=t[v].siz;
if(!t[u].hson||t[v].siz>t[t[u].hson].siz) t[u].hson=v;
}
}
inline void dfs2(int u,int top){
t[u].top=top,t[u].dfn=++tim;
if(!t[u].hson) return;
dfs2(t[u].hson,top);
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==t[u].fa||v==t[u].hson) continue;
dfs2(v,v);
}
}
inline int lca(int u,int v){
while(t[u].top!=t[v].top){
if(t[t[u].top].dep<t[t[v].top].dep) swap(u,v);
u=t[t[u].top].fa;
}
return t[u].dep<t[v].dep?u:v;
}
inline bool cmp(int x,int y){return t[x].dfn<t[y].dfn;}
inline void build(){
sort(key+1,key+num+1,cmp);
len=0;
for(re int i=1;i<num;++i){
a[++len]=key[i];
a[++len]=lca(key[i],key[i+1]);
}
a[++len]=key[num];
a[++len]=1;
sort(a+1,a+len+1,cmp);
len=unique(a+1,a+len+1)-(a+1);
for(re int i=1;i<len;++i){
int lc=lca(a[i],a[i+1]);
g[lc].push_back(a[i+1]),g[a[i+1]].push_back(lc);
}
}
inline void dfs(int u,int fa){
t[u].siz=vis[u];
if(vis[u]) f[u][1]=f[u][2]=0;
else f[u][1]=inf,f[u][2]=-inf;
for(auto v:g[u]){
if(v==fa) continue;
dfs(v,u);
int w=t[v].dep-t[u].dep;
t[u].siz+=t[v].siz;
ans+=w*t[v].siz*(num-t[v].siz);
mn=min(mn,f[u][1]+f[v][1]+w),mx=max(mx,f[u][2]+f[v][2]+w);
f[u][1]=min(f[u][1],f[v][1]+w),f[u][2]=max(f[u][2],f[v][2]+w);
}
g[u].clear();
}
inline void solve(){
build();
ans=0,mn=inf,mx=-inf;
dfs(1,0);
cout<<ans<<" "<<mn<<" "<<mx<<'\n';
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(re int i=1,u,v;i<n;++i) cin>>u>>v,add(u,v),add(v,u);
dfs1(1,0),dfs2(1,1);
cin>>m;
while(m--){
cin>>num;
for(re int i=1;i<=num;++i) cin>>key[i],vis[key[i]]=1;
solve();
for(re int i=1;i<=num;++i) vis[key[i]]=0;
}
return 0;
}

[SCOI2016] 美味

异或问题基本就三个方向:按位考虑,01trie,线性基。

如果没有这个加 ,那这是可持久化trie模板。

upd:可持久化trie好像也能做?不太会,还是按位考虑好想。

但现在有 ,所以只能按位考虑。

我们从高位到低位贪心。

设前面位累计的答案为 ,然后考虑当前位情况。

设当前考虑到第 位。

若当前位 位为 1,则我们希望 当前位为 0。

那么

若当前位 位为 0,则我们希望 当前位为 1。

那么

如果这一位能找到合法的 ,那么 就加上

但这为什么是对的?为什么不会在低位的区间里找一个合法的 ,但它从未出现在高位的区间里过?

如果你仔细想,你会发现如果高位有一位选到了合法值,那么低位的区间会变小。

也就是说,区间大小是单调不增的。

那么如果你在低位的区间里找到了一个合法值,那么它一定也出现在高位的区间里过。

那么现在问题变为了序列上在 内,值域在 内的信息。

因为要求在线,所以需要主席树上区间二分。

当然树套树也行,但复杂度两个 ,太劣了。

如果题目能离线,那么莫队+值域分块一般也能做。

复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=2e5+10,lgn=30,V=1e5;
int n,m,segcnt;
int rt[maxn];
struct tree{
int ls,rs,sum;
}tr[maxn*lgn];
inline void up(int p){tr[p].sum=tr[tr[p].ls].sum+tr[tr[p].rs].sum;}
inline void update(int l,int r,int pos,int &p1,int p2){
p1=++segcnt;
tr[p1]=tr[p2];
if(l==r){++tr[p1].sum;return;}
int mid=(l+r)>>1;
if(pos<=mid) update(l,mid,pos,tr[p1].ls,tr[p2].ls);
else update(mid+1,r,pos,tr[p1].rs,tr[p2].rs);
up(p1);
}
inline bool query(int l,int r,int L,int R,int p1,int p2){
if(tr[p1].sum==tr[p2].sum) return 0;
int mid=(l+r)>>1;
if(l>=L&&r<=R){
if(l==r) return 1;
bool res=query(l,mid,L,R,tr[p1].ls,tr[p2].ls);
if(res) return 1;
else return query(mid+1,r,L,R,tr[p1].rs,tr[p2].rs);
}
else{
if(L<=mid){
bool res=query(l,mid,L,R,tr[p1].ls,tr[p2].ls);
if(res) return 1;
}
if(R>mid){
bool res=query(mid+1,r,L,R,tr[p1].rs,tr[p2].rs);
if(res) return 1;
}
return 0;
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(re int i=1,x;i<=n;++i){
cin>>x;
update(0,V,x,rt[i],rt[i-1]);
}
for(re int i=1,ans,b,x,l,r;i<=m;++i){
cin>>b>>x>>l>>r;
ans=0;
for(re int j=17,L,R,op;j>=0;--j){
if((b>>j)&1) L=ans-x,R=ans+(1<<j)-1-x,op=0;
else L=ans+(1<<j)-x,R=ans+(1<<(j+1))-1-x,op=1;
if(!query(0,V,max(0ll,L),min(R,V),rt[l-1],rt[r])) op^=1;
ans+=(op<<j);
}
cout<<(ans^b)<<'\n';
}
return 0;
}

[SDOI2013] 森林

看到链上 kth 想到主席树,看到动态加边想到 LCT。

但这俩没法同时维护。

所以考虑每个点维护一颗主席树,然后合并时启发式合并。

然后把链上 kth 做一下树上差分,同时查四颗主席树然后合并答案即可。

但有个问题,怎么动态求 lca?

维护 LCT 即可。

当然也可以倍增,在启发式合并时重构倍增数组即可。

动态 lca 模板:DYNALCA - Dynamic LCA

不会写 LCT 了,悲。

[HNOI2015] 接水果

树套树和整体二分我都不会啊?只能写无脑莫队了。

查 kth 直接值域分块。

注意:在一条链的两个点都被加进来时,这条链的权值才能算进来。

没了。

upd1:补题的时候 20 分钟写完,真的很好写啊,吊打同学写的整体二分了,还得调半天。

upd2:唐,这个莫队的复杂度错完了,竟然能让我草过去,大家还是写正解整体二分吧。

upd3:莫队真了。

刚才那个莫队假的原因是一个点上会挂一堆路径,你这一个点遍历多次就死掉了,复杂度变成

所以我们把一个点展开成多个点(每挂一条路径就多一个点),这样新序列长度是 的。

然后把询问的区间对应到这个新序列上,再进行莫队,复杂度就真了。

代码以后补。

upd4:莫队又死了。

因为树上莫队需要考虑 ,所以可以构造 被拆成一堆点,那么你每次还是得跑这一堆点,就死了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include"bits/stdc++.h"
#define re register
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
const int maxn=4e4+10,maxm=210;
int n,p,m,cnt,tim,siz,tot;
int a[maxn];
int head[maxn],seq[maxn<<1],ans[maxn];
int st[maxm],ed[maxm],bel[maxn];
int cnt1[maxn],cnt2[maxm];
vector<pii> g[maxn];
bool vis[maxn];
struct edge{
int to,nxt;
}e[maxn<<1];
struct node{
int l,r;
int fa,siz,top,hson,dep;
}t[maxn];
struct Query{
int l,r,k,id,lca;
}q[maxn];
inline bool cmp(Query a,Query b){return ((a.l/siz)^(b.l/siz))?(a.l/siz)<(b.l/siz):((a.l/siz)&1)?a.r<b.r:a.r>b.r;}
inline void add(int u,int v){
e[++cnt]={v,head[u]};
head[u]=cnt;
}
inline void dfs1(int u,int fa){
t[u].l=++tim;seq[tim]=u;
t[u].fa=fa,t[u].siz=1,t[u].dep=t[fa].dep+1;
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue;
dfs1(v,u);
t[u].siz+=t[v].siz;
if(!t[u].hson||t[v].siz>t[t[u].hson].siz) t[u].hson=v;
}
t[u].r=++tim;seq[tim]=u;
}
inline void dfs2(int u,int top){
t[u].top=top;
if(!t[u].hson) return;
dfs2(t[u].hson,top);
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==t[u].fa||v==t[u].hson) continue;
dfs2(v,v);
}
}
inline int lca(int u,int v){
while(t[u].top!=t[v].top){
if(t[t[u].top].dep<t[t[v].top].dep) swap(u,v);
u=t[t[u].top].fa;
}
return t[u].dep<t[v].dep?u:v;
}
inline void init(){
siz=sqrt(p);
tot=p/siz;
for(re int i=1;i<=tot;++i) st[i]=(i-1)*siz+1,ed[i]=i*siz;
if(ed[tot]<n) ++tot,st[tot]=ed[tot-1]+1,ed[tot]=p;
for(re int i=1;i<=tot;++i) for(re int j=st[i];j<=ed[i];++j) bel[j]=i;
}
inline void add(int x){++cnt1[x],++cnt2[bel[x]];}
inline void del(int x){--cnt1[x],--cnt2[bel[x]];}
inline int query(int k){
for(re int i=1;i<=tot;++i){
if(k-cnt2[i]<=0){
for(re int j=st[i];j<=ed[i];++j){
if(k-cnt1[j]<=0) return a[j];
k-=cnt1[j];
}
}
k-=cnt2[i];
}
return -1;
}
inline void work(int u){
if(vis[u]){for(auto x:g[u]) if(vis[x.fi]) del(x.se);}
else{for(auto x:g[u]) if(vis[x.fi]) add(x.se);}
vis[u]^=1;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>p>>m;
for(re int i=1,u,v;i<n;++i) cin>>u>>v,add(u,v),add(v,u);
dfs1(1,0),dfs2(1,1);
for(re int i=1,u,v,w;i<=p;++i) cin>>u>>v>>w,g[u].push_back({v,w}),g[v].push_back({u,w}),a[i]=w;
sort(a+1,a+p+1);
p=unique(a+1,a+p+1)-(a+1);
for(re int u=1;u<=n;++u) for(auto &x:g[u]) x.se=lower_bound(a+1,a+p+1,x.se)-a;
for(re int i=1,u,v,k,lc;i<=m;++i){
cin>>u>>v>>k;
if(t[u].l>t[v].l) swap(u,v);
lc=lca(u,v);
if(lc==u) q[i]={t[u].l,t[v].l,k,i,0};
else q[i]={t[u].r,t[v].l,k,i,lc};
}
siz=sqrt(n<<1);
sort(q+1,q+m+1,cmp);
init();
int l=1,r=0;
for(re int i=1;i<=m;++i){
while(r<q[i].r) work(seq[++r]);
while(l>q[i].l) work(seq[--l]);
while(r>q[i].r) work(seq[r--]);
while(l<q[i].l) work(seq[l++]);
if(q[i].lca) work(q[i].lca);
ans[q[i].id]=query(q[i].k);
if(q[i].lca) work(q[i].lca);
}
for(re int i=1;i<=m;++i) cout<<ans[i]<<'\n';
return 0;
}

[SHUPC 2024] 原神,启动!

表示第 个被攻击的次数,那么列出方程应该长这样

然后高斯消元解就完事了。

注意是在模意义下解高斯消元,所以除法要注意。