#6 有个搬题人搬的题很好但题搬得很差

无心随笔 / 2023-09-01 / 原文

Infinite Game

题面

感觉是一个比较经典的自动机dp。对于一个回合,有效的只有 \(0:0,0:1,1:0,1:1\)\(4\) 种情况。将这种情况分别看作 \(1,2,3,4\)。可以发现最后一定能形成环,因为我们如果我们把字符串看成一轮,用一轮开始环上的点的状态表示这一轮的环,那么在无限轮之后一定有重复的,即一定存在环。

如果我们简单从 \(0\) 开始走,走完一遍后重新走,这是不对的,因为我们需要保证中间过程中的 ? 每一轮选到字母是一样的。于是我们想到可以将四个状态都记录下来,令 \(f_{i,a,b,c,d}\) 表示走 \(i\) 步,且起点为 \(0,1,2,3\) 时走到 \(a,b,c,d\) 的方案数。

统计答案直接统计两人的胜场之差,但这只在最后的环上会产生贡献。我们考虑枚举最后环上会出现的点的点集,只统计最后构成的环上的点和枚举的点集相同的情况产生的贡献。

点击查看代码
#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=410,mx=400,mod=998244353;
int n,f[N<<1][4][4][4][4],g[N<<1][4][4][4][4],nxt[4][2],ans[4];
char s[N];
void solve(){
    nxt[0][0]=1,nxt[0][1]=2;
    nxt[1][0]=0,nxt[1][1]=3;
    nxt[2][0]=3,nxt[2][1]=0;
    nxt[3][0]=nxt[3][1]=0;
    cin>>(s+1);
    n=strlen(s+1);
    for(int S=0;S<16;S++){
        memset(f,0,sizeof(f));
        f[mx][0][1][2][3]=1;
        for(int i=1;i<=n;i++){
            for(int j=0;j<=mx*2;j++){
                for(int a=0;a<4;a++)for(int b=0;b<4;b++)for(int c=0;c<4;c++)for(int d=0;d<4;d++){
                    if(!f[j][a][b][c][d]){
                        continue;
                    }
                    for(int k=0;k<2;k++){
                        if(s[i]-'a'!=k&&s[i]!='?'){
                            continue;
                        }
                        int nxta=nxt[a][k],nxtb=nxt[b][k],nxtc=nxt[c][k],nxtd=nxt[d][k];
                        int nxtj=((S&1)&&(a&(k+1)))+((S&2)&&(b&(k+1)))+((S&4)&&(c&(k+1)))+((S&8)&&(d&(k+1)));
                        nxtj=(k?j+nxtj:j-nxtj);
                        g[nxtj][nxta][nxtb][nxtc][nxtd]=(g[nxtj][nxta][nxtb][nxtc][nxtd]+f[j][a][b][c][d])%mod;
                    }
                }
            }
            for(int j=0;j<=mx*2;j++){
                for(int a=0;a<4;a++)for(int b=0;b<4;b++)for(int c=0;c<4;c++)for(int d=0;d<4;d++){
                    f[j][a][b][c][d]=g[j][a][b][c][d];
                    g[j][a][b][c][d]=0;
                }
            }
        }
        for(int a=0;a<4;a++)for(int b=0;b<4;b++)for(int c=0;c<4;c++)for(int d=0;d<4;d++){
            int trans[4]={a,b,c,d},x=0;
            for(int i=0;i<10;i++){
                x=trans[x];
            }//保证能走到环上就行
            int st=0;
            for(int i=0;i<10;i++){
                st|=(1<<x);
                x=trans[x];
            }//判断点集与环上的点是否相同。
            if(st!=S){
                continue;
            }
            for(int j=0;j<=mx*2;j++){
                ans[(j>=mx)+(j>mx)]=(ans[(j>=mx)+(j>mx)]+f[j][a][b][c][d])%mod;
            }
        }
    }
    write_endl(ans[0]);
    write_endl(ans[1]);
    write_endl(ans[2]);
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}

[USACO23FEB] Watching Cowflix P

题面

先做 \(O(n^2)\) 的做法吧,令 \(f_{i,0/1}\) 表示点 \(i\) 选或不选的最小代价,转移比较简单,把所有的基础贡献在连通块中度数再去加。\(f_{u,0}=\min(f_{v,0},f_{v,1}+k),f_{u,1}=1+\min(f_{v,0},f_{v,1})\)。最后特殊处理下根就可以了。

可以发现有个链的部分分,我们考虑对于一个序列,我们该怎么做。有一个显然正确的贪心策略,如果两个点之间的距离小于 \(k\),我们必然把这两个放在一个连通块中,因为从一个连通块扩展到另一个连通块的代价小于建一个新连通块,更优。这个策略同样可以放到树上,我们可以得到,树上的连通块的数量不会超过 \(\frac{n+k}{k+1}\),可以近似看作 \(\frac{n}{k}\),我们将连通块建出虚树,在虚树上跑上述的dp,复杂度 \(O(\sum\limits_{k=1}^n{\frac{n}{k}})=O(n\log n)\)

点击查看代码
#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=2e5+10,inf=1e9;
int n,f[N][2],fa[N],idx,k,pos[N],dep[N],val[N],rpos[N],all;
char s[N];
vector<int>e[N],G[N];
int make_tree(int u,int father){
    dep[u]=dep[father]+1;
    int cnt=0,Pos=0;
    for(auto v:e[u]){
        if(v==father){
            continue;
        }
        int p=make_tree(v,u);
        if(!p){
            continue;
        }
        if(Pos){
            fa[Pos]=u;
        }
        Pos=p;
        cnt++;
    }
    if(cnt<=1&&s[u]=='0'){
        return Pos;
    }
    else{
        pos[++idx]=u;
        fa[Pos]=u;
        return u;
    }
}
void dfs(int u){
    f[u][0]=(s[u]=='1'?inf:0);
    f[u][1]=1;
    for(auto v:G[u]){
        dfs(v);
        f[u][0]+=min(f[v][0],f[v][1]+k);
        f[u][1]+=min(f[v][0],f[v][1]+min(k,val[v]));
    }
}
void merge(int u,int opt){
    if(opt){
        s[u]='1';
    }
    for(auto v:G[u]){
        fa[v]=rpos[u];
        if(!opt){
            merge(v,f[v][1]+k<=f[v][0]);
            continue;
        }
        int type=(f[v][1]+min(k,val[v])<=f[v][0]);
        if(type&&val[v]<=k){
            rpos[v]=rpos[u];
            all+=val[v]+1;
        }
        merge(v,type);
    }
}
void solve(){
    read(n);
    for(int i=1;i<=n;i++){
        s[i]=getchar();
        while(s[i]!='0'&&s[i]!='1'){
            s[i]=getchar();
        }
    }
    for(int i=1;i<n;i++){
        int u,v;
        read(u),read(v);
        e[u].pb(v);
        e[v].pb(u);
    }
    int rt=make_tree(1,0);
    for(int i=1;i<=idx;i++){
        val[pos[i]]=dep[pos[i]]-dep[fa[pos[i]]]-1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=idx;j++){
            if(fa[pos[j]]){
                G[fa[pos[j]]].pb(pos[j]);
            }
        }
        for(int j=1;j<=idx;j++){
            rpos[pos[j]]=pos[j];
        }
        k=i;
        dfs(rt);
        write_endl(min(f[rt][0],f[rt][1]+i)+all);
        int id=0;
        merge(rt,f[rt][1]+i<=f[rt][0]);
        for(int j=1;j<=idx;j++){
            G[pos[j]].clear();
            G[pos[j]].shrink_to_fit();
        }
        for(int j=1;j<=idx;j++){
            if(rpos[pos[j]]==pos[j]){
                pos[++id]=pos[j];
            }
        }
        idx=id;
    }
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}

马超战潼关

题面

可以发现原图就是一个网络流求二分图最大匹配的模型,最小割大小一定为最大匹配的大小。

求出一个最大匹配方案。构造一张图,每个点代表一个匹配,用点权 \(0,1,2\) 表示匹配 \((u,v)\) 所在路径选择割 \((S,u),(u,v),(v,T)\) 三条边。设 \(val_i\) 表示第 \(i\) 条匹配边的点权。对于一条边 \((e_u,e_v)\),设 \(x\)\(e_u\) 所在匹配,\(y\)\(e_v\) 所在匹配,\(e_u,e_v\) 若不在匹配中,则 \(x,y\)\(0\)

对于一条不在匹配中的边 \((e_u,e_v)\),因为匹配的最大性,所以 \(e_u,e_v\) 必有至少一个在匹配中。

  1. \(x>0,y=0\),则 \(val_x=0\)
  2. \(x=0,y>0\),则 \(val_y=2\)
  3. \(x>0,y>0\),则 \(val_x=0\text{或}val_y=2\)

对于最后一种情况,我们从 \(x\)\(y\) 连一条边。根据情况 \(3\) 的限制,可以发现在新图中如果存在一条边 \((u,v)\)\(val_u\le val_v\text{且不存在}val_u=val_v=1\)。即对于图上一条链,点权一定类似于 \(0\rightarrow 0\rightarrow 0\rightarrow (1)\rightarrow 2\rightarrow 2\rightarrow 2\) 的样子,其中点权为 \(1\) 的点可以不必出现。

将新得到的这张图进行一个缩点操作,显然环中所有点点权应相同,且没有点点权为 \(1\)。缩点后可以得到一个DAG,求出拓扑序。因为数据较大,考虑 meet in the middle,枚举前一半点的权值,可以限制后一半某些点必须选 \(2\),预处理后一半的情况,做一遍高维前缀和,可以得到 \(O(3^n n^2)\) 的算法,这是不够的。

但我们新图中还有一个极为重要的性质是没有用上的——\(1\) 至多出现一个。所以可以前半部分只限制是否选 \(0\),后半部分能为 \(1\) 的只有导出子图中入度为 \(0\) 的点,反过来,限制后半部分做一遍对称的,只限制是否选 \(2\),前半部分能为 \(1\) 的只有导出子图中出度为 \(0\) 的点。预处理前半部分,做高位前缀和即可。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll __int128_t
#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=510,M=9e6+10;
int n,m;
ll ans;
struct edge{
    int u,v;
}e[N];
int id1[N],id2[N],idx,reach_small[N],reach_large[N];
int f[M];
namespace KM{
    int must_be_0[N],must_be_2[N],vis[N],couple[N],cnt;
    vector<int>G[N];
    int find(int u,int tag){
		if(vis[u]==tag){
            return 0;
        }
		vis[u]=tag;
		for(auto i:G[u]){
			int v=e[i].v;
			if(!couple[v] || find(e[couple[v]].u,tag)){
				couple[v]=i;
				return 1;
			}
		}   
		return 0;
	}
}
namespace Tarjan{
    vector<int>G[N];
    int dfn[N],low[N],stk[N],top,cnt=0,in_stack[N];
    int must_be_0[N],must_be_2[N],cant_be_1[N],col[N],col_cnt;
    void dfs(int u){
        dfn[u]=low[u]=++cnt;
        in_stack[u]=1;
        stk[++top]=u;
        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]){
            int sum=0;
            while(1){
                int v=stk[top--];
                sum++;
                must_be_0[col_cnt]|=KM::must_be_0[v];
                must_be_2[col_cnt]|=KM::must_be_2[v];
                in_stack[v]=0;
                col[v]=col_cnt;
                if(v==u){
                    break;
                }
            }
            if(sum>1){
                cant_be_1[col_cnt]=1;
            }
            ++col_cnt;
        }
    }
}
namespace DAG{
    int in[N],out[N],vis[N];
    vector<int>G[N];
    void dfs(int u){
        if(vis[u]){
            return;
        }
        vis[u]=1;
        for(auto v:G[u]){
            dfs(v);
        }
    }
    void solve(){
        int cnt=Tarjan::col_cnt,l=cnt/2,r=cnt-l;
        for(int i=0;i<cnt;i++){
            for(int j=0;j<cnt;j++){
                vis[j]=0;
            }
            dfs(i);
            for(int j=0;j<l;j++){
                if(vis[j]){
                    reach_small[i]|=(1ll<<j);
                }
            }
            for(int j=l;j<cnt;j++){
                if(vis[j]){
                    reach_large[i]|=(1ll<<j);
                }
            }
        }
        for(int S=0;S<(1ll<<l);S++){
            int R=((1ll<<l)-1)^S,flag=1;
            for(int i=0;i<l&&flag;i++){
                if(S>>i&1ll){
                    if(Tarjan::must_be_0[i]){
                        flag=0;
                    }
                    if((reach_small[i]&S)!=reach_small[i]){
                        flag=0;
                    }
                }
                else if(Tarjan::must_be_2[i]){
                    flag=0;
                }
            }
            if(!flag){
                continue;
            }
            int tot=0;
            for(int i=0;i<l;i++){
                if(S>>i&1ll){
                    continue;
                }
                if(((out[i]&R)==0ll)&&(!Tarjan::must_be_0[i])&&(!Tarjan::must_be_2[i])&&(!Tarjan::cant_be_1[i])){
                    tot++;
                }
            }
            f[S]=(1ll<<tot);
        }
        for(int k=1;k<(1ll<<l);k*=2){
            for(int i=0;i<(1ll<<l);i+=k*2){
                for(int j=i;j<i+k;j++){
                    f[j]+=f[j+k];
                }
            }
        }
        for(int T=0;T<(1ll<<r);T++){
            int S=T<<l,flag=1;
            for(int i=l;i<l+r&&flag;i++){
                if(S>>i&1ll){
                    if(Tarjan::must_be_0[i]){
                        flag=0;
                    }
                    if((reach_large[i]&S)!=reach_large[i]){
                        flag=0;
                    }
                }
                else if(Tarjan::must_be_2[i]){
                    flag=0;
                }
            }
            if(!flag){
                continue;
            }
            int R=0,tot=0;
            for(int i=l;i<l+r;i++){
                if(!(S>>i&1ll)){
                    continue;
                }
                R|=reach_small[i];
                if(((in[i]&S)==0ll)&&(!Tarjan::must_be_0[i])&&(!Tarjan::must_be_2[i])&&(!Tarjan::cant_be_1[i])){
                    tot++;
                }
            }
            ans+=(1ll<<tot)*f[R];
        }
        write_endl(ans);
    }
}   
void solve(){
    read(n),read(m);
    for(int i=1;i<=m;i++){
        read(e[i].u),read(e[i].v);
        KM::G[e[i].u].pb(i);
    }
    for(int i=1;i<=n;i++){
        KM::cnt+=KM::find(i,i);
    }
    for(int i=1;i<=n;i++){
        if(KM::couple[i]){
            id1[e[KM::couple[i]].u]=id2[i]=++idx;
        }
    }
    for(int i=1;i<=m;i++){
        if(i==KM::couple[e[i].v]){
            continue;
        }
        int u=id1[e[i].u],v=id2[e[i].v];
        if(u&&!v){
            KM::must_be_0[u]=1;
        }
        else if(!u&&v){
            KM::must_be_2[v]=1;
        }
        else if(u&&v){
            Tarjan::G[u].pb(v);
        }
    }
    for(int i=1;i<=KM::cnt;i++){
        if(!Tarjan::dfn[i]){
            Tarjan::dfs(i);
        }
    }
    for(int u=1;u<=KM::cnt;u++){
        for(auto v:Tarjan::G[u]){
            int x=Tarjan::col[u],y=Tarjan::col[v];
            if(x!=y){
                DAG::G[x].pb(y);
                DAG::in[y]|=(1ll<<x);
                DAG::out[x]|=(1ll<<y);
            }
            else{
                Tarjan::cant_be_1[x]=1;
            }
        }
    }
    DAG::solve();
}
signed main(){
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}

The Child and Sequence

题面

注意到取模的性质:
\(a\equiv r\pmod x\),得 \(a=kx+r(r<x)\)
\(k>0\),则 \(a>2r\),若 \(k=0\),则 \(a=r\)
一次取模过后,如果一个值变小了,那么它至少会减小到一半,因此一个数 \(a\) 最多进行 \(\log a\) 次有效取模。

可以使用线段树维护和,操作一和操作三是最经典的线段树单点修改,区间查询。对于操作二,因为取模操作不能区间操作然后 pushdown,显然我们只能暴力修改。容易得到的是不需要对所有的区间进行操作,可能被修改的区间只有最大值大于等于 \(x\) 的数才会被修改。因此我们在维护区间和时同时维护区间最大值,修改时如果当前区间最大值小于 \(x\) 直接返回。复杂度 \(O(q\log n\log V)\)

点击查看代码
#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=1e5+10;
int n,m,a[N];
namespace Seg_Tree{
    int ans[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){
        ans[p]=ans[ls(p)]+ans[rs(p)];
        mx[p]=max(mx[ls(p)],mx[rs(p)]);
    }
    void build(int p,int l,int r){
        if(l==r){
            ans[p]=mx[p]=a[l];
            return;
        }
        int mid=(l+r)>>1;
        build(ls(p),l,mid);
        build(rs(p),mid+1,r);
        push_up(p);
    }
    void update(int p,int l,int r,int q_l,int q_r,int mod){
        if(mx[p]<mod){
            return;
        }
        if(l==r){
            ans[p]%=mod;
            mx[p]%=mod;
            return;
        }
        int mid=(l+r)>>1;
        if(q_l<=mid){
            update(ls(p),l,mid,q_l,q_r,mod);
        }
        if(q_r>mid){
            update(rs(p),mid+1,r,q_l,q_r,mod);
        }
        push_up(p);
    }
    void change(int p,int l,int r,int pos,int val){
        if(l==r){
            ans[p]=mx[p]=val;
            return;
        }
        int mid=(l+r)>>1;
        if(pos<=mid){
            change(ls(p),l,mid,pos,val);
        }
        else{
            change(rs(p),mid+1,r,pos,val);
        }
        push_up(p);
    }
    int query(int p,int l,int r,int q_l,int q_r){
        if(q_l<=l&&r<=q_r){
            return ans[p];
        }
        int mid=(l+r)>>1,res=0;
        if(q_l<=mid){
            res+=query(ls(p),l,mid,q_l,q_r);
        }
        if(q_r>mid){
            res+=query(rs(p),mid+1,r,q_l,q_r);
        }
        return res;
    }
}
void solve(){
    read(n),read(m);
    for(int i=1;i<=n;i++){
        read(a[i]);
    }
    Seg_Tree::build(1,1,n);
    for(int i=1;i<=m;i++){
        int opt,l,r,val;
        read(opt);
        if(opt==1){
            read(l),read(r);
            write_endl(Seg_Tree::query(1,1,n,l,r));
        }
        else if(opt==2){
            read(l),read(r),read(val);
            Seg_Tree::update(1,1,n,l,r,val);
        }
        else{
            read(l),read(val);
            Seg_Tree::change(1,1,n,l,val);
        }
    }
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}

Freezing with Style

题面

中位数不好处理,考虑二分中位数 \(x\),将大于等于 \(x\) 的数记为 \(+1\),将小于 \(x\) 的数记为 \(-1\),这样只要判断树上是否有一条满足条件且权值和大于等于 \(0\) 的路径即可。

对于路径问题容易想到点分治,用贪心的思想做,对于深度相同的链,我们只保存边权和最大的。令 \(f_i\) 表示之前所有处理过的子树中深度为 \(i\) 的链的边权和最大值,\(g_i\) 表示当前正在处理的子树中深度为 \(i\) 的链的边权和最大值。要求的就是 \(\max\{f_i+g_j|l\le i+j\le r\}\ge 0\)。容易发现这个是像滑动窗口的操作,可以用单调队列维护。但我们注意到如果直接做,单调队列的大小是 \(O(n)\) 的。令 \(pre\) 表示处理过的子树中的点的最大深度,\(now\) 表示当前子树中的点的最大深度,当 \(pre\le now\) 时,单调队列的大小为 \(O(pre)\)

于是我们得到了在 calc 时先将子树按照最大深度大小排序的做法,这样的复杂度为 \(O(\sum pre)=O(n)\),对于排序,因为每个子树对应一条子树根到分治中心的边,所以排序总复杂度为 \(O(n\log n)\)

点击查看代码
#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,l,r,ansu,ansv,head[N],tot=1;
struct edge{
    int v,w,nxt;
}e[N<<1];
void add(int u,int v,int w){
    e[++tot].v=v;
    e[tot].w=w;
    e[tot].nxt=head[u];
    head[u]=tot;
}
void add_e(int u,int v,int w){
    add(u,v,w);
    add(v,u,w);
}
int vis[N],siz[N],dep[N],mx[N],rt;
void get_rt(int u,int fa,int sz){
    siz[u]=1;
    mx[u]=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(v==fa||vis[v]){
            continue;
        }
        get_rt(v,u,sz);
        siz[u]+=siz[v];
        mx[u]=max(mx[u],siz[v]);
    }
    mx[u]=max(mx[u],sz-siz[u]);
    if(mx[rt]>mx[u]){
        rt=u;
    }
}
int maxdep[N],weight[N],f[N],f_pos[N],g[N],g_pos[N],dis[N];
void dfs(int st,int u,int fa,int Dep,int Len,int val,int rt){
    dep[u]=Dep;
    maxdep[st]=max(maxdep[st],dep[u]);
    dis[u]=Len;
    if(dis[u]>g[dep[u]]){
        g[dep[u]]=dis[u];
        g_pos[dep[u]]=u;
    }
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].v,w=e[i].w;
        if(v==fa||vis[v]){
            continue;
        }
        dfs(st,v,u,Dep+1,Len+(w>=val?1:-1),val,rt);
    }
}
int num[N],que[N];
bool cmp(int x,int y){
    return maxdep[x]<maxdep[y];
}
int calc(int u,int val){
    int tot=0,Maxdep=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].v,w=e[i].w;
        if(vis[v]){
            continue;
        }
        num[++tot]=v;
        maxdep[v]=0;
        weight[v]=w;
    }
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].v,w=e[i].w;
        if(vis[v]){
            continue;
        }
        dfs(v,v,u,1,w>=val?1:-1,val,u);
        Maxdep=max(Maxdep,maxdep[v]);
    }
    sort(num+1,num+tot+1,cmp);
    for(int i=0;i<=Maxdep;i++){
        g[i]=f[i]=-inf;
        f_pos[i]=g_pos[i]=0;
    }
    f[0]=0;
    f_pos[0]=u;
    for(int i=1;i<=tot;i++){
        int v=num[i],w=weight[v];
        if(vis[v]){
            continue;
        }
        dfs(v,v,u,1,w>=val?1:-1,val,u);
        int now=0,head,tail;
        head=tail=1;
        for(int j=min(r,maxdep[v]);j>=0;j--){
            while(now<=maxdep[num[i-1]]&&now+j<=r){
                while(head<tail&&f[que[tail-1]]<f[now]){
                    tail--;
                }
                que[tail++]=now;
                now++;
            }
            while(head<tail&&que[head]+j<l){
                head++;
            }
            if(head<tail&&que[head]+j<=r&&f[que[head]]+g[j]>=0){
                ansu=f_pos[que[head]],ansv=g_pos[j];
                return 1;
            }
        }
        for(int j=0;j<=maxdep[v];j++){
            if(g[j]>f[j]){
                f[j]=g[j];
                f_pos[j]=g_pos[j];
            }
            g[j]=-inf;
            g_pos[j]=0;
        }
    }
    return 0;
}
int divide(int u,int sz,int val){
    if(calc(u,val)){
        return 1;
    }
    rt=0;
    get_rt(u,0,sz);
    vis[u]=1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(vis[v]){
            continue;
        }
        int Siz=siz[v];
        rt=0;
        get_rt(v,u,Siz);
        if(divide(rt,Siz,val)){
            return 1;
        }
    }
    return 0;
}
bool check(int x){
    for(int i=1;i<=n;i++){
        vis[i]=0;
    }
    ansu=ansv=0;
    mx[0]=inf;
    rt=0;
    get_rt(1,0,n);
    return divide(rt,n,x);
}
void solve(){
    int L=inf,R=0;
    read(n),read(l),read(r);
    for(int i=1,u,v,w;i<n;i++){
        read(u),read(v),read(w);
        add_e(u,v,w);
        L=min(L,w);
        R=max(R,w);
    }
    int ans=L;
    while(L<=R){
        int mid=(L+R)>>1;
        if(check(mid)){
            L=mid+1;
            ans=mid;
        }
        else{
            R=mid-1;
        }
    }
    check(ans);
    write_space(ansu),write_endl(ansv);
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}