Codeforces Round #749

还我Rating

我已经暴怒了,神(^{ t TM})前六道都是构造题,我真的受够了!!!

再也不拿大号打 ( t Div1+Div2) 了,下次我打 ( t Div1) 直接杀穿,吊打小 ( t T) 做梦不是问题。

( t RNM),退钱!!!

F. Defender of Childhood Dreams

题目描述

点此看题

(n) 个点,对于 (a<b) 都有一条有向边 ((a,b)),现在要对边染色,问最少的颜色数使得不存在一条长度为 (k) 的边同色路径,并给出构造方案。

(2leq k<nleq 1000)

解法

我们递归的构造,首先我们把 (1sim n) 排成一个序列,然后划分成 (k) 个长度相等的连续段(或者尽可能相近),那么段间的边都用一种颜色 (x),然后递归下去的子段都不用颜色 (x) 了。构造方案合法,因为最长的同色路径是跨段的 (k-1),并且使用的颜色数为 (lceillog _k n ceil)

那么我们只需要证明颜色数的下界就是 (lceillog_k n ceil) 即可,这等价于证明颜色数 (c) 能承受的最大点数是 (k^c)

直接上归纳法,(0) 个颜色能承受的最大点数是 (1),假设 (c-1) 种颜色能够承受的最大点数是 (k^{c-1})

我们取颜色 (c),对还未染色的原图任意染色,那么得到集合 (s_0,s_1,s_2..s_{k-1})(s_i) 表示从它开始存在长度为 (i)(c) 同色路径,那么每个集合内部是没有 (c) 边的,可以知道最大点数是 (k^{c-1}),因为一共有 (k) 个这样的集合,所以最大点数是 (kcdot k^{c-1}=k^c)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1005;
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,a[M][M];
signed main()
{
	n=read();k=read();
	for(int i=0;i<n;i++)
		for(int j=i+1;j<n;j++)
		{
			int u=i,v=j;
			while(u!=v)
			{
				u/=k,v/=k;
				a[i][j]++;
			}
			m=max(m,a[i][j]);
		}
	printf("%d
",m);
	for(int i=0;i<n;i++)
		for(int j=i+1;j<n;j++)
			printf("%d ",a[i][j]);
}

G. Omkar and Time Travel

题目描述

点此看题

(n) 个传送门 ((a_i,b_i)),如果你走到 (b_i) 并且这个传送门处于开放状态,你会立刻传送到 (a_i),当前传送门关闭,并且所有 (a_i<a_j) 的传送门 (j) 都会立即变为开放状态。

给定 (t) 个数表示一个集合,问让这个集合所有的传送门变成关闭状态的最小传送次数,答案模 (1e9+7)

(1leq nleq 2 imes 10^5,1leq a_i<b_ileq 2n),保证 (a_i,b_i) 互不相同。

解法

本来我想设计状态表示点亮一段前缀的花费,但是不方便求答案而且难以转移。

询问是给定了一个集合,这提示你要把贡献放在单点上,可以设 (dp[i]) 表示点亮 (i) 的花费。

那么现在还剩下两个问题:哪些点需要贡献?(dp) 如何转移?对于第一个问题,首先在集合中的点是要贡献的,再者若 (a_i<a_j,b_i<b_j) 并且 (j) 在集合中,(i) 是要产生贡献的,因为点亮 (j) 在逻辑上需要 (i) 被点亮

第二个问题如何转移?首先考虑按什么顺序,因为点亮这个点需要消除一段后缀,在逻辑上需要这段后缀被点亮,如果满足 (a_i<a_j,b_j<b_i) 那么点亮 (i) 时就需要 (j) 被点亮,那么 (dp_i=sum dp_j+1)

为什么我上文一再强调“在逻辑上”,因为这题是按照 (a_i) 的偏序关系做的,而不是按照时间顺序做的。

总结

考虑答案的形式对于 (dp) 也很重要,这题答案显然是对若干个单点提出了要求,所以我们要求单点的贡献,算贡献是把所有点独立开来,你可以在思想上让其他点为这个点而服务。

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 400005;
const int MOD = 1e9+7;
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,a[M],b[M],p[M],dp[M],f[M],vis[M],suf[M];
int cmp(int x,int y) {return a[x]<a[y];}
int lowbit(int x)
{
	return x&(-x);
}
void ins(int x,int c)
{
	for(int i=x;i<=2*n;i+=lowbit(i))
		f[i]=(f[i]+c)%MOD;
}
int ask(int x)
{
	int res=0;
	for(int i=x;i>=1;i-=lowbit(i))
		res=(res+f[i])%MOD;
	return res;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),b[i]=read(),p[i]=i;
	m=read();
	for(int i=1;i<=m;i++)
		vis[read()]=1;
	//
	sort(p+1,p+1+n,cmp);
	for(int i=n;i>=1;i--)
	{
		if(vis[p[i]]) suf[i]=b[p[i]];
		suf[i]=max(suf[i],suf[i+1]);
	}
	for(int i=1;i<=n;i++)
		if(suf[i]>b[p[i]]) vis[p[i]]=1;
	//
	for(int i=n;i>=1;i--)
	{
		dp[i]=ask(b[p[i]])+1;
		ins(b[p[i]],dp[i]);
		if(vis[p[i]]) ans=(ans+dp[i])%MOD;
	}
	printf("%d
",ans);
}

H. Omkar and Tours

题目描述

点此看题

有一棵 (n) 个点的树,每个点有一个快乐度 (a_i),每条边有一个容量 (c_i) 花费 (t_i)

(q) 次询问,每个有一个车辆 (v) 的车队从 (x) 出发,他们只能经过 (c_igeq v) 的边。定义路径的花费为路径上所有边花费的最大值,求能到达点的最大快乐值 和 所有到最快乐点的简单路径花费的最大值。

(2leq nleq 2 imes 10^5,1leq qleq 2 imes 10^5)

解法

本来自己想了一个辣鸡 (O(nlog^2 n)) 的重口味做法,不足为外人道也。

可以把所有询问离线,那么动态维护连通块就可以解决 (cgeq v) 的限制。

如果所有点的快乐值两两不同的话,我们可以记录连通块内最大的快乐值和这个点,然后直接求到这个点树上路径的最大值即可,变成了一个倍增板子。

回到原始问题上来,因为存在多条路径,但是感性理解一下这些路径是有很大重复的。这里想提出名为最值放缩的技巧:第一种情况是可以把某些不优的情况混入最大值去求,可以去掉多余的判断;第二种情况是可以对一个元素多次求最大值,只要最后把所有元素都考虑到即可。

本题适用第二种情况,一个关键的 ( t observation) 是:我们取一个最快乐点 (a),求出任意 (m)(a) 的路径花费最大值,和 (x)(a) 的花费最大值,那么 (x) 到任意 (m) 的最大值就是这两者取 (max)

你可以把它理解成我们先走到 (a),然后再走到任意点 (m),花费最大值是不变的。

那么可以维护 (best[u]) 表示以 (u) 为根的连通块中两两最快乐点间的路径最大花费,那么在合并的时候如果遇到最大快乐值相等的情况,(best[u]=max(best[u],best[v],w(a,b))),其中 (a,b) 分别表示 (u,v) 中任意的最快乐点。

所以只需要打一个倍增即可,时间复杂度 (O(nlog n))

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
#define pii pair<int,int>
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],id[M],fa[M],p[M][20],mx[M][20];
int tot,dep[M],f[M],hp[M],ct[M],bst[M];
struct edge
{
	int v,c,next;
}e[2*M];
struct node
{
	int u,v,x;
	bool operator < (const node &b) const
	{
		return x>b.x;
	}
}s[M],q[M];
int find(int x)
{
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
void dfs(int u,int fa)
{
	p[u][0]=fa;
	dep[u]=dep[fa]+1;
	for(int i=1;i<20;i++)
	{
		int t=p[u][i-1];
		p[u][i]=p[t][i-1];
		mx[u][i]=max(mx[u][i-1],mx[t][i-1]);
	}
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v,c=e[i].c;
		if(v==fa) continue;
		mx[v][0]=c;
		dfs(v,u);
	}
}
int lca(int u,int v)
{
	int r=0;
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19;i>=0;i--)
		if(dep[p[u][i]]>=dep[v])
		{
			r=max(r,mx[u][i]);
			u=p[u][i];
		}
	if(u==v) return r;
	for(int i=19;i>=0;i--)
		if(p[u][i]^p[v][i])
		{
			r=max(r,mx[u][i]);
			r=max(r,mx[v][i]);
			u=p[u][i];v=p[v][i];
		}
	r=max(r,mx[u][0]);
	r=max(r,mx[v][0]);
	return r;
}
void add(int u,int v)
{
	u=find(u);v=find(v);
	fa[v]=u;
	if(a[u]>a[v]) return ;
	if(a[u]<a[v])
	{
		a[u]=a[v];
		id[u]=id[v];
		bst[u]=bst[v];
		return ;
	}
	bst[u]=max(bst[u],bst[v]);
	bst[u]=max(bst[u],lca(id[u],id[v]));
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),id[i]=fa[i]=i;
	for(int i=1;i<n;i++)
	{
		s[i].u=read();s[i].v=read();
		s[i].x=read();
		int u=s[i].u,v=s[i].v,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++)
	{
		q[i].x=read();
		q[i].u=read();
		q[i].v=i;
	}
	dfs(1,0);
	sort(s+1,s+n);
	sort(q+1,q+1+m);
	for(int i=1,j=1;i<=m;i++)//process each query
	{
		while(j<n && s[j].x>=q[i].x)
		{
			add(s[j].u,s[j].v);
			j++;
		}
		int rt=find(q[i].u);
		hp[q[i].v]=a[rt];
		ct[q[i].v]=max(bst[rt],lca(id[rt],q[i].u));
	}
	for(int i=1;i<=m;i++)
		printf("%d %d
",hp[i],ct[i]);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15430766.html