Codeforces 1096 Div3(ABCDEFGH)

前言

坚持补完 div3 是对的!!努力努力再努力!!

一、A. Koshary

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int x,y;
    cin>>x>>y;

    if(x%2&&y%2)
    {
        NO;
    }
    YES;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

由于长度为 1 的只能走一步,所以若 x 和 y 都是奇数那么就不可能走到。

二、B. Party Monster

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n;
    cin>>n;
    string s;
    cin>>s;
    s=" "+s;

    vector<int>a(n+1);
    for(int i=1;i<=n;i++)
    {
        a[i]=(s[i]=='('?1:-1);
    }

    vector<int>pre(n+1);
    for(int i=1;i<=n;i++)
    {
        pre[i]=pre[i-1]+a[i];
    }

    if(pre[n]!=0)
    {
        NO;
    }
    YES;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

首先,转化为 +1 和 -1 后,若整个数组的累加和不为 0,那么必然不可能。

之后,在栈模拟匹配的过程中,此时最后栈里必然是 x 个右括号和 x 个左括号。此时就只需要把后面这部分的左括号选出来移动到前面即可,所以必然有解。

三、C. Snowfall

又是 guess......

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    vector<int>six;
    vector<int>two;
    vector<int>three;
    vector<int>no;
    for(int i=1;i<=n;i++)
    {
        if(a[i]%6==0)
        {
            six.push_back(a[i]);
        }
        else if(a[i]%2==0)
        {
            two.push_back(a[i]);
        }
        else if(a[i]%3==0)
        {
            three.push_back(a[i]);
        }
        else
        {
            no.push_back(a[i]);
        }
    }

    for(auto x:six)
    {
        cout<<x<<" ";
    }
    for(auto x:two)
    {
        cout<<x<<" ";
    }
    for(auto x:no)
    {
        cout<<x<<" ";
    }
    for(auto x:three)
    {
        cout<<x<<" ";
    }
    cout<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

对于是 6 的倍数的数,其肯定无法避免使得区间累乘积为 6 的倍数,此时 guess 一下将其全丢到数组的一边。之后对于 2 的倍数和 3 的倍数,此时肯定不能让他们在一起产生贡献。所以考虑先把 2 的倍数全输出,再输出剩余的数将两部分隔开,最后输出 3 的倍数即可。

四、D. Palindromex

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n;
    cin>>n;
    vector<int>a(2*n+1);
    for(int i=1;i<=2*n;i++)
    {
        cin>>a[i];
    }

    vector<int>pl(n);
    vector<int>pr(n);
    for(int i=1;i<=2*n;i++)
    {
        if(pl[a[i]]==0)
        {
            pl[a[i]]=i;
        }
        else
        {
            pr[a[i]]=i;
        }
    }

    auto check=[&](int jobl,int jobr)->int
    {
        vector<int>vis(n);
        for(int l=jobl,r=jobr;l<=r;l++,r--)
        {
            if(a[l]!=a[r])
            {
                return 1;
            }

            vis[a[l]]=1;
        }
        for(int l=jobl,r=jobr;l>=1&&r<=2*n;l--,r++)
        {
            if(a[l]!=a[r])
            {
                break;
            }
            vis[a[l]]=1;
        }

        for(int i=0;i<n;i++)
        {
            if(!vis[i])
            {
                return i;
            }
        }
        return n;
    };

    int p1=check(pl[0],pr[0]);
    int p2=check(pl[0],pl[0]);
    int p3=check(pr[0],pr[0]);
    
    int ans=max({p1,p2,p3});
    cout<<ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

如果要想 MEX 更大,那么其必然需要有 0 这个数,所以可以考虑从 0 开始看最远能扩到什么程度。那么对于 0 出现的两个位置,分别讨论以这两个位置的中点为对称轴,和以其中一个位置自己为对称轴这三种情况,取最大即可。

具体在计算时,考虑先收集初始边界内出现的数,再尝试能否往外扩,最后过一遍求 MEX 即可。

五、E. It All Went Sideways

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct Segment_Tree
{
    vector<T>data;
    //自定义
    vector<T>info;

    Segment_Tree(){}

    Segment_Tree(int n){
        data.assign(n<<2,0);
        info.assign(n<<2,0);
    }

    void add(int jobl,int jobr,T jobv,int l,int r,int i){
        if(jobl<=l&&r<=jobr){
            addLazy(i,jobv,r-l+1);
        }
        else{
            int m=(l+r)>>1;

            down(i,m-l+1,r-m);

            if(jobl<=m){
                add(jobl,jobr,jobv,l,m,i<<1);
            }
            if(m+1<=jobr){
                add(jobl,jobr,jobv,m+1,r,i<<1|1);
            }

            up(i);
        }
    }

    T query(int jobi,int l,int r,int i){
        if(l==r){
            return data[i];
        }
        
        int m=(l+r)>>1;

        down(i,m-l+1,r-m);

        //自定义
        T ans=0;
        if(jobi<=m){
            ans+=query(jobi,l,m,i<<1);
        }
        else{
            ans+=query(jobi,m+1,r,i<<1|1);
        }
        return ans;
    }

    //自定义
    void addLazy(int i,T v,int n){
        data[i]+=v*n;
        info[i]+=v;
    }

    //自定义
    void up(int i){
        data[i]=data[i<<1]+data[i<<1|1];
    }

    //自定义
    void down(int i,int ln,int rn){
        if(info[i])
        {
            addLazy(i<<1,info[i],ln);
            addLazy(i<<1|1,info[i],rn);
            info[i]=0;
        }
    }
};

void solve()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    ll ans=0;
    vector<int>up(n+2);
    up[n+1]=n+1;
    for(int i=n,top=n+1;i>=1;i--)
    {
        ans+=max(0,a[i]-top);

        while(top>a[i])
        {
            top--;
        }
        up[i]=top;
    }

    ll mx=0;
    Segment_Tree<ll>st(n+1);
    for(int i=1;i<=n;i++)
    {
        if(up[i]<up[i+1])
        {
            mx=max(mx,st.query(a[i],1,n,1));
        }
        
        st.add(1,min(a[i],up[i]),1,1,n,1);
    }

    cout<<ans+mx<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

首先,由于删除某一列的最上方的格子,只会对当前这一行产生影响。所以可以考虑现在统计出原本不删除时的答案,再统计出删除每一列最上方的格子后,新增加的贡献,取最大值即可。

那么在求原本就会移动的数量时,考虑从后往前遍历,维护一个 top 变量表示当前从 top 高度开始往上的部分都会发生移动。那么若当前的 ,此时 top 就会更新到 的位置,因为前面所有高于这个高度的方块肯定至少会往右滑到这一列。而若 ,那么当前列就会有 个方块发生移动。

其实可以发现 top 就是后缀最小值......

在上述过程中,考虑用 up 数组记录来到每个位置的 top。那么对于 的位置,即将 top 更新得更小的位置,此时选择这个位置拿走最上方的方块,就可以让所有左侧这个高度的方块发生移动。这个是因为当前列必然是左侧所有高度为 的方块移动后来到的位置,因为右侧所有列的高度都大于 。而如果不在这拿走,而是在前面高度也为 的位置拿,那么发生移动的方块数肯定稳定不优。所以实现时就可以拿线段树维护每个高度上的方块数,每次单点查取最大,然后范围增加即可。注意增加时不要把 上方本来就发生移动的方块算上。

六、F. It Just Keeps Going Sideways

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct Segment_Tree
{
    vector<T>data;
    //自定义
    vector<T>info;

    Segment_Tree(){}

    Segment_Tree(int n){
        data.assign(n<<2,0);
        info.assign(n<<2,0);
    }

    void add(int jobl,int jobr,T jobv,int l,int r,int i){
        if(jobl<=l&&r<=jobr){
            addLazy(i,jobv,r-l+1);
        }
        else{
            int m=(l+r)>>1;

            down(i,m-l+1,r-m);

            if(jobl<=m){
                add(jobl,jobr,jobv,l,m,i<<1);
            }
            if(m+1<=jobr){
                add(jobl,jobr,jobv,m+1,r,i<<1|1);
            }

            up(i);
        }
    }

    T query(int jobl,int jobr,int l,int r,int i){
        if(jobl<=l&&r<=jobr){
            return data[i];
        }
        
        int m=(l+r)>>1;

        down(i,m-l+1,r-m);

        //自定义
        T ans=0;
        if(jobl<=m){
            ans+=query(jobl,jobr,l,m,i<<1);
        }
        if(m+1<=jobr){
            ans+=query(jobl,jobr,m+1,r,i<<1|1);
        }

        return ans;
    }

    //自定义
    void addLazy(int i,T v,int n){
        data[i]+=v*n;
        info[i]+=v;
    }

    //自定义
    void up(int i){
        data[i]=data[i<<1]+data[i<<1|1];
    }

    //自定义
    void down(int i,int ln,int rn){
        if(info[i])
        {
            addLazy(i<<1,info[i],ln);
            addLazy(i<<1|1,info[i],rn);
            info[i]=0;
        }
    }
};

void solve()
{
    int n;
    cin>>n;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    vector<ll>up(n+2);
    up[n+1]=n+1;
    for(int i=n,top=n+1;i>=1;i--)
    {
        while(top>a[i])
        {
            top--;
        }
        up[i]=top;
    }

    Segment_Tree<ll>right(n+1);
    ll ans=0;
    for(int i=n;i>=1;i--)
    {
        if(a[i]>up[i+1])
        {
            ans+=(a[i]-up[i+1])*(n-i)-right.query(up[i+1]+1,a[i],1,n,1);
        }

        if(a[i])
        {
            right.add(1,a[i],1,1,n,1);
        }
    }

    Segment_Tree<ll>cnts(n+1);
    ll mx=0;
    for(int i=1;i<=n;i++)
    {
        if(up[i]<up[i+1])
        {
            mx=max(mx,cnts.query(a[i],a[i],1,n,1));
        }

        cnts.add(1,a[i],1,1,n,1);
    }

    cout<<ans+mx<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

在之前的基础上,考虑此时如何计算贡献。

对于某一列某个高度的方块,其去往的位置取决于其右侧的方块数。那么若右侧有 x 个方块,此时其去往的位置就是 n-x,那么其贡献就是 n-x-i。又因为发生移动的是高度大于 的部分,所以若右侧的个数为 ,那么总贡献就是 。对这个式子拆贡献,有 。所以可以考虑拿线段树维护每个高度右侧方块的个数,范围加范围查累加和即可。

之后在计算增加的贡献时,若移除当前列顶部的方块,可以发现其仅对其左侧的方块产生影响。由于这个高度少了一个方块,所以左侧的每个方块都会多往右落一个位置,所以产生的额外贡献就是左侧的方块数。这个还是可以用线段树维护,范围加单点查即可。

注意是每次求累加和,所以要开 long long,警钟长鸣......

七、G. Drowning

byd 太变态了......

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct BIT{
    vector<T>tree;
    int n;

    BIT(){}

    BIT(int _n,T v=0){
        tree.resize(_n,v);
        n=_n-1;
    }

    int lowbit(int i){
        return i&-i;
    }

    void add(int i,T v){
        while(i<=n){
            tree[i]+=v;
            i+=lowbit(i);
        }
    }

    T sum(int i){
        T ans=0;
        while(i>0){
            ans+=tree[i];
            i-=lowbit(i);
        }
        return ans;
    }

    T query(int l,int r){
        if(r>n||l>r){
            return 0;
        }
        return sum(r)-sum(l-1);
    }

    //第k小
    int kth(int k){
        if(k<1||k>sum(n)){
            return 0;
        }
        
        int p=0;
        for(int i=1<<20;i;i>>=1){
            if(p+i<=n&&tree[p+i]<k){
                k-=tree[p+i];
                p+=i;
            }
        }
        return p+1;
    }
};

void solve()
{
    int n;
    cin>>n;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    vector<ll>pre(n+1);
    for(int i=1;i<=n;i++)
    {
        pre[i]=pre[i-1]+(i%2?1:-1)*a[i];
    }

    vector<ll>sorted(n+2);
    sorted[1]=0;
    for(int i=1;i<=n;i++)
    {
        sorted[i+1]=pre[i];
    }
    sort(sorted.begin()+1,sorted.end());
    int len=1;
    for(int i=2;i<=n+1;i++)
    {
        if(sorted[len]!=sorted[i])
        {
            sorted[++len]=sorted[i];
        }
    }

    auto rnk=[&](ll v)->int
    {
        int l=1;
        int r=len;
        int m;
        int ans;
        while(l<=r)
        {
            m=l+r>>1;
            if(sorted[m]>=v)
            {
                ans=m;
                r=m-1;
            }
            else
            {
                l=m+1;
            }
        }
        return ans;
    };

    for(int i=0;i<=n;i++)
    {
        pre[i]=rnk(pre[i]);
    }

    vector<BIT<ll>>bit(2,BIT<ll>(len+1));
    bit[0].add(pre[0],1);

    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        int id=i%2;

        if(id)
        {
            ans+=bit[id^1].query(1,pre[i]-1);
        }
        else
        {
            ans+=bit[id^1].query(pre[i]+1,len);
        }

        bit[id].add(pre[i],1);
    }
    cout<<ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

对于这种任意操作题,还是考虑从不变量入手分析。举 inf 个例子手玩可以发现,不管怎么操作,整个区间奇数位置的累加和减去偶数位置的累加和是不变的。

证明就是先定义区间交替和 。对于连续的三个数 ,令 ,那么如果可以进行操作的话必然有 。之后,若 x 在 S 中符号为正,那么结果就是 ,将 放回 S 中还是正数。而若 x 在 S 中符号为负,那么结果就是 ,将 放回 S 中还是负数。证毕。

也就是说,只有当子数组交替和大于 0 时,这个子数组才是好的。考虑维护交替和的前缀和 ,之后对于当前位置 i,就是找和其奇偶性不同的位置里小于 的个数。这个可以通过离散化后使用树状数组解决,注意奇偶位置分讨即可。

八、H. Fallen Leaves

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n;
    cin>>n;
    vector<vector<int>>g(n+1);
    vector<int>deg(n+1);
    for(int i=1,u,v;i<=n-1;i++)
    {
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
        deg[u]++;
        deg[v]++;
    }

    vector<int>leaf(n+1);
    for(int i=1;i<=n;i++)
    {
        leaf[i]=(deg[i]==1);
    }

    auto dfs=[&](auto &&self,int u,int fa)->void
    {
        for(auto v:g[u])
        {
            if(v!=fa)
            {
                self(self,v,u);

                leaf[u]+=leaf[v];
            }
        }
    };
    dfs(dfs,1,0);

    int ans=0;
    for(int i=2;i<=n;i++)
    {
        ans+=leaf[i]%2;
    }

    if(leaf[1]%2)
    {
        vector<int>d(n+1);

        auto dfs2=[&](auto &&self,int u,int fa)->void
        {
            for(auto v:g[u])
            {
                if(v!=fa)
                {
                    d[v]=d[u]+(leaf[v]%2?-1:1);
                    self(self,v,u);
                }
            }
        };
        dfs2(dfs2,1,0);

        int mn=INF;
        for(int i=1;i<=n;i++)
        {
            if(deg[i]==1)
            {
                mn=min(mn,d[i]);
            }
        }
        cout<<ans+mn<<endl;
    }
    else
    {
        cout<<ans<<endl;
    }
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

考虑计算每条边产生的贡献,即会被经过多少次。那么对于 (fa,u) 的这条边,若当前节点 u 的子树中有 个叶子,此时必然让其中的叶子两两配对,因为往上配对的话代价必然更高。所以,若当前子树内有偶数个叶子,那么就可以完美地两两配对。否则,即有奇数个叶子,那么就必然有一个叶子没法配对。此时这个叶子必然就需要经过 (fa,u) 这条边。

那么每次这样处理后,每条边被经过的次数的累加和,即总代价,就是以每个节点为根的子树内,叶子个数奇偶性的累加和。注意,此时统计时不包括根节点,因为若剩余一个叶子那么就天然无法完成匹配。

对于剩一个叶子的情况,考虑找出产生代价最大的叶子,然后删除这个叶子即可。若选择删掉叶子 x,那么其从根一直到自己整条路径上每个节点子树内叶子的数量都会发生变化。

考虑从上到下递推,定义 表示如果去掉 u 子树内的一个叶子节点,从根节点一直到节点 u 的这条路径上,所有边代价的变化量。那么对于 (u,v) 这条边,若原本 是奇数,删掉后就会少一点代价。而若是偶数,那么删掉后就会导致增加一点代价。每次去儿子前计算出 ,最后统计所有叶子节点的最小值,加上这个变化量即可。

总结

啥时候能 ak div3......

END

相关推荐
wanzehongsheng2 小时前
基于天文算法的双轴太阳能追踪系统:从原理到工程实现
算法
誰能久伴不乏2 小时前
ibmodbus “Invalid argument“ 错误的排查与修复
c++·qt·modbus
basketball6162 小时前
Kadane算法 C++实现
java·c++·算法
handler012 小时前
【C++】二叉搜索树详解及其模拟实现(代码)
开发语言·c++·算法·c··二叉搜索树·搜索树
luj_17682 小时前
残熵算法的稳健防灾逻辑
c语言·开发语言·c++·经验分享·算法
玖釉-2 小时前
二叉树基础详解:TreeNode、buildTree、deleteTree 与 printTree 的实现原理(C++)
c++·windows·算法
Severus_black2 小时前
【初阶数据结构与算法】八大排序之非比较排序(计数排序),一次性讲清!
数据结构·算法·排序算法
罗西的思考2 小时前
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (4)--- 系统架构
人工智能·算法·机器学习
QiLinkOS2 小时前
从技术到资产的跃迁:企业专利布局的深层逻辑
c语言·数据结构·c++·单片机·嵌入式硬件·算法·开源