[BJOI2016]IP地址
数据结构题。
没啥好说的。难点在于想到打懒惰标记。
简单说一下吧。设 f i f_i fi表示节点 i i i的变化次数。下传标记的充要条件是子节点不含插入字符串。
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
int n,m,cnt;
int trie[100005*64][2],tot,res[100005],op[100005];
int f[100005*64],tag[100005*64],v[100005*64];
string s[100005];
struct node{
string s;
int x,id,op;
bool operator <(const node &a)const {
return x<a.x;
}
}q[200005];
void ins(string s){
int it=0;
for(int i=0;i<s.size();i++){
if(!trie[it][s[i]-'0'])trie[it][s[i]-'0']=++tot;
it=trie[it][s[i]-'0'];
}
}
void pushdown(int it){
if(!it)return;
int l=trie[it][0],r=trie[it][1];
if(tag[it]){
if(!v[l])tag[l]+=tag[it],f[l]+=tag[it];
if(!v[r])tag[r]+=tag[it],f[r]+=tag[it];
tag[it]=0;
}
}
void upd(string s){
int it=0;
for(int i=0;i<s.size();i++){
pushdown(it);
if(!(it=trie[it][s[i]-'0']))return;
}f[it]++,tag[it]++,v[it]^=1;
}
int qry(string s){
int it=0;
for(int i=0;i<s.size();i++){
pushdown(it),it=trie[it][s[i]-'0'];
}return f[it];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
string tmp;
cin>>tmp>>s[i],op[i]=(tmp=="Add")?1:-1;
}
for(int i=1;i<=m;i++){
string tmp;int l,r;
cin>>tmp>>l>>r,ins(tmp);
q[++cnt]={tmp,r,i,1},q[++cnt]={tmp,l,i,-1};
}sort(q+1,q+1+cnt);int j=1;
for(int i=1;i<=cnt;i++){
while(j<=q[i].x)upd(s[j]),j++;
res[q[i].id]+=q[i].op*qry(q[i].s);
}for(int i=1;i<=m;i++)cout<<res[i]<<"\n";
}
[SCOI2016]背单词
首先有一个基本的事实: t r i e trie trie树上的祖先节点一定最先选择。
考虑自底向上合并。注意到按子树大小从小到大排序即可。答案是每一步合并操作的花费,也就是各节点代价之和。
#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pb push_back
using namespace std;
int n,trie[510005][26],vs[510005],sz[510005],tot,num;
ll res;
vector<int>ve[510005];
void ins(string s){
int it=0;
for(int i=0;i<s.size();i++){
if(!trie[it][s[i]-'a'])trie[it][s[i]-'a']=++tot;
it=trie[it][s[i]-'a'];
}vs[it]=sz[it]=1;
}void dfs(int u,int topf){
for(int i=0;i<26;i++){
int v=trie[u][i];
if(v){
dfs(v,vs[u]?u:topf),sz[u]+=sz[v];
}
}
if(vs[u]){
sort(ve[u].begin(),ve[u].end());
int tmp=1;
for(int i=0;i<ve[u].size();i++){
res+=tmp,tmp+=ve[u][i];
}ve[topf].pb(sz[u]);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
string tmp;cin>>tmp,reverse(tmp.begin(),tmp.end()),ins(tmp);
} vs[0]=1,dfs(0,0),cout<<res;
}
[Ynoi2011] 竞赛实验班
考察一个子问题。
给定一个序列 { ( a 1 ⊕ x ) ⊕ y , ( a 2 ⊕ x ) ⊕ y , . . . , ( a n ⊕ x ) ⊕ y } \{(a_1\oplus x)\oplus y,(a_2\oplus x)\oplus y,...,(a_n\oplus x)\oplus y\} {(a1⊕x)⊕y,(a2⊕x)⊕y,...,(an⊕x)⊕y},其中前一部分的 a i ⊕ x a_i\oplus x ai⊕x是有序的,问你区间 [ l , r ] [l,r] [l,r]的元素值之和。
对于有序的部分,在 trie \text{trie} trie树上二分 a i ⊕ x a_i\oplus x ai⊕x对应的有序元素中的 [ 1 , r ] [1,r] [1,r],通过记录每一位 0 0 0, 1 1 1的个数就能把异或后的和求出来。
对于无序的部分直接前缀和即可。
每次排序操作相当于把无序的序列合并到有序的序列上面去。
比较蛋疼的地方在于这样 trie \text{trie} trie树上还要统计每个子树中每一位 0 0 0, 1 1 1的数目,这样空间复杂度 O ( n log 2 n ) O(n\log ^2n) O(nlog2n)无法通过。
注意到对于只有一个儿子的节点并不需要存储长度为 26 26 26的数组,这样每次加入时最多只会增加 1 1 1个节点,这样空间少一个 log \log log。
注意到这道题可以离线,那么我们每次单独考虑某一位,这样空间就是线性的了。
码码码。。。
我信仰什么,我便实现哪种方法。
#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pb push_back
using namespace std;
const int N=6200005;
const int M=200005;
int n,m,cnt;
int trie[N][2],tot,ok;
int a[M];
int d1,d2,s;
int v1,v2,sz2[N][31];
ll sz[M][31];
ll sz1[N];
void ins(int x){
int it=0;
for(int i=30;i>=0;i--){
int k=x>>i&1;
if(!trie[it][k])trie[it][k]=++tot;
it=trie[it][k],sz1[it]++;
for(int j=0;j<=30;j++)sz2[it][j]+=(x>>j&1);
}
}
void ins2(int x){
a[++v2]=x^d1^d2;
for(int i=0;i<=30;i++)sz[v2][i]=sz[v2-1][i]+(a[v2]>>i&1);
}
ll qry(int x){
int it=0;ll num=0,tmp=0;
for(int i=30;i>=0;i--){
int k=d1>>i&1;
if(sz1[trie[it][k]]>=x){
it=trie[it][k],tmp+=k*(1ll<<i);
}
else {
for(int j=0;j<=30;j++){
if((d1^d2)>>j&1)num+=(1ll<<j)*(sz1[trie[it][k]]-sz2[trie[it][k]][j]);
else num+=(1ll<<j)*sz2[trie[it][k]][j];
}
x-=sz1[trie[it][k]],it=trie[it][k^1],tmp+=(k^1)*(1ll<<i);
}
}for(int i=0;i<=30;i++){
if(((d1^d2)>>i&1)^(tmp>>i&1))num+=(1ll<<i)*x;
}
return num;
}
ll qry2(int x){
ll res=0;
for(int i=0;i<=30;i++){
if((d1^d2)>>i&1){
res+=(1ll<<i)*(x-sz[x][i]);
}
else {
res+=(1ll<<i)*sz[x][i];
}
}
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;for(int i=1;i<=n;i++){
int x;cin>>x,ins2(x);
}cin>>m;
for(int i=1;i<=m;i++){
int op,l,r,x;
cin>>op;
if(op==1){
cin>>x,ins2(x);
}
else if(op==2){
cin>>l>>r;ll res=0;
if(r<=v1)res=qry(r)-qry(l-1);
else if(l>v1)res=qry2(r-v1)-qry2(l-v1-1);
else res=qry2(r-v1)+qry(v1)-qry(l-1);
cout<<res<<"\n";
}
else if(op==3){
cin>>x,d2^=x;
}
else {
for(int j=1;j<=v2;j++)ins(a[j]);
v1+=v2,v2=0,d1^=d2,d2=0;
}
}
}
[UOJ176]新年的繁荣
巧妙的生成树题目。
首先对于相同的权值可以看成一个点。
然后考虑 kruskal \text{kruskal} kruskal算法。从大到小枚举边权 v a l = ( 0110...0 ) 2 val=(0110...0)_2 val=(0110...0)2,注意到能连的边不会超过 m m m条,那么我们对于每个 0 0 0的位置(假设第 i i i位)上找到一个点 u u u满足 ( u , v a l ∣ 2 i ) a n d = v a l ∣ 2 i (u,val|2^i)_{and}=val|2^i (u,val∣2i)and=val∣2i,然后把这些点连起来就行了。
怎么理解上述过程呢,注意到对于每个 0 0 0的位置(假设第 i i i位)如果 ( u , 2 i ) a n d ≠ 0 (u,2^i)_{and}\ne 0 (u,2i)and=0 并且 ( v , 2 i ) a n d ≠ 0 (v,2^i)_{and}\ne 0 (v,2i)and=0那么显然 ( u , v ) a n d > v a l (u,v)_{and}>val (u,v)and>val,事实上我们可以把这些位置分成若干组,其中每一组是封闭且联通的(与其他组的与运算都恰好是 v a l val val),那么边权 v a l val val的数目就是组数 − 1 -1 −1。
复杂度 O ( 2 m m ) O(2^mm) O(2mm)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m;
int fa[1<<18],v[1<<18],v2[1<<18];
ll res;
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++){
int x;cin>>x;
if(!v2[x])v[x]=x,v2[x]=1;
else res+=x;
}
for(int i=(1<<m)-1;i>0;i--){
for(int j=0;j<m;j++){
if(!(i>>j&1)&&v[i|(1<<j)]){
v[i]=v[i|(1<<j)];
break;
}
}
}for(int i=1;i<(1<<m);i++)fa[i]=i;
for(int i=(1<<m)-1;i>0;i--){
int u=-1;if(v2[i])u=i;
for(int j=0;j<m;j++){
if(!(i>>j&1)&&v[i+(1<<j)]){
int v2=v[i+(1<<j)];
if(u==-1)u=v2;
else if(find(u)!=find(v2)){
fa[fa[v2]]=fa[u],res+=i;
}
}
}
}cout<<res;
}
CF888G Xor-MST
考虑在 trie \text{trie} trie树上自底向上合并的过程。
暴力合并即可。复杂度 O ( n log n log A ) O(n\log n\log A) O(nlognlogA)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,a[200005],sz[6000005],ed[6000005];
int trie[6000005][2],tot=1,tmp;
ll res;
void ins(int x){
int it=1;
for(int i=29;i>=0;i--){
ed[it]=i;
int k=x>>i&1;if(!trie[it][k])trie[it][k]=++tot;
it=trie[it][k],sz[it]++;
}
}
void dfs2(int u,int v,int w){
if(ed[u]==-1){tmp=min(tmp,w);return;}
if(trie[u][0]){
if(trie[v][0])dfs2(trie[u][0],trie[v][0],w);
else if(trie[v][1])dfs2(trie[u][0],trie[v][1],w+(1<<ed[u]));
}
if(trie[u][1]){
if(trie[v][1])dfs2(trie[u][1],trie[v][1],w);
else if(trie[v][0])dfs2(trie[u][1],trie[v][0],w+(1<<ed[u]));
}
}
void dfs(int u){
if(!u)return;int l=trie[u][0],r=trie[u][1];
dfs(l),dfs(r);
if(l&&r){
if(sz[l]>sz[r])swap(l,r);tmp=1<<30,dfs2(l,r,1<<ed[u]),res+=tmp;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n,memset(ed,-1,sizeof ed);
for(int i=1;i<=n;i++){
int x;cin>>x,ins(x);
}dfs(1),cout<<res;
}