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]);
}