• @4 UOJ386 & UOJ388 & UOJ390


    鸽子固定器

    题目描述

    点此看题

    解法

    考虑这样一种暴力的想法:我们把物品按照 \(s\) 排序,枚举 \(\max s\),然后扫描 \(\min s\),过程中维护前 \(m\) 大的 \(v\)

    考虑优化这个暴力,对于选取个数 \(<m\) 的情况,一定选取了一段连续的区间,暴力枚举所有这样的区间即可,那么我们只需要考虑选取个数恰好 \(=m\) 的情况。

    关键的 \(\tt observation\) 是:\(\min s\) 减小的目的是为了达到 \(v\) 的替换。以前的暴力是显式地让 \(\min s\) 减少,不妨切换枚举主体,枚举的过程变为显式地替换 \(v\)

    所以我们把所有物品按照 \(v\) 排序,从小到大地删除物品。设现在删除的物品为 \(x\),我们只需要取出 \(x\) 的前面 \(m-1\) 个物品 \(/\) 后面 \(m-1\) 个物品,枚举长度为 \(m\) 的区间计算,这代表了替换 \(x\) 的过程。

    用链表实现数的删除和取出前驱后继,时间复杂度 \(O(nm)\)

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 200005;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,A,B,ans,s[M],l[M],r[M],b[M],id[M];
    struct node{int x,y;}a[M];
    int get(int x,int y) {return y==1?x:x*x;}
    int calc(int a,int b) {return get(a,A)-get(b,B);}
    signed main()
    {
    	n=read();m=read();B=read();A=read();
    	for(int i=1;i<=n;i++)
    		a[i].x=read(),a[i].y=read();
    	sort(a+1,a+1+n,[&](node a,node b){return a.x<b.x;});
    	for(int i=1;i<=n;i++)
    		s[i]=s[i-1]+a[i].y,id[i]=i;
    	for(int l=1;l<=m;l++)
    		for(int i=1;i+l-1<=n;i++)
    			ans=max(ans,calc(s[i+l-1]-s[i-1],a[i+l-1].x-a[i].x));
    	sort(id+1,id+1+n,[&](int u,int v){return a[u].y<a[v].y;});
    	for(int i=1;i<n;i++) r[i]=i+1,l[i+1]=i;
    	for(int i=1;i<=n;i++)
    	{
    		int u=id[i],t=0;
    		for(int j=1,p=l[u];j<m && p;j++,p=l[p])
    			b[++t]=p;
    		reverse(b+1,b+1+t);
    		for(int j=1,p=r[u];j<m && p;j++,p=r[p])
    			b[++t]=p;
    		for(int j=1;j<=t;j++) s[j]=s[j-1]+a[b[j]].y;
    		for(int j=1;j+m-1<=t;j++)
    			ans=max(ans,calc(s[j+m-1]-s[j-1],a[b[j+m-1]].x-a[b[j]].x));
    		r[l[u]]=r[u];
    		l[r[u]]=l[u];
    	}
    	printf("%lld\n",ans);
    }
    

    配对树

    题目描述

    点此看题

    解法

    匹配的策略是很清晰的,因为我们想让匹配两点的 \(\tt lca\) 越深越好。进一步分析,我们自底向上地来做,子树内的点是能匹配则匹配,一个子树给父亲的未匹配点至多有一个。

    高妙的算贡献方式:考虑每一条边,这一条边会贡献当且仅当这条边链接的子树里有奇数个选出的点。那么问题转化成对每一条边,有多少个长度为偶数的区间使得子树内有奇数个区间内的点。

    对于一棵子树,把在子树中的点设置为 \(1\),不在子树中的点设置为 \(0\),问题转化成区间和为奇数的偶区间有多少个。写出条件,其实就是:\(i=j\bmod 2,s[j]-s[i]=1\bmod 2\)

    考虑分奇偶位置维护,统计 \(s\)\(0/1\) 的个数,就可以很方便地计算答案。推广到多棵子树的情况,我们维护一棵差分标记的线段树,按照题目给的序列初始化,做线段树合并即可,时间复杂度 \(O(n\log n)\)

    总结

    注意贡献的主体,比如本题如果计算 \(\tt lca\) 处的贡献是不好做的。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 100005;
    const int N = 20*M;
    const int MOD = 998244353;
    #define ll long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,ans,tot,F[M],rt[M];
    int cnt,ls[N],rs[N],f[N][2][2],s[N];
    struct edge{int v,c,next;}e[M<<1];
    void up(int x,int l,int r)
    {
    	s[x]=s[ls[x]]+s[rs[x]];
    	int k=s[ls[x]]&1,mid=(l+r)>>1;
    	for(int i=0;i<2;i++)
    		for(int j=0;j<2;j++)
    			f[x][i][j]=f[ls[x]][i][j]+
    			f[rs[x]][i^k][j];
    	if(!ls[x]) f[x][0][0]+=mid/2-(l-1)/2,
    		f[x][0][1]+=(mid+1)/2-l/2;
    	if(!rs[x]) f[x][k][0]+=r/2-mid/2,
    		f[x][k][1]+=(r+1)/2-(mid+1)/2;
    }
    void ins(int &x,int l,int r,int p)
    {
    	if(!x) x=++cnt;
    	f[x][0][0]=r/2-(l-1)/2;
    	f[x][0][1]=(r+1)/2-l/2;
    	if(l==r) {s[x]++;return ;}
    	int mid=(l+r)>>1;
    	if(mid>=p) ins(ls[x],l,mid,p);
    	else ins(rs[x],mid+1,r,p);
    	up(x,l,r);
    }
    int merge(int x,int y,int l,int r)
    {
    	if(!x || !y) return x+y;
    	int mid=(l+r)>>1;
    	ls[x]=merge(ls[x],ls[y],l,mid);
    	rs[x]=merge(rs[x],rs[y],mid+1,r);
    	up(x,l,r);return x;
    }
    void dfs(int u,int fa)
    {
    	for(int i=F[u];i;i=e[i].next)
    	{
    		int v=e[i].v,c=e[i].c;
    		if(v==fa) continue;
    		dfs(v,u);
    		ans=(ans+(ll)f[rt[v]][0][0]*
    		f[rt[v]][1][0]%MOD*c)%MOD;
    		ans=(ans+(ll)f[rt[v]][0][1]*
    		f[rt[v]][1][1]%MOD*c)%MOD;
    		rt[u]=merge(rt[u],rt[v],1,m+1);
    	}
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read(),c=read();
    		e[++tot]=edge{v,c,F[u]},F[u]=tot;
    		e[++tot]=edge{u,c,F[v]},F[v]=tot;
    	}
    	for(int i=1;i<=m;i++)
    		ins(rt[read()],1,m+1,i);
    	dfs(1,0);
    	printf("%d\n",ans);
    }
    

    百鸽笼

    题目描述

    点此看题

    解法

    主动使用容斥,假设现在我们要让 \(i\) 留到最后,枚举集合 \(S\) 都在 \(i\) 之后消失,那么答案是:

    \[\sum_{S} (-1)^{|S|}\cdot g(i,S) \]

    其中 \(g(i,S)\) 表示集合 \(S\) 都在 \(i\) 之后消失的概率,由于容斥的特性,我们不需要考虑 \(S\cup \{i\}\) 以外的元素,所以每一步选取的概率都是固定的 \(\frac{1}{|S|+1}\),设选取总数为 \(L\),那么总概率是 \(\frac{1}{(|S|+1)^L}\)

    现在就变得很好做了,设 \(f(i,j)\) 表示 \(S\) 的大小是 \(i\)\(L=j\) 的容斥系数。加入一个元素时,讨论这个元素是否出现在集合 \(S\) 中,已经被选取了多少个(小于 \(a_x\) 个),暴力做时间复杂度 \(O(n^6)\)

    发现可以退背包,时间复杂度 \(O(n^5)\)

    #include <cstdio>
    const int M = 35;
    const int N = 1005;
    const int MOD = 998244353;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,a[M],f[M][N],C[N][N];
    void add(int &x,int y) {x=(x+y)%MOD;}
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    int inv(int x) {return qkpow(x,MOD-2);}
    void ins(int n,int m,int k)
    {
    	for(int i=n-1;i>=0;i--)
    		for(int j=m;j>=0;j--) if(f[i][j])
    			for(int t=0;t<k;t++)
    				add(f[i+1][j+t],MOD-f[i][j]*C[j+t][t]%MOD);
    }
    void del(int n,int m,int k)
    {
    	for(int i=0;i<n;i++)
    		for(int j=0;j<=m;j++) if(f[i][j])
    			for(int t=0;t<k;t++)
    				add(f[i+1][j+t],f[i][j]*C[j+t][t]%MOD);
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    		m+=(a[i]=read());
    	for(int i=0;i<N;i++)
    	{
    		C[i][0]=1;
    		for(int j=1;j<=i;j++)
    			C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
    	}
    	f[0][0]=1;
    	for(int i=1,s=0;i<=n;i++)
    		ins(i,s,a[i]),s+=a[i];
    	for(int i=1;i<=n;i++)
    	{
    		del(n,m,a[i]);int r=0;
    		for(int j=0;j<n;j++)
    			for(int k=0;k<=m-a[i];k++) if(f[j][k])
    				add(r,f[j][k]*qkpow(inv(j+1),k+a[i])
    				%MOD*C[k+a[i]-1][a[i]-1]);
    		printf("%lld ",(r+MOD)%MOD);
    		ins(n-1,m-a[i],a[i]);
    	}
    }
    
  • 相关阅读:
    Python的包管理工具Pip
    [Reactive Programming] RxJS dynamic behavior
    [Reactive Programming] Using an event stream of double clicks -- buffer()
    [RxJS + AngularJS] Sync Requests with RxJS and Angular
    [Javascript] An Introduction to JSPM (JavaScript Package Manager)
    [Angular 2] 9. Replace ng-modle with #ref & events
    [React] Intro to inline styles in React components
    [AngualrJS + Webpack] Production Source Maps
    [AngularJS + Webpack] Uglifying your JavaScript
    [AngularJS + Webpack] Production Setup
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16475708.html
Copyright © 2020-2023  润新知