zoukankan      html  css  js  c++  java
  • Ynoi 杂题选做

    拒绝根号,拥抱 ( ext{polylog})

    P6108 [Ynoi2009] rprsvq

    [ ext{res}=sum_{emptysub Ssubseteq[l,r]}frac{1}{|S|}sum_{a_iin S}(a_i-frac{1}{|S|}sum_{a_iin S}a_i)^2\ =sum_{emptysub Ssubseteq[l,r]}frac{1}{|S|}(sum_{a_iin S}a_i^2-sum_{a_iin S}frac{2a_i}{|S|}sum_{a_iin S}a_i+frac{1}{|S|}(sum_{a_iin S}a_i)^2)\ =sum_{emptysub Ssubseteq[l,r]}frac{1}{|S|}(sum_{a_iin S}a_i^2-frac{1}{|S|}(sum_{a_iin S}a_i)^2)\ = ext{res1}- ext{res2}\ ]

    为啥我只会卷卷卷来维护啊。

    [ ext{res1}=sum_{a_iin[l,r]}sum_{j=0}^{r-l}inom{r-l}{j}frac{a_i^2}{j+1}\ =sum_{j=0}^{r-l}inom{r-l}{j}frac{1}{j+1}sum_{a_iin[l,r]}a_i^2\ =frac{1}{r-l+1}sum_{j=1}^{r-l+1}inom{r-l+1}{j}sum_{a_iin[l,r]}a_i^2\ =frac{1}{r-l+1}(2^{r-l+1}-1)sum_{a_iin[l,r]}a_i^2\ ]

    [ ext{res2}=sum_{emptysub Ssubseteq[l,r]}(frac{1}{|S|}sum_{a_iin S}a_i)^2\ =sum_{a_iin[l,r]}a_isum_{{a_i}subseteq Ssubseteq[l,r]}frac{1}{|S|^2}sum_{a_jin S}a_j\ =sum_{a_iin[l,r]}a_isum_{j=0}^{r-l}frac{1}{(j+1)^2}(inom{r-l}{j}a_i+sum_{a_kin([l,r]-{a_i})}inom{r-l-1}{j-1}a_k)\ =sum_{a_iin[l,r]}a_isum_{j=0}^{r-l}frac{1}{(j+1)^2}(inom{r-l}{j}a_i+sum_{a_kin([l,r]-{a_i})}(inom{r-l}{j}-inom{r-l-1}{j})a_k)\ =sum_{a_iin[l,r]}a_isum_{j=0}^{r-l}frac{1}{j+1}(sum_{a_kin[l,r]}frac{a_k}{r-l+1}inom{r-l+1}{j+1}-sum_{a_kin([l,r]-{a_i})}frac{a_k}{r-l}inom{r-l}{j+1})\ = ext{res3}- ext{res4}\ ]

    [ ext{res3}=frac{1}{r-l+1}(sum_{a_iin[l,r]}a_i)^2sum_{j=0}^{r-l}frac{1}{j+1}inom{r-l+1}{j+1}\ ]

    [ ext{res4}=sum_{a_iin[l,r]}a_isum_{j=0}^{r-l}frac{1}{j+1}sum_{a_kin([l,r]-{a_i})}frac{a_k}{r-l}inom{r-l}{j+1}\ =sum_{a_iin[l,r]}a_isum_{j=0}^{r-l}frac{1}{j+1}(sum_{a_kin[l,r]}frac{a_k}{r-l}inom{r-l}{j+1}-frac{a_i}{r-l}inom{r-l}{j+1})\ = ext{res5}- ext{res6}\ ]

    [ ext{res5}=frac{1}{r-l}(sum_{a_iin[l,r]}a_i)^2sum_{j=0}^{r-l}frac{1}{j+1}inom{r-l}{j+1}\ ]

    [ ext{res6}=frac{1}{r-l}sum_{a_iin[l,r]}a_i^2sum_{j=0}^{r-l}frac{1}{j+1}inom{r-l}{j+1}\ ]

    发现我们需要计算这个式子。

    [f(n)=sum_{i=1}^nfrac{1}{i}inom{n}{i}\ f(n+1)=sum_{i=1}^{n+1}frac{1}{i}inom{n+1}{i}\ f(n+1)-f(n)=sum_{i=1}^nfrac{1}{i}inom{n}{i-1}+frac{1}{n+1}inom{n+1}{n+1}\ =frac{1}{n+1}sum_{i=1}^ninom{n+1}{i}+frac{1}{n+1}inom{n+1}{n+1}\ =frac{1}{n+1}sum_{i=1}^{n+1}inom{n+1}{i}\ =frac{2^{n+1}-1}{n+1}\ ]

    哇哦,做完了啊!!!

    #include<bits/stdc++.h>
    using namespace std;
    const int N=5e6+5;
    const int MOD=998244353;
    int ADD(int x,int y){return x+y>=MOD?x+y-MOD:x+y;}
    int TIME(int x,int y){return (int)(1ll*x*y%MOD);}
    int ksm(int x,int k=MOD-2){int res=1;for(;k;k>>=1,x=TIME(x,x))if(k&1)res=TIME(res,x);return res;}
    int n,m,f[N];
    int C[3][3]={{1,1,1,},{1,2,3},{1,3,6}};
    int fact[N],ifact[N],ksm2[N];
    int inv(int x){return TIME(ifact[x],fact[x-1]);}
    struct Data{int f[3];int &operator [](int x){return f[x];}};
    Data operator * (Data f,Data g){
    	Data res;
    	memset(res.f,0,sizeof(res.f));
    	for(int i=0;i<=2;++i){
    		for(int j=0;j<=2-i;++j)
    		res[i+j]=ADD(res[i+j],TIME(C[i][j],TIME(f[i],g[j])));
    	}
    	return res;
    }
    Data operator + (Data f,Data g){
    	Data res;
    	memset(res.f,0,sizeof(res.f));
    	for(int i=0;i<=2;++i) res[i]=ADD(f[i],g[i]);
    	return res;
    }
    Data fuck(int x){
    	Data res;
    	res[0]=1,res[1]=x,res[2]=TIME(x,x);
    	return res;
    }
    struct Seg_Tree{
    	struct Node{Data f;int tag;}tr[N<<2];
    	void up(int u){tr[u].f=tr[u<<1].f+tr[u<<1|1].f;}
    	void update(int u,int z){tr[u].f=tr[u].f*fuck(z),tr[u].tag=ADD(tr[u].tag,z);}
    	void down(int u){update(u<<1,tr[u].tag),update(u<<1|1,tr[u].tag),tr[u].tag=0;}
    	void build(int u,int l,int r){
    		if(l==r) return tr[u].f=fuck(0),void();
    		int mid=(l+r)>>1;build(u<<1,l,mid),build(u<<1|1,mid+1,r),up(u);
    	}
    	void modify(int u,int l,int r,int x,int y,int z){
    		if(x<=l&&r<=y) return update(u,z);
    		int mid=(l+r)>>1;down(u);
    		if(x<=mid) modify(u<<1,l,mid,x,y,z);
    		if(y>mid) modify(u<<1|1,mid+1,r,x,y,z);
    		return up(u);
    	}
    	Data query(int u,int l,int r,int x,int y){
    		if(x<=l&&r<=y) return tr[u].f;
    		int mid=(l+r)>>1;Data res1,res2;down(u);
    		if(x<=mid) res1=query(u<<1,l,mid,x,y);
    		if(y>mid) res2=query(u<<1|1,mid+1,r,x,y);
    		if(x<=mid&&y>mid) return res1+res2;
    		return x<=mid?res1:res2;
    	}
    }t;
    int main(){
    	cin>>n>>m;
    	ksm2[0]=1;for(int i=1;i<=n;++i) ksm2[i]=ADD(ksm2[i-1],ksm2[i-1]);
    	fact[0]=1;for(int i=1;i<=n;++i) fact[i]=TIME(fact[i-1],i);
    	ifact[n]=ksm(fact[n]);for(int i=n;i>=1;--i) ifact[i-1]=TIME(ifact[i],i);
    	for(int i=1;i<=n;++i) f[i]=ADD(f[i-1],TIME(ksm2[i]-1,inv(i)));
    	t.build(1,1,n);
    	for(int i=1;i<=m;++i){
    		int opt,l,r,x;
    		scanf("%d%d%d",&opt,&l,&r);
    		if(opt==2){
    			Data tmp=t.query(1,1,n,l,r);int res=0;
    			res=ADD(res,TIME(TIME(ksm2[r-l+1]-1,inv(r-l+1)),tmp[2]));
    			res=ADD(res,MOD-TIME(TIME(f[r-l+1],inv(r-l+1)),TIME(tmp[1],tmp.f[1])));
    			res=ADD(res,TIME(TIME(f[r-l],inv(r-l)),TIME(tmp[1],tmp[1])));
    			res=ADD(res,MOD-TIME(TIME(f[r-l],inv(r-l)),tmp[2]));
    			printf("%d
    ",res);
    		}
    		else scanf("%d",&x),t.modify(1,1,n,l,r,x);
    	}
    	return 0;
    }
    

    P7880 [Ynoi2006] rldcot

    首先如果你被这个 ( ext{dep}) 骗了(像我一样)去搞一些关于树上距离的做法,那你是没有前途的。

    题意转化了之后就是区间虚树上点的颜色个数。

    你考虑数颜色的题有一个单 (log_2n) 的做法,需要维护每一个颜色最后出现的位置,我们这里就可以这样维护。

    那么对于一个点,其会被算在颜色数中要么他就是这段区间的数,要么他的两棵子树中存在点。

    我们考虑我们答案的更新实际上只需要找到每一个点在这个 ( ext{lca}) 上的非同一子树的编号最近点即可。实际上我们可以暴力维护这个过程,利用启发式合并的复杂度分析可以知道是 (O(nlog_2^2n)) 的。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+5,M=5e5+5;
    int n,m;long long c[N];vector<long long> col;
    struct Edge{int nxt,to,val;}e[N<<1];int fir[N];
    void add(int u,int v,int w,int i){e[i]=(Edge){fir[u],v,w},fir[u]=i;}
    struct Query{int l,r,id;}a[M];bool cmp(Query a,Query b){return a.r<b.r;}
    vector<pair<int,int> > opt[N];set<int> bag[N];
    int get_id(long long c){return lower_bound(col.begin(),col.end(),c)-col.begin()+1;}
    void merge(int c,int u,int v){
    	if(bag[u].size()<bag[v].size()) swap(bag[u],bag[v]);
    	for(set<int>::iterator i=bag[v].begin(),j;i!=bag[v].end();++i){
    		j=bag[u].upper_bound(*i);
    		if(j!=bag[u].end()) opt[*j].push_back(make_pair(c,*i));
    		j=bag[u].lower_bound(*i);
    		if(j!=bag[u].begin()) j--,opt[*i].push_back(make_pair(c,*j));
    	}
    	while(!bag[v].empty()){
    		bag[u].insert(*bag[v].begin());
    		bag[v].erase(bag[v].begin());
    	}
    }
    void dfs1(int u,int fa){
    	for(int i=fir[u];i;i=e[i].nxt){
    		int v=e[i].to;if(v==fa) continue;
    		c[v]=c[u]+e[i].val,dfs1(v,u);
    	}
    }
    void dfs2(int u,int fa){
    	for(int i=fir[u];i;i=e[i].nxt){
    		int v=e[i].to;if(v!=fa) dfs2(v,u);
    	}
    	bag[u].insert(u);int tmp=get_id(c[u]);
    	opt[u].push_back(make_pair(tmp,u));
    	for(int i=fir[u];i;i=e[i].nxt){
    		int v=e[i].to;if(v!=fa) merge(tmp,u,v);
    	}
    }
    struct Tree_Array{
    	int tr[N];
    	int lowbit(int k){return k&(-k);}
    	void add(int k,int x){for(;k<=n;k+=lowbit(k))tr[k]+=x;}
    	int sum(int k){int res=0;for(;k;k-=lowbit(k))res+=tr[k];return res;}
    }t;
    int pos[N],res[M];
    int main(){
    	cin>>n>>m;
    	for(int i=1;i<n;++i){
    		int u,v,w;scanf("%d%d%d",&u,&v,&w);
    		add(u,v,w,i<<1),add(v,u,w,i<<1|1);
    	}
    	for(int i=1;i<=m;++i) scanf("%d%d",&a[i].l,&a[i].r),a[i].id=i;
    	sort(a+1,a+1+m,cmp),dfs1(1,0);
    	for(int i=1;i<=n;++i) col.push_back(c[i]);
    	sort(col.begin(),col.end());
    	col.erase(unique(col.begin(),col.end()),col.end());
    	dfs2(1,0);int R=0;
    	for(int i=1;i<=m;++i){
    		while(R<a[i].r){
    			++R;
    			for(int j=0;j<(int)opt[R].size();++j){
    				pair<int,int> tmp=opt[R][j];
    				if(pos[tmp.first]) t.add(pos[tmp.first],-1);
    				pos[tmp.first]=max(pos[tmp.first],tmp.second);
    				t.add(pos[tmp.first],1);
    			}
    		}
    		res[a[i].id]=t.sum(a[i].r)-t.sum(a[i].l-1);
    	}
    	for(int i=1;i<=m;++i) printf("%d
    ",res[i]);
    	return 0;
    }
    /*
    这是什么毒瘤题。
    原本想从 O(1) lca 的角度去思考,但是发现边权可以是负的。
    等一下,如果存在两个点 dep 相同但是点不相同,能否意味着还存在一个 lca ?
    好像不太行,因为可能两个点的 lca 的深度是和他们相同的。
    --------------------------------------------------------------
    或者是我们考虑每一个 lca 的贡献。
    但是由于是编号上的询问,我们根本不好搞。
    根据不同的深度建虚树?这样会丢失掉点标号的信息。
    -------------------------------------------------------------
    偷偷看了下题解第一段,意识到自己题目转化错了,是求区间虚树的颜色数。我们再来尝试做
    做看。
    既然是区间的虚树数颜色,首先每一个点自己会产生贡献。然后他会与区间中的点组合然后再
    lca 处做贡献。
    首先感觉会有莫队的思路,但是发现我们动态虚树维护是带一个log,所以我们不考虑。
    然后数颜色还有一种线段树的搞法,我们考虑这里可不可以实现。
    考虑线段树上记录的是当前右端点每一个颜色最后出现的位置。
    然后考虑右端点向右移动一位的话,实际上可能会增加很多的位置,这是并不合算的。
    我们从单独的 lca 出发,该点的颜色最后一个的位置应该就是倒数第二个的子树。
    ------------------------------------------------------------------------
    思路方向有点对了,但是不知道怎么处理。
    实际上每一个点只需要找到其在其祖先中的前驱即可。
    我们考虑这个东西的复杂度是什么?实际上我们容易想到树上启发式合并。
    然后我们实际上寻找这个前驱的过程可以在启发式合并的过程中搞?
    每个点在插入的时候找到前驱后继,更新即可。
    NB啊,这是什么神仙题。
    */
    

    P7897 [Ynoi2006] spxmcq

    首先我们考虑如果单次询问怎么做,非常轻松的可以得到一个 ( ext{dp}) 式子。

    [f_{u}=a_u+x+sum_vmax(f_v,0) ]

    我们考虑怎么多次算这个式子。

    实际上我们发现,随着 (x) 的增加,(max(f_v,0)) 的取值是单调的,这就启发我们维护一个森林,其中若 (f_vge 0)(e_{u,v}) 连边,这样实际上每一个点的答案就很简单了:

    [f_u=a_u+x+sum_vf_v ]

    我们此时再来考虑如何得知一个点他此时大于 (0) 了,实际上我们只需要判断其下方所有与之相连的点的权值和与点数乘 (x) 的大小关系即可。然后我们可以利用并查集和优先队列维护这个连边的过程。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e6+5,Q=1e6+5;
    int n,q;
    struct DSU{
    	int fa[N];
    	void init(int n){for(int i=1;i<=n;++i)fa[i]=i;}
    	int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
    }d;
    struct Node{long long siz,sum;int id;}tr[N];
    bool operator > (Node a,Node b){return (-a.sum)*b.siz>(-b.sum)*a.siz;}
    bool operator != (Node a,Node b){return a.siz!=b.siz||a.sum!=b.sum;}
    struct Query{int u,x,id;}a[Q];
    bool cmp(Query a,Query b){return a.x<b.x;}
    long long res[Q];
    priority_queue<Node,vector<Node>,greater<Node> > pq;
    struct Tree_Array{
    	long long tr[N];
    	int lowbit(int k){return k&(-k);}
    	void add(int k,long long x){for(;k&&k<=n;k+=lowbit(k))tr[k]+=x;}
    	long long sum(int k){long long res=0;for(;k;k-=lowbit(k))res+=tr[k];return res;}
    }t1,t2;
    int fa[N],L[N],R[N],cnt_dfn;
    struct Edge{int nxt,to;}e[N];int fir[N];
    void add(int u,int v,int i){e[i]=(Edge){fir[u],v},fir[u]=i;}
    void dfs(int u){
    	L[u]=++cnt_dfn;
    	for(int i=fir[u];i;i=e[i].nxt) dfs(e[i].to);
    	R[u]=cnt_dfn;
    }
    int main(){
    	cin>>n>>q,d.init(n);
    	for(int i=2;i<=n;++i) scanf("%d",&fa[i]),add(fa[i],i,i);
    	for(int i=1;i<=n;++i) scanf("%lld",&tr[i].sum),tr[i].siz=1,tr[i].id=i;
    	for(int i=1;i<=q;++i) scanf("%d%d",&a[i].u,&a[i].x),a[i].id=i;
    	sort(a+1,a+1+q,cmp),dfs(1);
    	for(int i=1;i<=n;++i){
    		t1.add(L[i],tr[i].siz),t1.add(L[fa[i]],-tr[i].siz);
    		t2.add(L[i],tr[i].sum),t2.add(L[fa[i]],-tr[i].sum);
    	}
    	for(int i=1;i<=n;++i) pq.push(tr[i]);
    	for(int i=1;i<=q;++i){
    		while(!pq.empty()){
    			if(pq.top()!=tr[pq.top().id]){pq.pop();continue;}
    			if(pq.top().sum+pq.top().siz*a[i].x<0) break;
    			Node u=pq.top();pq.pop();
    			if(fa[u.id]){
    				int fu=d.find(fa[u.id]),fv=d.find(u.id);d.fa[fv]=fu;
    				tr[fu].siz+=tr[fv].siz,tr[fu].sum+=tr[fv].sum,pq.push(tr[fu]);
    				t1.add(L[fa[fv]],tr[fv].siz),t1.add(L[fa[fu]],-tr[fv].siz);
    				t2.add(L[fa[fv]],tr[fv].sum),t2.add(L[fa[fu]],-tr[fv].sum);
    			}
    		}
    		long long tmp1=t1.sum(R[a[i].u])-t1.sum(L[a[i].u]-1);
    		long long tmp2=t2.sum(R[a[i].u])-t2.sum(L[a[i].u]-1);
    		res[a[i].id]=a[i].x*tmp1+tmp2;
    	}
    	for(int i=1;i<=q;++i) printf("%lld
    ",res[i]);
    	return 0;
    }
    /*
    我们首先考虑没有修改的单次询问怎么做。
    首先如果是一个区间序列的话,实际上就是一个贪心的过程。
    我们意识到还有一个在线段树上的做法。
    我们考虑这个线段树上的做法。由于是要强制包含子树的根节点的,实际上就是所有子树的
    答案加起来加上自己的权值。这本质上和区间的做法是一样的,
    我们再考虑修改怎么搞,我想考虑搞一个关于 x 的分段函数。
    膜拜 dpair ,数据结构之神!!!!
    我们考虑到每一个点的贡献实际上就是一段斜线,我们考虑转化成差分数组之后线段树合并。
    但是我是值域线段树,不会搞了。
    ------------------------------------------------------------------------
    我们考虑离线之后按 x 排序,然后对于 $max(f_v,0)$ 这个转移实际上取值是单调的,我
    们就考虑暴力维护每一个点的转移时间,我们考虑到这与其 siz 和 sum 有关。
    但是注意到,已经连接的我们已经不需要维护了,我们只需要维护最上面的一个点的 siz 和
    sum 即可,所以暴力用并查集来搞就行了。
    */
    

    P6018 [Ynoi2010] Fusion tree

    首先这题有一个 ( ext{trick}) ,就是每一个节点只统计孩子的贡献,然后父亲的贡献是可以直接算的。

    我们就考虑如何维护孩子权值的异或和,同时需要进行全局加 (1)

    你会意识到全局加一在二进制下就是最低位的 (0) 变成 (1) ,然后比其低的 (1) 变成 (0) 。我们发现如果我们倒建 ( ext{trie}) 树是可以轻松维护的。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=5e5+5;
    int n,m,a[N],b[N],fa[N];
    struct Edge{int nxt,to;}e[N<<1];int fir[N];
    void add(int u,int v,int i){e[i]=(Edge){fir[u],v},fir[u]=i;}
    void dfs(int u){
    	for(int i=fir[u];i;i=e[i].nxt)
    	if(e[i].to!=fa[u]) fa[e[i].to]=u,dfs(e[i].to);
    }
    struct Trie{
    	int rt[N],tot;
    	struct Node{int data,siz,son[2];}tr[N*22];
    	void up(int u,int k){
    		tr[u].data=(tr[tr[u].son[0]].data^tr[tr[u].son[1]].data);
    		tr[u].data|=(1<<k)*tr[tr[u].son[1]].siz;
    	}
    	void insert(int &u,int k,int x){
    		if(!u) u=++tot;
    		tr[u].siz^=1;if(k>=20) return ;
    		insert(tr[u].son[(x>>k)&1],k+1,x),up(u,k);
    	}
    	void opt(int u,int k){
    		if(k>=20) return ;
    		swap(tr[u].son[0],tr[u].son[1]);
    		opt(tr[u].son[0],k+1),up(u,k);
    	}
    }t;
    int main(){
    	cin>>n>>m;
    	for(int i=1;i<n;++i){
    		int u,v;scanf("%d%d",&u,&v);
    		add(u,v,i<<1),add(v,u,i<<1|1);
    	}
    	dfs(1);
    	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    	for(int i=1;i<=n;++i) if(fa[i]) t.insert(t.rt[fa[i]],0,a[i]);
    	for(int i=1;i<=m;++i){
    		int opt,u;scanf("%d%d",&opt,&u);
    		if(opt==2){
    			int x;scanf("%d",&x);
    			if(fa[u]) t.insert(t.rt[fa[u]],0,a[u]+b[fa[u]]);
    			a[u]-=x;if(fa[u]) t.insert(t.rt[fa[u]],0,a[u]+b[fa[u]]);
    		}
    		else if(opt==1){
    			if(fa[u]){
    				if(fa[fa[u]]) t.insert(t.rt[fa[fa[u]]],0,a[fa[u]]+b[fa[fa[u]]]);
    				a[fa[u]]++;if(fa[fa[u]]) t.insert(t.rt[fa[fa[u]]],0,a[fa[u]]+b[fa[fa[u]]]);
    			}
    			b[u]++,t.opt(t.rt[u],0);
    		}
    		else printf("%d
    ",t.tr[t.rt[u]].data^(a[fa[u]]+b[fa[fa[u]]]));
    	}
    }
    /*
    距离为 $1$ 的点的权值异或和。这是啥。。。
    我们能否考虑只维护异或和?感觉有点不太可行。
    --------------------------------------------------------------------------
    你考虑每一次加一,实际上是在干嘛,就是将最低位的 $0$ 变成 $1$ 然后比其低的 $1$ 变
    成 $0$ 。你发现这个东西我们可以建一个反向 $trie$ 来维护,然后做完了。
    */
    
  • 相关阅读:
    用户态和内核态
    Spring Cloud构建微服务架构:服务网关(路由配置)【Dalston版】
    为什么说分布式事务不再适用于微服务架构
    基于selenium的二次开发
    Python常用方法
    深入浅出runloader
    python socket
    python API接口测试框架
    python装饰器
    python多进程安全
  • 原文地址:https://www.cnblogs.com/Point-King/p/15501020.html
Copyright © 2011-2022 走看看