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