Codeforces Round #749

还我Rating

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

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

\(\tt RNM\),退钱!!!

F. Defender of Childhood Dreams

题目描述

点此看题

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

\(2\leq k<n\leq 1000\)

解法

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

那么我们只需要证明颜色数的下界就是 \(\lceil\log_k n\rceil\) 即可,这等价于证明颜色数 \(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\) 个这样的集合,所以最大点数是 \(k\cdot 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\n",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\)

\(1\leq n\leq 2\times 10^5,1\leq a_i<b_i\leq 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\n",ans);
}

H. Omkar and Tours

题目描述

点此看题

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

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

\(2\leq n\leq 2\times 10^5,1\leq q\leq 2\times 10^5\)

解法

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

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

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

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

本题适用第二种情况,一个关键的 \(\tt 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(n\log 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\n",hp[i],ct[i]);
}
posted @ 2021-10-20 21:05  C202044zxy  阅读(142)  评论(0编辑  收藏  举报