• AHOI 2022 题目选做


    真的是码农场,\(\tt T1\) 写两个小时,看到是道蓝题直接心态爆炸。

    但是可以拿的分还是很多,如果早上没有这么困的话草上 \(300+\) 还是有希望的。

    钥匙

    题目描述

    点此看题

    解法

    关注特殊性质 \(A\),发现可以得到若干个 \((x,y)\),表示依次经过 \(x,y\) 就会产生 \(1\) 的贡献。这个可以转化成 \(\tt dfn\) 序上的矩阵加,然后每个询问对应着一个单点查询,离线下来树状数组即可。

    可以把上面的做法拓展,考虑贡献法。举个例子,对于路径 1122112,我们拆成三个点对的贡献:\((2,3),(1,4),(6,7)\);所以把它看作括号匹配是可以完美地处理贡献的,因为这样无论在前面或者后面添加什么东西,匹配点对都是不变的。也就是有贡献的匹配点对和询问没有关系,怎么样询问贡献的点对都是那些。

    如何处理出匹配点对呢?利用 同一种的钥匙最多只有5把 的关键性质,我们把同种颜色的钥匙和宝箱建成虚树,然后以每个钥匙为起点 \(\tt dfs\),把钥匙看成 \(1\),宝箱看成 \(-1\),第一个权值为 \(0\) 的点就是这个钥匙匹配的宝箱,找到即可回溯。

    时间复杂度 \(O(n\log n)\),看起来很长实际上随便打。

    彩蛋

    在实现这道题的时候,我和 \(rainybunny\) 发生了这样的对话:

    rainybunny:卧槽,这题还要建双向边。

    我:建双向边不是有手就行?

    几分钟后.....

    我:草,我虚树没有建双向边,改了他妈直接过样例。

    #include <cstdio>
    #include <vector>
    #include <cassert>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 1000005;
    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;
    }
    void write(int x)
    {
    	if(x>=10) write(x/10);
    	putchar(x%10+'0');
    }
    int n,m,k,a[M],b[M],fa[M][20],dfn[M],dep[M];
    int t,rt,Ind,out[M],ans[M],X[M],Y[M];
    vector<int> g[M],z[M],G[M];
    struct node
    {
    	int x,l,r,f;
    	bool operator < (const node &b) const
    		{return x<b.x;}
    }s[M*10],q[M];
    void dfs(int u)
    {
    	dfn[u]=++k;X[u]=++Ind;
    	dep[u]=dep[fa[u][0]]+1;
    	for(int i=1;i<20;i++)
    		fa[u][i]=fa[fa[u][i-1]][i-1];
    	for(int v:g[u]) if(v^fa[u][0])
    		fa[v][0]=u,dfs(v);
    	out[u]=k;Y[u]=++Ind;
    }
    void ins(int lx,int rx,int ly,int ry)
    {
    	if(lx>rx || ly>ry) return ;
    	s[++t]={lx,ly,ry,1};
    	s[++t]={rx+1,ly,ry,-1};
    }
    void add(int x,int c)
    {
    	for(int i=x;i<=n;i+=i&(-i)) b[i]+=c;
    }
    int ask(int x)
    {
    	int r=0;
    	for(int i=x;i>0;i-=i&(-i)) r+=b[i];
    	return r;
    }
    int find(int u,int v)
    {
    	for(int i=19;i>=0;i--)
    		if(dep[fa[u][i]]>dep[v])
    			u=fa[u][i];
    	return u;
    }
    void zxy(int u,int v)
    {
    	int lu=dfn[u],ru=out[u];
    	int lv=dfn[v],rv=out[v];
    	if(lu<=lv && lv<=ru)//u is a ancestor of v
    	{
    		int x=find(v,u);
    		ins(1,dfn[x]-1,lv,rv);
    		ins(out[x]+1,n,lv,rv);
    	}
    	else if(lv<=lu && lu<=rv)
    	{
    		int x=find(u,v);
    		ins(lu,ru,1,dfn[x]-1);
    		ins(lu,ru,out[x]+1,n);
    	}
    	else ins(lu,ru,lv,rv);
    }
    int cmp(int a,int b)
    {
    	int t1=a>0?X[a]:Y[-a];
    	int t2=b>0?X[b]:Y[-b];
    	return t1<t2;
    }
    int lca(int u,int v)
    {
    	if(dep[u]<dep[v]) swap(u,v);
    	for(int i=19;i>=0;i--)
    		if(dep[fa[u][i]]>=dep[v])
    			u=fa[u][i];
    	if(u==v) return u;
    	for(int i=19;i>=0;i--)
    		if(fa[u][i]^fa[v][i])
    			u=fa[u][i],v=fa[v][i];
    	return fa[u][0];
    }
    void get(int u,int fa,int d)
    {
    	if(b[u]==b[rt])
    	{
    		if(a[u]==1) d++;
    		if(a[u]==2)
    		{
    			d--;
    			if(d==0) zxy(rt,u);
    			if(d<=0) return ;
    		}
    	}
    	for(int v:G[u]) if(v^fa)
    		get(v,u,d);
    }
    signed main()
    {
    	//freopen("keys.in","r",stdin);
    	//freopen("keys.out","w",stdout);
    	n=read();m=read();
    	for(int i=1;i<=n;i++)
    		a[i]=read(),b[i]=read();
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read();
    		g[u].push_back(v);
    		g[v].push_back(u);
    	}
    	dfs(1);
    	for(int i=1;i<=n;i++)
    		z[b[i]].push_back(i);
    	for(int i=1;i<=n;i++) if(!z[i].empty())
    	{
    		static int A[M]={},vis[M]={},s[M]={};
    		int k=0,k2=0,tp=0;
    		for(int x:z[i]) A[++k]=x,vis[x]=1;
    		sort(A+1,A+1+k,cmp);k2=k;
    		for(int i=1;i<k2;i++)
    		{
    			int x=lca(A[i],A[i+1]);
    			if(!vis[x]) vis[x]=1,A[++k]=x;
    		}
    		if(!vis[1]) A[++k]=1,vis[1]=1;k2=k;
    		for(int i=1;i<=k2;i++) A[++k]=-A[i];
    		sort(A+1,A+1+k,cmp);
    		for(int i=1;i<=k;i++)
    		{
    			if(A[i]>0) s[++tp]=A[i];
    			else
    			{
    				int t=s[tp--];
    				if(t==1) break;
    				G[s[tp]].push_back(t);
    				G[t].push_back(s[tp]);
    			}
    		}
    		for(int x:z[i]) if(a[x]==1)
    			rt=x,get(x,0,0);
    		for(int i=1;i<=k;i++) if(A[i]>0)
    			vis[A[i]]=0,G[A[i]].clear();
    	}
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read();
    		q[i]=node{dfn[u],dfn[v],0,i};
    	}
    	sort(q+1,q+1+m);
    	sort(s+1,s+1+t);
    	for(int i=1;i<=n;i++) b[i]=0;
    	for(int i=1,j=1;i<=m;i++)
    	{
    		while(j<=t && s[j].x<=q[i].x)
    		{
    			add(s[j].l,s[j].f);
    			add(s[j].r+1,-s[j].f);
    			j++;
    		}
    		ans[q[i].f]=ask(q[i].l);
    	}
    	for(int i=1;i<=m;i++)
    		write(ans[i]),puts("");
    }
    

    山河重整

    题目描述

    点此看题

    解法

    考虑充要条件是:对于任意的前缀 \(i\)\([1,i]\) 内选取的数字和需要 \(\geq i\)

    \(dp[i][j]\) 表示考虑了前 \(i\) 个数的选取情况,已经覆盖到了前缀 \(j\) 的方案数。转移考虑第 \(i\) 个数选不选取,如果选取则把 \(j\leftarrow j+i\),合法状态的要求是 \(j\geq i\),可以获得 \(60\) 分的高分。

    优化可以考虑容斥,记 \(f[i]\) 表示第一次 \([1,i]\) 内选取的数字和等于 \(i\) 的方案数。那么不合法的状态就可以表示为:前 \(i\) 个数字和等于 \(i\),第 \(i+1\) 个数字强制不选取,后面的数字随意选取,那么答案是:

    \[2^n-\sum_{i=0}^{n-1} f[i]\cdot 2^{n-i-1} \]

    求出 \(f[i]\) 可以考虑第一次去重法(隶属于正难则反法),首先利用 \(O(n\sqrt n)\) 的拆分数 \(dp\) 求出不要求第一次,\([1,i]\) 内选取的数字和等于 \(i\) 的方案数,并且将他设置为 \(f[i]\) 的初始值。

    然后从小到达去重 \(i\),现在考虑用 \(f[j]\) 去重 \(f[i]\),发现多算的部分是:强制不选取 \(j+1\)\([j+2,i]\) 之内选取数字和为 \(j-i\) 的方案数。关键的 \(\tt observation\) 是:可以用于去重的 \(j\) 满足 \(j\leq \frac{i}{2}\)

    所以我们可以分治下去,每次考虑左半边对右半边的影响。可以统一地做一次 \(O(n\sqrt n)\) 的拆分数来去重。

    时间复杂度 \(T(n)=O(n\sqrt n)+T(\frac{n}{2})=O(n\sqrt n)\),做拆分数的具体方法可以参考代码实现。

    #include <cstdio>
    const int M = 500005;
    const int B = 1000;
    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,f[M],g[M],b[M];
    void add(int &x,int y) {if((x+=y)>=m) x-=m;}
    void solve(int n)
    {
    	if(n<=1) return ;solve(n>>1);//递归左边
    	for(int i=0;i<=n;i++) g[i]=0;
    	for(int i=B;i;i--)//去重的过程仍然是拆分数
    	{
    		for(int j=n;j>=i;j--)
    			g[j]=g[j-i];
    		for(int j=0;j+i*(j+2)<=n;j++)
    			add(g[j+i*(j+2)],f[j]);
    		//这里的初始化变成了添加 i 个 j+2 的数字
    		//因为要计算 [j+2,i] 内选数和为 j-i 的方案数 
    		for(int j=i;j<=n;j++)
    			add(g[j],g[j-i]);
    	}
    	for(int i=(n>>1)+1;i<=n;i++)
    		add(f[i],m-g[i]);//正难则反
    }
    int main()
    {
    	n=read();m=read();
    	for(int i=B;i;i--)
    	//此时还在增加的有 i 个数,转移就是整体加 1
    	{
    		for(int j=n;j>=i;j--) f[j]=f[j-i];f[i]=1;
    		//初始化,现在的 i 个数每个值都是 1
    		for(int j=i;j<=n;j++) add(f[j],f[j-i]);
    		//可以整体增加多次,所以做完全背包
    	}
    	f[0]=b[0]=1;solve(n);
    	for(int i=1;i<=n;i++) b[i]=b[i-1]*2%m;
    	for(int i=0;i<n;i++)
    		add(ans,1ll*f[i]*b[n-i-1]%m);
    	printf("%d\n",(b[n]+m-ans)%m);
    }
    

    回忆

    题目描述

    点此看题

    解法

    考虑调整法,初始每一条链单独走一遍,然后尽可能合并更多的链。

    直接按照 \(\tt dfs\) 的顺序贪心,我们考虑现在面对的局面是怎么样的。对于子树 \(u\),有若干条链的终点是比 \(u\) 浅的,这些链暂时不能考虑,我们记在 \(s[u]\) 集合中;有若干条链的终点是在 \(u\) 子树中的,它们可以和子树外的路径自由匹配起来,我们记录它的个数 \(f[u]\);还有子树内的若干对匹配,每一对可以减少 \(1\) 的行走数量,记录其个数为 \(p[u]\)

    考虑如何合并子树信息,对于 \(u\) 的所有儿子 \(v\)\(s\) 集合是可以直接启发式合并的(注意 \(s[v]\) 中终点深度恰好为 \(dep[u]\) 的链要取出来变成自由链)。

    然后考虑尽可能把自由链给匹配起来,记 \(sum=\sum f_v\),那么只考虑自由链的匹配数是:

    \[\min(\lfloor\frac{sum}{2}\rfloor,sum-\max f_v) \]

    但是每一对匹配可以拆分成两个自由链,所以我们可能会把 \(p[v]\) 拆开以最大化匹配数量。

    合并完子树信息之后,剩下的问题就是增加一条以 \(u\) 为起点的链,可以按照如下顺序考虑:

    • 如果 \(s[u]\) 非空,一定和最浅的一条链一并解决了。
    • 如果还存在自由边,可以把一条自由边和它合并,加入集合 \(s[u]\) 中。
    • 如果还存在匹配,把这个匹配拆分,和它合并之后形成一条自由边,另一条边加入 \(s[u]\) 中。
    • 否则直接加入 \(s[u]\) 中,增加初始答案 \(ans\)

    那么最后的答案就是 \(ans-p[1]\),时间复杂度 \(O(n\log^2n)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <set>
    using namespace std;
    const int M = 200005;
    const int inf = 0x3f3f3f3f;
    #define fi first
    #define se second
    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 T,n,m,ans,f[M],p[M],d[M],a[M];
    vector<int> g[M];multiset<int> s[M];
    void pre(int u,int fa)
    {
    	d[u]=d[fa]+1;
    	for(int v:g[u]) if(v^fa) pre(v,u);
    }
    void dfs(int u,int fa)
    {
    	f[u]=p[u]=0;
    	vector<pair<int,int>> b;
    	for(int v:g[u]) if(v^fa)
    	{
    		dfs(v,u);
    		while(!s[v].empty() && *s[v].rbegin()==d[u])
    			f[v]++,s[v].erase(--s[v].end());
    		if(s[u].size()<s[v].size()) swap(s[u],s[v]);
    		for(int x:s[v]) s[u].insert(x);
    		b.push_back({f[v],p[v]});
    	}
    	if(!b.empty())
    	{
    		int len=b.size(),sum=0;
    		sort(b.begin(),b.end());
    		for(int i=0;i+1<len;i++)
    			sum+=b[i].fi+2*b[i].se;
    		if(b[len-1].fi>=sum)
    		{
    			f[u]=b[len-1].fi-sum;
    			p[u]=sum+b[len-1].se;
    		}
    		else
    		{
    			sum=0;
    			for(int i=0;i+1<len;i++)
    				sum+=b[i].fi,p[u]+=b[i].se;
    			int d=max(0,(b[len-1].fi-sum+1)/2);
    			p[u]-=d;sum+=2*d+b[len-1].fi;
    			p[u]+=(sum>>1)+b[len-1].se;f[u]=sum&1;
    		}
    	}
    	if(a[u]<inf)
    	{
    		if(!s[u].empty())
    		{
    			if(*s[u].begin()>a[u])
    			{
    				s[u].erase(s[u].begin());
    				s[u].insert(a[u]);
    			}
    		}
    		else if(f[u])
    			f[u]--,s[u].insert(a[u]);
    		else if(p[u])
    			p[u]--,f[u]++,s[u].insert(a[u]);
    		else
    			ans++,s[u].insert(a[u]);
    	}
    }
    void work()
    {
    	n=read();m=read();ans=0;
    	for(int i=1;i<=n;i++)
    		g[i].clear(),s[i].clear(),a[i]=inf;
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read();
    		g[u].push_back(v);
    		g[v].push_back(u);
    	}
    	pre(1,0);
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read();
    		a[v]=min(a[v],d[u]);
    	}
    	dfs(1,0);
    	printf("%d\n",ans-p[1]);
    }
    int main()
    {
    	T=read();
    	while(T--) work();
    }
    
  • 相关阅读:
    shared_ptr weak_ptr boost 内存管理
    _vimrc win7 gvim
    qt 拖放
    数学小魔术 斐波那契数列
    qt4 程序 移植到 qt5
    (转)字符串匹配算法总结
    c++11
    BM 字符串匹配
    编译qt5 demo
    c++ 类库 学习资源
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16348877.html
Copyright © 2020-2023  润新知