Atcoder Grand Contest 009&010

009E Eternal Average

题目描述

点此看题

解法

本题的操作是树形结构,所以我们可以直接去考虑最后的结果而不去考虑过程。

可以把操作看成一棵 k 叉树,叶子代表初始的数。设权值为 1 的点深度是 xi,权值为 0 的点的深度是 yi,那么根节点的数一定是 kxi,此外这棵树能构造出的充要条件是:kxi+kyi=1

原题自动机 C202044zxy 提醒您,这里有一道结论一模一样的题目:To make 1

那么我们想在合法的基础上求出以 kxi 区分的方案数,这里我们尽量往数位 dp 的方向靠。主要问题是并不满足 0xi<k 的关键条件,我们尝试转化问题把这个关键条件给搞出来。

根据 1 的个数固定我们可以写出:xi=n,那么我们考虑进位之后的 xi,只要满足 xinxi=nmod(k1) 就一定对应着一个原来的 x 序列,这样我们可以很方便的用 x 去做数位 dp

根据 0 的个数固定和总和为 1 我们可以写出:1+i=1len(k1xi)=m,设 t=(k1)lenxi+1,那么做同样的转化可以得到 tmt=mmod(k1)

f[i][j][0/1] 表示考虑到第 i 位,xi=jxi=0/1 的方案数,由于我们要保证 len 才能判断上述条件,所以我们在保证 xi=1 的情况下统计满足上述条件的方案数即可,时间复杂度 O(n2k)

总结

这种操作之后除以某个数的题目通常有树形结构,通过树形结构可以导出一些结果向的结论。

本题的转化方便了数位 dp,这说明可以考虑进位之后的数列有什么性质,在原序列和新序列之间建立等价关系

#include <cstdio>
const int M = 2005;
const int MOD = 1e9+7;
#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,k,ans,f[M<<1][M][2],g[M];
signed main()
{
	n=read();m=read();k=read();
	f[0][0][0]=1;
	for(int i=1,up=(n+m-1)/(k-1);i<=up;i++)
	{
		g[0]=(f[i-1][0][0]+f[i-1][0][1])%MOD;
		for(int j=1;j<=n;j++)
			g[j]=(g[j-1]+f[i-1][j][0]+f[i-1][j][1])%MOD;
		f[i][0][0]=g[0];
		for(int j=1;j<=n;j++)
		{
			f[i][j][0]=(g[j]-g[j-1])%MOD;
			f[i][j][1]=(g[j-1]-(j-k>=0?g[j-k]:0)+MOD)%MOD;
		}
		for(int j=1;j<=n;j++)
		{
			int t=1+(k-1)*i-j;
			if(j%(k-1)==n%(k-1) && t<=m && t%(k-1)==m%(k-1))
				ans=(ans+f[i][j][1])%MOD;
		}
	}
	printf("%lld\n",ans);
}

010E Rearranging

题目描述

点此看题

解法

这种确定顺序的题,常用的方法是思考不变的相对顺序

本题我们观察到不互质的数一定不会被后手改变相对顺序,并且考虑到相对顺序通常是用图论来表示的,所以我们把不互质的数之间连一条边,这样会得到一个无向图。

因为互质数的相对顺序是可以任意改变的,先手决定的只有互质的数的相对顺序。考虑先手的功能是给无向图定向(若 ij 则表示 pi<pj,定向完一定是个拓扑图),后手的功能是给这个无向图定下字典序最大的拓扑序。

先手的定向具有固定策略,首先值最小的点一定作为第一个点,然后我们考虑和它相连边的方向都固定了。然后考虑把下一个位置优先给值更小的点 x ,发现此时我们可以把 x 当根,这是一个递归的过程:

void dfs(int u)
{
	vis[u]=1;
	for(int v=1;v<=n;v++)
		if(!vis[v] && b[u][v])
			g[u].pb(v),dfs(v);
}

我们只需要保留在 dfs 树上的边(因为非树边对顺序没有更强的限制),这种方法为什么是正确的呢?我的理解是我们让值小点的位置尽量小,就使他处在偏序关系的上层,但如果子树间无关联那么顺序是任意的。

如果有更加理性的证明请联系我,上面的文字就是我的一些感性理解

#include <cstdio>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
const int M = 2005;
#define pb push_back
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,a[M],b[M][M],vis[M];
priority_queue<int> q;vector<int> g[M],ans;
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
void dfs(int u)
{
	vis[u]=1;
	for(int v=1;v<=n;v++)
		if(!vis[v] && b[u][v])
			g[u].pb(v),dfs(v);
}
void topo()
{
	while(!q.empty())
	{
		int u=q.top();q.pop();ans.pb(u);
		for(int v:g[u]) q.push(v);
	}
}
signed main()
{
	freopen("rearranging.in","r",stdin);
	freopen("rearranging.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	sort(a+1,a+1+n);
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			b[i][j]=b[j][i]=(gcd(a[i],a[j])>1);
	for(int i=1;i<=n;i++) if(!vis[i])
		q.push(i),dfs(i);
	topo();
	for(int x:ans) printf("%d ",a[x]);puts("");
}

009D Uninity

题目描述

点此看题

解法

首先不难把题目转化为重构一棵点分树(但是并不是传统意义上的按重心划分),使得最大深度最小。因为这东西我都想得出来肯定很简单所以就不证明了,同时我们可以发现一个性质就是答案 logn(按传统点分树划分就可以得到上界)

考虑进一步转化,因为我们想在固定结构上规划。考虑点分树是可以和路径表示法互化的,那么我们可以把题意转化成给每个点定一个标号 label使得最后任意两个 label 相等的点树上路径(原树)存在点的 label 大于它。证明这个转化是很简单的,也就是考虑这两点同层,那么路径一定会过点分树的某个根,而根的 label 大于它就是合法点分树。

那么可以在原树上规划 label,设 g[u] 表示子树 u 中未解决的 label 集合状压起来的值(因为答案 logn 所以存得下 ),注意这里如果子树内某个点 vu 的路径上存在 label[x]>label[v] 就视为 v 已解决。

那么我们考虑合并子树的过程中会新增多少限制,发现就是位运算取并,我们再把取并的结果或起来就得到了总的限制。那么首先这个点的 label 必须要足够大,此外不能和子树中未解决点的 label 相同(要不然就直接不合法了)

实现就暴力增大 label,时间复杂度 O(nlogn),本题最后的做法本质上是一个树上延迟贪心模型

总结

规划问题中,把动态结构转化成固定结构可以用标号法,要求 label 可以充分描述动态结构的性质。

//The second I waste is more than I can take. 
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
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,tot,ans,f[M],g[M];
struct edge{int v,next;}e[M<<1];
void dfs(int u,int fa)
{
	int s=0,dp=0;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,u);s|=g[u]&g[v];g[u]|=g[v];
	}
	while(s) dp++,s>>=1;
	while(g[u]&(1<<dp)) dp++;
	g[u]=((g[u]>>dp)|1)<<dp;
	ans=max(ans,dp);
}
signed main()
{
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge{v,f[u]},f[u]=tot;
		e[++tot]=edge{u,f[v]},f[v]=tot;
	}
	dfs(1,0);
	printf("%d\n",ans);
}
posted @   C202044zxy  阅读(361)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示