#8 谨防越界
[HNOI/AHOI2018] 道路
令 表示根到点 的路径上有 条左边和 条右边未被标记,转移得
注意到直接转移空间会超,我们每次需要的其实只有一条链及其上的点的左儿子,这样的点的数量不超过 。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}>
}
using namespace IO;
const int N=4e4+10;
int n,ch[N][2],a[N],b[N],c[N],nxt[N][2],f[100][50][50],idx=1,dep[N];
void dfs(int u,int now){
int pre=idx;
if(u>=n){
for(int i=0;i<=dep[u];i++){
for(int j=0;j<=dep[u];j++){
f[now][i][j]=c[u]*(a[u]+i)*(b[u]+j);
}
}
}
else{
for(int i=0;i<2;i++){
nxt[u][i]=++idx;
dep[ch[u][i]]=dep[u]+1;
dfs(ch[u][i],nxt[u][i]);
}
for(int i=0;i<=dep[u];i++){
for(int j=0;j<=dep[u];j++){
f[now][i][j]=min(f[nxt[u][0]][i+1][j]+f[nxt[u][1]][i][j],f[nxt[u][1]][i][j+1]+f[nxt[u][0]][i][j]);
}
}
}
idx=pre;
}
void solve(){
read(n);
for(int i=1;i<n;i++){
int x;
read(x);
if(x<0){
x=abs(x)+n-1;
}
ch[i][0]=x;
read(x);
if(x<0){
x=abs(x)+n-1;
}
ch[i][1]=x;
}
for(int i=n;i<n*2;i++){
read(a[i]),read(b[i]),read(c[i]);
}
dfs(1,1);
write_endl(f[1][0][0]);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
升级版
在模拟赛中出现了一道题,改了题目中的一个信息,每个点不一定割且只割一条儿子边,但总共割的边的数量为 。
考虑一件事,标记的边越多,答案越小,且斜率是在逐渐变缓的,考虑使用wqs二分。对于一个斜率,如果它割函数的点的横坐标在 的左侧,该斜率在 处可能更新答案,剩下的直接dp即可。注意越界问题。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=4e4+10;
int n,ch[N][2],a[N],b[N];
ll c[N],delta;
vector<ll>sum[N];
vector<int>cnt[N];
int mxl[N],mxr[N];
int get_id(int u,int l,int r){
return l*mxr[u]+r;
}
void dfs(int u,int cntl,int cntr){
mxl[u]=cntl+1,mxr[u]=cntr+1;
sum[u].resize((cntl+1)*(cntr+1));
cnt[u].resize((cntl+1)*(cntr+1));
if(u>=n){
return;
}
dfs(ch[u][0],cntl+1,cntr);
dfs(ch[u][1],cntl,cntr+1);
}
void query(int u,int cntl,int cntr){
if(u>=n){
for(int i=0;i<=cntl;i++){
for(int j=0;j<=cntr;j++){
sum[u][get_id(u,i,j)]=cnt[u][get_id(u,i,j)]=0;
sum[u][get_id(u,i,j)]+=c[u]*(a[u]+i)*(b[u]+j);
}
}
}
else{
query(ch[u][0],cntl+1,cntr);
query(ch[u][1],cntl,cntr+1);
for(int i=0;i<=cntl;i++){
for(int j=0;j<=cntr;j++){
sum[u][get_id(u,i,j)]=cnt[u][get_id(u,i,j)]=0;
if(sum[ch[u][0]][get_id(ch[u][0],i,j)]+delta<sum[ch[u][0]][get_id(ch[u][0],i+1,j)]){
sum[u][get_id(u,i,j)]+=sum[ch[u][0]][get_id(ch[u][0],i,j)]+delta;
cnt[u][get_id(u,i,j)]+=cnt[ch[u][0]][get_id(ch[u][0],i,j)]+1;
}
else{
sum[u][get_id(u,i,j)]+=sum[ch[u][0]][get_id(ch[u][0],i+1,j)];
cnt[u][get_id(u,i,j)]+=cnt[ch[u][0]][get_id(ch[u][0],i+1,j)];
}
if(sum[ch[u][1]][get_id(ch[u][1],i,j)]+delta<sum[ch[u][1]][get_id(ch[u][1],i,j+1)]){
sum[u][get_id(u,i,j)]+=sum[ch[u][1]][get_id(ch[u][1],i,j)]+delta;
cnt[u][get_id(u,i,j)]+=cnt[ch[u][1]][get_id(ch[u][1],i,j)]+1;
}
else{
sum[u][get_id(u,i,j)]+=sum[ch[u][1]][get_id(ch[u][1],i,j+1)];
cnt[u][get_id(u,i,j)]+=cnt[ch[u][1]][get_id(ch[u][1],i,j+1)];
}
}
}
}
}
void wqs(){
ll l=0,r=1e13,ans=0;
while(l<=r){
ll mid=(l+r)/2;
delta=mid;
query(1,0,0);
if(cnt[1][get_id(1,0,0)]<=n-1){
ans=max(ans,sum[1][get_id(1,0,0)]-(n-1)*mid);
r=mid-1;
}
else{
l=mid+1;
}
}
write_endl(ans);
}
signed main(){
#ifdef luoshen
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
read(n);
for(int i=1,x;i<n;i++){
read(x);
if(x<0){
x=-x+n-1;
}
ch[i][0]=x;
read(x);
if(x<0){
x=-x+n-1;
}
ch[i][1]=x;
}
for(int i=n;i<n*2;i++){
read(a[i]),read(b[i]),read(c[i]);
}
dfs(1,0,0);
wqs();
// cerr<<1.0*clock()/CLOCKS_PER_SEC<<endl;
return 0;
}
Ciel and Flipboard
有一个简单做法,令 表示 是否翻转,因为 ,那么在确定选择哪几行后,这几行的第 列一定被选, 和 列有且只有一个被选;同理,在确定选择哪几列后,这几列的第 行一定被选, 和 行有且只有一个被选。我们可以得到 。
我们枚举第 行的第 列是否被选,因为差不等于 的两行之间是相互独立的。我们可以对于上半部分的每一行分开讨论,最后答案加起来即可。在确定每一行的第 列后,也是同样的,所以还可以对于左上这个部分的所有点分开讨论,最后答案加起来即可。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=40,inf=1e9;
int m,n,a[N][N],col[N][N];
int get_val(int x,int y){
if(col[x][y]){
return -a[x][y];
}
return a[x][y];
}
int query(int i,int j,int opt){
col[i][j]=opt;
col[i][j+m]=col[i][j]^col[i][m];
col[i+m][j]=col[i][j]^col[m][j];
col[i+m][j+m]=col[i][j]^col[i][m]^col[m][j]^col[m][m];
return get_val(i,j)+get_val(i+m,j)+get_val(i,j+m)+get_val(i+m,j+m);
}
int calc(int i,int opt){
col[m][i]=opt;
col[m][i+m]=opt^col[m][m];
int sum=get_val(m,i)+get_val(m,i+m);
for(int j=1;j<m;j++){
sum+=max(query(j,i,0),query(j,i,1));
}
return sum;
}
void solve(){
read(n);
m=(n+1)>>1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
read(a[i][j]);
}
}
int ans=-inf;
for(int S=0;S<(1<<m);S++){
for(int i=1;i<=m;i++){
col[i][m]=S>>(i-1)&1;
}
for(int i=m+1;i<=n;i++){
col[i][m]=col[i-m][m]^col[m][m];
}
int sum=0;
for(int i=1;i<=n;i++){
sum+=get_val(i,m);
}
for(int i=1;i<m;i++){
sum+=max(calc(i,0),calc(i,1));
}
ans=max(ans,sum);
}
write_endl(ans);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
Groceries in Meteor Town
这种求路径最大值的问题,一般就两种做法,重构树或者点分治。显然这题重构树更为优秀。按照边权从小到大建一棵重构树,路径上经过的最大的边就为点 与白点点集的并的点集的lca。无解很容易判,如果并出来的点集大小为 ,显然不可能经过任何边。
现在问题就来到了如何求一个点集的 lca。先给出结论,点集的 lca 等价于点集中 dfs 序最小的点与最大的点的 lca。证明比较简单,令 为任意一个点, 表示点 的 dfs 序, 表示点 的子树大小,点 子树的 dfs 序区间为 ,若 为点集的 lca,则说明 是满足点集内所有点都在子树内,即所有点的 dfs 序均在区间 内,其它点都是朴素的,真正影响是否在区间内的其实只有 dfs 序最小和最大的点。
考虑用一个东西去维护,因为有区间修改的操作,我们比较自然地想到线段树。先对线段树上的每个区间处理出区间最大值和最小值,再维护一个白点区间的最大最小值。一个操作 相当于加上一些白点区间,将这些区间的最大最小值分别变为原来预处理的区间最大最小值。同样,操作 相当于删去一些白点区间,将区间的最大最小值分别变为 。注意细节,如删去白点区间这个操作也是需要下放的。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=6e5+10,inf=1e9;
int n,m,cnt,val[N];
struct edge{
int u,v,w;
}e[N];
vector<int>G[N];
bool cmp(edge x,edge y){
return x.w<y.w;
}
namespace dsu{
int fa[N];
int getfa(int u){
if(fa[u]!=u){
fa[u]=getfa(fa[u]);
}
return fa[u];
}
}
int fa[N],dep[N],siz[N],heavy[N],top[N],id[N],rid[N],idx;
void make_tree(int u,int father){
fa[u]=father;
dep[u]=dep[father]+1;
siz[u]=1;
for(auto v:G[u]){
make_tree(v,u);
siz[u]+=siz[v];
if(siz[heavy[u]]<siz[v]){
heavy[u]=v;
}
}
}
void dfs(int u,int topf){
top[u]=topf;
id[u]=++idx;
rid[idx]=u;
if(heavy[u]){
dfs(heavy[u],topf);
}
for(auto v:G[u]){
if(v==heavy[u]){
continue;
}
dfs(v,v);
}
}
namespace Seg_Tree{
int mn[N<<2],mx[N<<2],wmn[N<<2],wmx[N<<2],tag[N<<2];
int ls(int p){
return p<<1;
}
int rs(int p){
return p<<1|1;
}
void build(int p,int l,int r){
wmn[p]=inf;
wmx[p]=0;
tag[p]=-1;
if(l==r){
mn[p]=mx[p]=id[l];
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
mx[p]=max(mx[ls(p)],mx[rs(p)]);
mn[p]=min(mn[ls(p)],mn[rs(p)]);
}
void push_up(int p){
wmx[p]=max(wmx[ls(p)],wmx[rs(p)]);
wmn[p]=min(wmn[ls(p)],wmn[rs(p)]);
}
void push_down(int p){
if(tag[p]==-1){
return;
}
if(tag[p]==1){
tag[ls(p)]=tag[rs(p)]=1;
wmx[ls(p)]=mx[ls(p)],wmx[rs(p)]=mx[rs(p)];
wmn[ls(p)]=mn[ls(p)],wmn[rs(p)]=mn[rs(p)];
tag[p]=-1;
}
else if(tag[p]==0){
tag[ls(p)]=tag[rs(p)]=0;
wmx[ls(p)]=wmx[rs(p)]=0;
wmn[ls(p)]=wmn[rs(p)]=inf;
tag[p]=-1;
}
}
void update(int p,int l,int r,int q_l,int q_r,int col){
if(q_l<=l&&r<=q_r){
if(col==1){
tag[p]=1;
wmx[p]=mx[p];
wmn[p]=mn[p];
}
else{
tag[p]=0;
wmx[p]=0;
wmn[p]=inf;
}
return;
}
push_down(p);
int mid=(l+r)>>1;
if(q_l<=mid){
update(ls(p),l,mid,q_l,q_r,col);
}
if(q_r>mid){
update(rs(p),mid+1,r,q_l,q_r,col);
}
push_up(p);
}
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
return u;
}
void solve(){
read(n),read(m);
for(int i=1;i<n;i++){
read(e[i].u),read(e[i].v),read(e[i].w);
}
sort(e+1,e+n,cmp);
cnt=n;
for(int i=1;i<=n*2;i++){
dsu::fa[i]=i;
}
for(int i=1;i<n;i++){
int u=dsu::getfa(e[i].u),v=dsu::getfa(e[i].v);
dsu::fa[u]=dsu::fa[v]=++cnt;
G[cnt].pb(u);
G[cnt].pb(v);
val[cnt]=e[i].w;
}
make_tree(cnt,0);
dfs(cnt,cnt);
Seg_Tree::build(1,1,n);
while(m--){
int opt,l,r,pos;
read(opt);
if(opt<=2){
read(l),read(r);
Seg_Tree::update(1,1,n,l,r,opt);
}
else{
read(pos);
int mn=Seg_Tree::wmn[1],mx=Seg_Tree::wmx[1];
mn=min(mn,id[pos]),mx=max(mx,id[pos]);
if(mn>=mx){
write_endl(-1);
continue;
}
int lca=LCA(rid[mn],rid[mx]);
write_endl(val[lca]);
}
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
Company
同样是上述结论的一个运用,不在赘述。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e5+10,inf=1e9;
int n,q,fa[N],dep[N],top[N],siz[N],heavy[N],id[N],rid[N],idx;
vector<int>e[N];
void make_tree(int u){
siz[u]=1;
for(auto v:e[u]){
dep[v]=dep[u]+1;
make_tree(v);
siz[u]+=siz[v];
if(siz[v]>siz[heavy[u]]){
heavy[u]=v;
}
}
}
void dfs(int u,int topf){
top[u]=topf;
id[u]=++idx;
rid[idx]=u;
if(heavy[u]){
dfs(heavy[u],topf);
}
for(auto v:e[u]){
if(v==heavy[u]){
continue;
}
dfs(v,v);
}
}
namespace Seg_Tree{
int mn[N<<2],mx[N<<2];
int ls(int p){
return p<<1;
}
int rs(int p){
return p<<1|1;
}
void push_up(int p){
mx[p]=max(mx[ls(p)],mx[rs(p)]);
mn[p]=min(mn[ls(p)],mn[rs(p)]);
}
void build(int p,int l,int r){
if(l==r){
mn[p]=mx[p]=id[l];
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
push_up(p);
}
pii query(int p,int l,int r,int q_l,int q_r){
if(q_l<=l&&r<=q_r){
return mp(mn[p],mx[p]);
}
int mid=(l+r)>>1,mini=inf,maxi=0;
if(q_l<=mid){
pii x=query(ls(p),l,mid,q_l,q_r);
mini=min(mini,x.first);
maxi=max(maxi,x.second);
}
if(q_r>mid){
pii x=query(rs(p),mid+1,r,q_l,q_r);
mini=min(mini,x.first);
maxi=max(maxi,x.second);
}
return mp(mini,maxi);
}
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
return u;
}
int query(int l,int mid,int r){
int minid=inf,maxid=0;
if(l<mid){
pii x=Seg_Tree::query(1,1,n,l,mid-1);
minid=min(minid,x.first);
maxid=max(maxid,x.second);
}
if(r>mid){
pii x=Seg_Tree::query(1,1,n,mid+1,r);
minid=min(minid,x.first);
maxid=max(maxid,x.second);
}
return dep[LCA(rid[minid],rid[maxid])];
}
void solve(){
read(n),read(q);
for(int i=2;i<=n;i++){
read(fa[i]);
e[fa[i]].pb(i);
}
make_tree(1);
dfs(1,1);
Seg_Tree::build(1,1,n);
while(q--){
int l,r;
read(l),read(r);
pii x=Seg_Tree::query(1,1,n,l,r);
int posmn=rid[x.first],posmx=rid[x.second];
int depmn=query(l,posmn,r),depmx=query(l,posmx,r);
if(depmn>=depmx){
write_space(posmn),write_endl(depmn);
}
else{
write_space(posmx),write_endl(depmx);
}
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
Hospital Queue
如何把一个点在拓扑排序中往前放,我们并不知道,于是考虑建反图,找到在反图上一个点可以放的最后位置。
对于如何把一个点 往后放这个我们是相当熟悉的,只要没必要放就不放。在这题中必须放有两种情况:第一种这个点是现在这张图上唯一一个度数为 的点,第二种是当前要放的点不能放在此时拓扑序末尾的位置,因为题目保证了有解,所以并不会出现有点放不下去且当前拓扑排序还排不到 的情况。同时为了贪心地使 往后排,即使第二种情况尽量晚地出现,我们会优先选择度数为 的点中限制最宽松的点放置。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=2e3+10;
int n,m,p[N],in[N],deg[N];
vector<int>e[N];
void topo(int x){
priority_queue<pii>q;
for(int i=1;i<=n;i++){
deg[i]=in[i];
}
for(int i=1;i<=n;i++){
if(!deg[i]){
q.push(mp(p[i],i));
}
}
int id=n;
while(!q.empty()){
int u=q.top().second;
q.pop();
if(u==x){
if(q.size()){
u=q.top().second;
q.pop();
q.push(mp(p[x],x));
}
else{
write_space(id);
return;
}//第一种情况
}
if(id>p[u]){
write_space(id);
return;
}//第二种情况。
id--;
for(auto v:e[u]){
deg[v]--;
if(!deg[v]){
q.push(mp(p[v],v));
}
}
}
}
void solve(){
read(n),read(m);
for(int i=1;i<=n;i++){
read(p[i]);
}
for(int i=1,u,v;i<=m;i++){
read(u),read(v);
e[v].pb(u);
in[u]++;
}
for(int i=1;i<=n;i++){
topo(i);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
[JOISC2020] 首都
将同色的点建一棵虚树,如果虚树的颜色属于最后答案的颜色集合,所有虚边上的点属于的颜色也要在颜色集合内。从虚树的颜色向虚边上的点的颜色连条有向边。因为答案要求操作次数最小即颜色集合最小,那么在缩点后选择的颜色集合一定是没有出度,且大小最小的颜色集合。
但我们注意到这里的边数是 级别的,我们需要优化建图。这种一个点向树上一个区间连边,很容易让人想到树剖+线段树优化建图。线段树上每个点向下一层连边,叶子节点向对应点的颜色连边,一条路径在树剖后再拆成若干个区间并向这些区间连边,因为每个点只有一条到点集 lca 的路径,所以总边数最多为 级别,可以通过本题。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=1e6+10,inf=1e9;
int n,m,col[N],cnt,id[N],rid[N];
vector<int>e[N],bel[N],G[N];
namespace Seg_Tree{
int p_id[N];
int ls(int p){
return p<<1;
}
int rs(int p){
return p<<1|1;
}
void build(int p,int l,int r){
p_id[p]=++cnt;
if(l==r){
G[p_id[p]].pb(col[rid[l]]);
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
G[p_id[p]].pb(p_id[ls(p)]);
G[p_id[p]].pb(p_id[rs(p)]);
}
void update(int p,int l,int r,int q_l,int q_r,int from){
if(q_l<=l&&r<=q_r){
G[from].pb(p_id[p]);
return;
}
int mid=(l+r)>>1;
if(q_l<=mid){
update(ls(p),l,mid,q_l,q_r,from);
}
if(q_r>mid){
update(rs(p),mid+1,r,q_l,q_r,from);
}
}
}
namespace Tree{
int fa[N],top[N],dep[N],siz[N],heavy[N],idx;
void make_tree(int u,int father){
fa[u]=father;
dep[u]=dep[father]+1;
siz[u]=1;
for(auto v:e[u]){
if(v==father){
continue;
}
make_tree(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[heavy[u]]){
heavy[u]=v;
}
}
}
void dfs(int u,int topf){
top[u]=topf;
id[u]=++idx;
rid[idx]=u;
if(heavy[u]){
dfs(heavy[u],topf);
}
for(auto v:e[u]){
if(v==heavy[u]||v==fa[u]){
continue;
}
dfs(v,v);
}
}
int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
return u;
}
void update(int u,int v,int c){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
Seg_Tree::update(1,1,n,id[top[u]],id[u],c);
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
Seg_Tree::update(1,1,n,id[u],id[v],c);
}
}
bool cmp(int x,int y){
return id[x]<id[y];
}
namespace Tarjan{
int dfn[N],low[N],tot,scc[N],scc_cnt,scc_siz[N],stk[N],top,in_stack[N];
void dfs(int u){
dfn[u]=low[u]=++tot;
stk[++top]=u;
in_stack[u]=1;
for(auto v:G[u]){
if(!dfn[v]){
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(in_stack[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
scc_cnt++;
while(1){
int v=stk[top--];
in_stack[v]=0;
scc[v]=scc_cnt;
scc_siz[scc_cnt]+=(v<=m);
if(v==u){
break;
}
}
}
}
}
int deg[N];
void solve(){
read(n),read(m);
cnt=m;
for(int i=1,u,v;i<n;i++){
read(u),read(v);
e[u].pb(v);
e[v].pb(u);
}
for(int i=1;i<=n;i++){
read(col[i]);
bel[col[i]].pb(i);
}
Tree::make_tree(1,0);
Tree::dfs(1,1);
Seg_Tree::build(1,1,n);
for(int i=1;i<=m;i++){
sort(bel[i].begin(),bel[i].end(),cmp);
if(!bel[i].size()){
continue;
}
int lca=Tree::LCA(bel[i].front(),bel[i].back());
for(auto u:bel[i]){
Tree::update(u,lca,i);
}
}
for(int i=1;i<=cnt;i++){
if(!Tarjan::dfn[i]){
Tarjan::dfs(i);
}
}
for(int u=1;u<=cnt;u++){
for(auto v:G[u]){
if(Tarjan::scc[u]!=Tarjan::scc[v]){
deg[Tarjan::scc[u]]++;
}
}
}
int mn=inf;
for(int i=1;i<=Tarjan::scc_cnt;i++){
if(!deg[i]){
mn=min(mn,Tarjan::scc_siz[i]);
}
}
write_endl(mn-1);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
[HNOI2014] 世界树
注意到关键点的数量之和不超过 ,很显然是建虚树。这题重点不在虚树,而是如何在虚树上求答案。
令 表示距离 最近的关键点, 表示 到 的距离。对于一个不在虚树上的点,有两种情况:在虚树上相邻两点之间 的路径上;位于虚树上一个点 的儿子 的子树内,且子树 内没有关键点。
对于第一种情况,这种路径会被拆成两部分,一部分到 ,一部分到 ,可以使用倍增找到分界点。
对于第二种情况,整个 子树会直接贡献到 。
使用两个 dfs 即可维护上述贡献。对于虚树上每个点 ,先计算出对应的 和 。再对于虚树上的一条边 ,可以计算出到 的贡献和不到 的贡献。令 表示这条虚边所在的链的分界点(这个点贡献到 ),为了防止重复计算,这里不计算 子树内的信息。因为 是分界点,所以到 的贡献可以表示为 ,直接加到 的贡献中即可;不到 的贡献为 , 的贡献先减去这些,最后再加上 就行。
唯一需要注意的是需要将 加入到虚树当中,这样可以在不破坏原树结构的情况下处理出不包括原来的虚树的根在原树中的子树的部分的贡献。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
const int inf=1e9;
namespace IO{
template<typename T>
inline void read(T &x){
x=0;
int f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-'){
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
x=(f==1?x:-x);
}
template<typename T>
inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>=10){
write(x/10);
}
putchar(x%10+'0');
}
template<typename T>
inline void write_endl(T x){
write(x);
putchar('\n');
}
template<typename T>
inline void write_space(T x){
write(x);
putchar(' ');
}
}
using namespace IO;
const int N=3e5+10,Lg=20;
int head[N],tot=1,first[N],cnt=1,n,m;
struct edge{
int v,nxt;
}e[N<<1],G[N<<1];
void add(int u,int v){
e[++tot].v=v;
e[tot].nxt=head[u];
head[u]=tot;
}
void add_e(int u,int v){
add(u,v);
add(v,u);
}
void ad(int u,int v){
G[++cnt].v=v;
G[cnt].nxt=first[u];
first[u]=cnt;
}
void add_G(int u,int v){
ad(u,v);
ad(v,u);
}
namespace init{
int f[N][Lg+5],dep[N],siz[N],dfn[N],idx;
void make_tree(int u,int fa){
dep[u]=dep[fa]+1;
siz[u]=1;
dfn[u]=++idx;
for(int i=1;i<=Lg;i++){
f[u][i]=f[f[u][i-1]][i-1];
}
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==fa){
continue;
}
f[v][0]=u;
make_tree(v,u);
siz[u]+=siz[v];
}
}
int lca(int u,int v){
if(dep[u]<dep[v]){
swap(u,v);
}
for(int i=Lg;i>=0;i--){
if(dep[f[u][i]]>=dep[v]){
u=f[u][i];
}
}
if(u==v){
return u;
}
for(int i=Lg;i>=0;i--){
if(f[u][i]!=f[v][i]){
u=f[u][i],v=f[v][i];
}
}
return f[u][0];
}
}
namespace Tree{
int top,stk[N],tr[N],vis[N],ans[N],f[N],pos[N],p[N];
void build(int x){
if(top==0){
top=1;
stk[top]=x;
return;
}
int lca=init::lca(stk[top],x);
while(top>1&&init::dep[lca]<init::dep[stk[top-1]]){
add_G(stk[top-1],stk[top]);
top--;
}
if(init::dep[lca]<init::dep[stk[top]]){
add_G(lca,stk[top--]);
}
if(top==0||stk[top]!=lca){
stk[++top]=lca;
}
stk[++top]=x;
}
void dfs(int u,int fa){
f[u]=inf;
for(int i=first[u];i;i=G[i].nxt){
int v=G[i].v;
if(v==fa){
continue;
}
dfs(v,u);
int dis=init::dep[v]-init::dep[u];
if(f[v]+dis<f[u]){
f[u]=f[v]+dis;
pos[u]=pos[v];
}
else if(f[v]+dis==f[u]){
pos[u]=min(pos[u],pos[v]);
}
}
if(vis[u]){
f[u]=0;
pos[u]=u;
}
}
void calc(int u,int v){
int y=v;
for(int i=Lg;i>=0;i--){
int llen=init::dep[v]-init::dep[init::f[y][i]]+f[v],rlen=init::dep[init::f[y][i]]-init::dep[u]+f[u];
if(init::dep[init::f[y][i]]>init::dep[u]&&(llen<rlen||(llen==rlen&&pos[v]<pos[u]))){
y=init::f[y][i];
}
}
ans[pos[v]]+=init::siz[y]-init::siz[v];
ans[pos[u]]-=init::siz[y];
}
void query(int u,int fa){
for(int i=first[u];i;i=G[i].nxt){
int v=G[i].v;
if(v==fa){
continue;
}
int dis=init::dep[v]-init::dep[u];
if(f[u]+dis<f[v]){
f[v]=f[u]+dis;
pos[v]=pos[u];
}
else if(f[u]+dis==f[v]){
pos[v]=min(pos[v],pos[u]);
}
calc(u,v);
query(v,u);
}
ans[pos[u]]+=init::siz[u];
vis[u]=first[u]=0;
}
bool cmp(int x,int y){
return init::dfn[x]<init::dfn[y];
}
void solve(){
bool flag=1;
top=0;cnt=1;
read(m);
for(int i=1;i<=m;i++){
read(tr[i]);
vis[tr[i]]=1;
ans[tr[i]]=0;
}
if(!vis[1]){
tr[++m]=1;
flag=0;
}
for(int i=1;i<=n;i++){
p[i]=tr[i];
}
sort(tr+1,tr+m+1,cmp);
for(int i=1;i<=m;i++){
build(tr[i]);
}
if(top){
while(--top){
add_G(stk[top],stk[top+1]);
}
}
dfs(1,0);
query(1,0);
for(int i=1;i<=m;i++){
if(p[i]!=1||flag){
write_space(ans[p[i]]);
}
}
putchar('\n');
}
}
void solve(){
read(n);
for(int i=1,u,v;i<n;i++){
read(u),read(v);
add_e(u,v);
}
init::make_tree(1,0);
int q;
read(q);
while(q--){
Tree::solve();
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
int t=1;
while(t--){
solve();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通