“蔚来杯“2022牛客暑期多校训练营5

A Don’t Starve

题意:

给定一个二维平面,从原点(0, 0)出发,地图中存在N个点,每个点坐标保证不同,每个点上都有1单位物品,每次离开当前点,物品会重新刷新。要求每次移动距离严格递减,求最多能拿到多少物品。
1 < = N < = 2000 , − 20000 < = x [ i ] , y [ i ] < = 20000 1 <= N <= 2000, -20000 <= x[i], y[i] <= 20000 1<=N<=2000,20000<=x[i],y[i]<=20000

思路:

由于每个点可重复到达。最开始一直以为类似Link with Level Editor I分层图进行dp转移。但是后来发现,不像。

一直以为是BFS。一共才2000 * 2000条边,且以为最多拿2000次。。没想到MLE。。(答案太大了)

注意到每次移动距离要严格递减。那么可对所有路线距离进行从大到小排序。可满足拓扑结构。

排序后,两点距离较大的点会去更新两点距离较小的点。(可尝试画图)

那么每次需要处理长度相同的段进行dp转移。(路径距离严格递减)

f[i][j]:从i号点走到j号点的拿到物品的集合。

属性:max

S[i]:走到i号点所拿物品的最大值。

f [ i ] [ j ] = m a x ( f [ i ] [ j ] , S [ i ] + 1 ) f[i][j] = max(f[i][j], S[i] + 1) f[i][j]=max(f[i][j],S[i]+1)

那么总的时间复杂度O(n ^ 2 logn)

code:

ll x[maxn], y[maxn], f[maxn][maxn], s[maxn];
struct note{
	int u;
	int v;
	ll w;
	bool operator <(const note &a) const{
		return w > a.w;
	}
};
int main()
{
	int N;
	scanf("%d", &N);
	bool flog = false;
	vector <PII> alls;
	alls.push_back({0, 0});
	for(int i = 1 ; i <= N ; i ++)
	{	
		scanf("%lld %lld", &x[i], &y[i]);
		if(x[i] == 0 && y[i] == 0) flog = true;
		else
		{
			alls.push_back({x[i], y[i]});
		}
	}
	for(int i = 0 ; i < alls.size() ; i ++)
	{
		s[i] = -2e18; //多少点 
		for(int j = 0; j < alls.size() ; j ++)
		f[i][j] = -2e18;
	}
	if(flog) f[0][0] = 1, s[0] = 1;
	else f[0][0] = 0, s[0] = 0;
	vector <note> edge;
	for(int i = 0 ; i < alls.size() ; i ++)
	{
		for(int j = 1 ; j < alls.size() ; j ++)
		{
			if(i != j)
			{
				edge.push_back({i,j, (alls[i].first - alls[j].first) * (alls[i].first - alls[j].first) + (alls[i].second - alls[j].second) * (alls[i].second - alls[j].second)});
			}
		}
	}
	sort(edge.begin(), edge.end());
	ll res = 0;
	for(int i = 0 ; i < edge.size() ; i ++)
	{
		int j = i;
		ll w = edge[i].w;
		vector <int> temp;
		while(j < edge.size() && edge[j].w == w) temp.push_back(j), j ++;
		for(auto t : temp)
		{
			f[edge[t].u][edge[t].v] = max(f[edge[t].u][edge[t].v], s[edge[t].u] + 1);
			res = max(res, f[edge[t].u][edge[t].v]);
		}
		for(auto t : temp)
		s[edge[t].v] = max(s[edge[t].v], f[edge[t].u][edge[t].v]);
		i = j - 1; 
	}
	printf("%lld\n",res);
	return 0;
}

D Birds in the tree

题意:

给定一棵树,n个节点。每个节点都有一个权值0 / 1,求图中存在多少连通点集中满足叶子节点(度数为1)权值相同。对结果 % 1e9 + 7。
$ 1 <= n <= 3e5$

思路:

个人感觉树上取模计算方案数,多半为DP。

发现度数为1的点,有可能是叶子节点,也有可能是根节点

因为之前根节点没考虑,导致样例老是多算。。

发现可能存在节点本身权值为0,但是连通点集中度数为1的点权值为1。
在这里插入图片描述
也会存在节点本身权值为1,连通点集中度数为1的点权值为1。
在这里插入图片描述
f[i][0 / 1]:以i号点为根的子树中,连通点集且叶子节点为0 / 1的集合

属性:max

f [ i ] [ 0 ] = ∏ s o n ( f [ s o n ] + 1 ) f[i][0] = \prod \limits_{son} (f[son] + 1) f[i][0]=son(f[son]+1)

code:

har str[maxn];
int h[maxn], ne[maxn * 2], e[maxn * 2], idx;
ll f[maxn][3];
const ll mod = 1e9 + 7;
ll ans;
ll get_mod1(ll a, ll b)
{
	return (a + b) % mod;
}
ll get_mod2(ll a, ll b)
{
	return (a % mod * (b % mod)) % mod;
}
void add(int u, int v)
{
	e[idx] = v;
	ne[idx] = h[u];
	h[u] = idx ++;	
} 
void dfs(int sta, int fa)
{
	f[sta][0] = f[sta][1] = 1;
	ll sum1 = 0, sum0 = 0;  //子节点 
	for(int i = h[sta] ; i != -1 ; i = ne[i])
	{
		int son = e[i];
		if(son == fa) continue;
		dfs(son, sta);
		f[sta][1] = get_mod2(f[sta][1], f[son][1] + 1); //选son节点   + 不选 
		sum1 = get_mod1(sum1, f[son][1]);
		f[sta][0] = get_mod2(f[sta][0], f[son][0] + 1);
		sum0 = get_mod1(sum0, f[son][0]);
	} 
	if(str[sta] == '1')
	{
		f[sta][0] = (f[sta][0] - 1 + mod) % mod;
		ans = get_mod1(ans, get_mod1(f[sta][1], (f[sta][0] - sum0 + mod) % mod));
	}
	else
	{
		f[sta][1] = (f[sta][1] - 1 + mod) % mod; // - 孤立点 - 选一个节点的方案 
		ans = get_mod1(ans, get_mod1(f[sta][0], (f[sta][1] - sum1 + mod) % mod));	
	}
}
int main()
{
	int n;
	scanf("%d", &n);
	scanf("%s", str + 1);
	memset(h, -1, sizeof(h)), idx = 0;
	for(int i = 1 ; i < n ; i ++)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		add(u, v), add(v, u);
	}
	dfs(1, -1);
//	for(int i = 1 ; i <= n ; i ++)
//	printf("%d %lld %lld\n", i, f[i][0], f[i][1]); 
	printf("%lld\n", ans);
	return 0;	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值