练习题解2

1. Bertown roads

链接 https://codeforces.com/problemset/problem/118/E

tag

dfs树

题意

n ( 1 < = n < = 1 e 5 ) n(1<=n<=1e5) n(1<=n<=1e5)个点, m ( 1 < = m < = 1 e 5 ) m(1<=m<=1e5) m(1<=m<=1e5)条双向边,问能否让每条边变得单向使得图是一个强连通分量。如果能则输出方案。

分析

考虑dfs树,如果存在桥就是存在点没有被返祖边覆盖,根据定义,不妨记桥两边的点为 u , v u,v u,v, 如果u能到v,那么v就不存在别的路径到u,显然不符合题意的要求,否则每个点都至少被一条返祖边覆盖,我们可以构造令dfs树的树边往下连,而返祖边往上连即可满足要求。

参考代码

#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;

int n,m,k;
vector<tuple<int,int,int>> g[N];
vector<pair<int,int>> t;
int f[N];
int dfn[N],low[N],tot;

bool dfs(int u,int fa)
{
    dfn[u]=low[u]=++tot;
    // cout<<u<<" "<<fa<<"\n";
    for(auto [j,x,c]:g[u])
    {
        if(j==fa) continue;
        if(!dfn[j])
        {
            if(!dfs(j,u)) return false;
            if(!f[x]) f[x]=c;
            low[u]=min(low[u],low[j]);
            if(low[j]>dfn[u]) return false;      // 存在桥
        } else 
        {
            low[u]=min(low[u],dfn[j]);
            if(!f[x]) f[x]=c;
        }
    }
    return true;
}

void slove()
{
    cin>>n>>m;
    rep(i,0,m)
    {
        int a,b; cin>>a>>b;
        g[a].emplace_back(b,i,1);
        g[b].emplace_back(a,i,-1);
        t.emplace_back(a,b);
    }
    // dfs(1,0);
    // cout<<"head\n";
    // rep(i,1,n+1) cout<<dfn[i]<<" "<<low[i]<<"\n";
    // return ;
    if(!dfs(1,0)) cout<<"0\n";
    else 
    {
        rep(i,0,m)
        {
            auto [a,b]=t[i];
            if(f[i]<0) swap(a,b);
            cout<<a<<' '<<b<<"\n";
        }
    }
}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false); 
    int _=1;
    // cin>>_;
    while(_--) slove();
    return 0;
}

2. 数码

链接 https://ac.nowcoder.com/acm/problem/13221

tag

整数分块

题意

给定两个整数 l 和 r ,对于所有满足$1 ≤ l ≤ x ≤ r ≤ 10^9 $的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。

分析

枚举每个约数的贡献,则答案为 ∑ i = 1 n i ∗ n i \sum_{i=1}^n i * \dfrac{n}{i} i=1niin,但n很多显然直接枚举不行,注意到右边是经典的整数分块形式,我们考虑枚举右边,则对于特定的数码x,一块区间 [ l , r ] [l,r] [l,r]的贡献是区间 [ x , x + 1 ) , [ x ∗ 10 , ( x + 1 ) ∗ 10 ) , x [ ∗ 100 , ( x + 1 ) ∗ 100 − 1 ) . . . [x,x+1), [x*10,(x+1)*10), x[*100,(x+1)*100-1)... [x,x+1),[x10,(x+1)10),x[100,(x+1)1001)... [ l , r ] [l,r] [l,r]的区间交。

参考代码

#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;

int n,m,k;
ll ans[N];

ll get(int x,int up)
{
    ll res=0;
    for(int l=1,r;l<=up;l=r+1)
    {
        r=up/(up/l);
        ll s=0;
        for(ll a=x,b=x+1;a<=up;a*=10,b*=10)
        {
            s+=max<ll>(0,min<ll>(b-1,r)-max<ll>(a,l)+1);
        }
        res+=s*(up/r);
    }
    return res;
}

void slove()
{
    int l,r; 
    cin>>l>>r;
    rep(i,1,10)
    {
        // cout<<get(i,r)<<" "<<get(i,l-1)<<"\n";
        cout<<(get(i,r)-get(i,l-1))<<"\n";
    }

}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false); 
    int _=1;
    // cin>>_;
    while(_--) slove();
    return 0;
}

3. Paint Box

链接 https://ac.nowcoder.com/acm/problem/13884

tag

组合数学,容斥定理

题意

n个盒子,从m种颜色中选出k进行染色,要求相邻的不能相同颜色,问染色结果恰有k种颜色的方案数。 ( 1 ≤ n , m ≤ 1 0 9 1 ≤ k ≤ 1 0 6 , k ≤ n ,   m ) (1≤n,m≤10^9 1≤k≤10^6, k≤n,\ m) (1n,m1091k106,kn, m)

分析

从m种颜色中选择 C ( m , k ) C(m,k) C(m,k),然后进行染色的方案数 k ∗ ( k − 1 ) n − 1 k*(k-1)^{n-1} k(k1)n1,但是这样表示的是<=k种颜色的方案数且有重复的部分,我们考虑二项式反演,令 x n x_n xn为<=n种颜色的方案数, y n y_n yn为恰好n种颜色的方案数,则
x n = k ∗ ( k − 1 ) n − 1 = ∑ i = 1 n C ( n , i ) ∗ y i x_n=k*(k-1)^{n-1}=\sum_{i=1}^n C(n,i)*y_i xn=k(k1)n1=i=1nC(n,i)yi,由二项式反演得到, y n = ∑ i = 1 n ( − 1 ) n − i ∗ C ( n , i ) ∗ x i y_n=\sum_{i=1}^n (-1)^{n-i}*C(n,i)*x_i yn=i=1n(1)niC(n,i)xi, y k y_k yk即为所求

参考代码

#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;

int n,m,k;

int add(int x,int y) {
    x+=y;
    return x<0?x+mod:x>=mod?x-mod:x;
}

int mul(int x,int y) {
    return 1ll*x*y%mod;
}

struct Bio {
    int fac[N],inv[N];
    int _n;
    int ksm(int a,int b,int res=1) {
        for(;b;b>>=1,a=1ll*a*a%mod) 
            if(b&1) res=1ll*res*a%mod;
        return res;
    }
    Bio(int n=N-1) : _n(n) {
        fac[0]=inv[0]=1;
        for(int i=1;i<=_n;++i) fac[i]=fac[i-1]*1ll*i%mod;
        inv[_n]=ksm(fac[_n],mod-2);
        for(int i=_n-1;i;--i) inv[i]=inv[i+1]*1ll*(i+1)%mod;
    }
    int C(int a,int b) {
        if(a<b||b<0) return 0;
        return 1ll*fac[a]*inv[b]%mod*inv[a-b]%mod; 
    }
} b; 

void slove()
{
    cin>>n>>m>>k;
    int res=0;
    for(int i=k,p=1;i;--i,p*=-1)
    {
        int t=mul(b.C(k,i), mul(i, b.ksm(i-1, n-1)));
        res=add(res, mul(p, t));
    }
    for(int i=1,j=m;i<=k;++i,--j)
        res=mul(res, mul(j, b.ksm(i, mod-2)));
    cout<<res<<"\n";
}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false); 
    int _=1;
    cin>>_;
    while(_--) slove();
    return 0;
}

4. Vasya and Good Sequences

链接 https://codeforces.com/problemset/problem/1030/E

tag

dp

题意

给定一个数组,每次操作可以将一个数的某一位 1 1 1 移动到这个数的别的位上,你可以选择一个区间对于区间中的数可以使用任意多次操作,如果操作结束后这个区间的异或和为0,则为好区间,问有多少 ( 1 ≤ n ≤ 3 ∗ 1 0 5 ) (1≤n≤3*10^5) (1n3105) ( 1 ≤ a i ≤ 1 0 18 ) (1≤ai≤10^{18}) (1ai1018)

分析

如果这一段异或和为0那么每一个1都要有另一个1都会抵消,所以这一段的1的数量要偶数,然后手玩一下发现如果某个值大于这段别的1的数量之和也不行,然后考虑维护这个结论,前者可以通过开辟数组记忆化实现,而后者因为 ( 1 ≤ a i ≤ 1 0 18 ) (1≤ai≤10^{18}) (1ai1018) 所以对于某个数作为最大值,这些有矛盾的区间长度<=65,我们暴力枚举这个长度。

参考代码

#include<bits/stdc++.h>
#define ls (u<<1)
#define rs (u<<1|1)
#define mid (l+r>>1)

#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=19,mod=1e9+7;
const int N=1<<bit|7;

int n,m,k;
ll c[N],a[N];
int cnt[N][2];

void slove()
{
    cin>>n;
    rep(i,1,n+1) 
    {
        cin>>a[i];
        c[i]=__builtin_popcountll(a[i]);
    }
    ll res=0,p=0;
    cnt[0][0]=1;
    for(int i=1;i<=n;++i)
    {
        ll s=c[i],mx=c[i];
        p^=s&1;
        for(int j=i-1;j>max(i-65,0);--j)   // 处理最大值
        {
            s+=c[j],mx=max(mx,c[j]);
            if(s%2==0&&mx<=s-mx) res++;
        }
        if(i>=66) res+=cnt[i-66][p];
        rep(j,0,2) cnt[i][j]=cnt[i-1][j];
        cnt[i][p]++;
        // cout<<i<<" "<<c[i]<<" "<<res<<"\n";
    }
    cout<<res<<"\n";
}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false); 
    int _=1;
    // cin>>_;
    while(_--) slove();
    return 0;
}

5. Moovie Mooving G

链接 https://www.luogu.com.cn/problem/P3118

tag

状态压缩

题意

给一个时间T,和n部电影,每部电影会有一个固定的持续时间和多个开始时间,现你要从时间0开始连续看电影,不能看同一部电影,问最少要看多少部电影才能到时间T。 1 < = T < = 1 0 9 ,   1 < = n < = 20 1<=T<=10^9 ,\ 1<=n<=20 1<=T<=109, 1<=n<=20

分析

状态压缩维护最大值,而对于要枚举的电影,如果暴力枚举决策,会超时,贪心考虑应当选择最大的小于当前度过的时间,如果 t i , j + d [ i ] < = n o w t_{i,j}+d[i]<=now ti,j+d[i]<=now显然选择后不优,我们用 s t d : : s e t std::set std::set维护有序序列查找所需的值。

参考代码

#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;

int n,m,k;
int a[N];
set<int> s[N];
ll f[N];

void slove()
{
    cin>>n>>k;
    rep(i,0,n)
    {
        cin>>a[i];
        cin>>m;
        while(m--) 
        {
            int val; cin>>val;
            s[i].emplace(val);
        }
    }

    for(int i=1;i<1<<n;++i)
    {

        for(int j=0;j<n;++j)
        {
            if(i>>j&1)
            {
                ll t=f[i^(1<<j)];
                auto it=s[j].upper_bound(t);
                if(it==s[j].begin()) continue;
                // cout<<i<<" "<<(*it)<<' '<<t<<' '<<j<<"\n";
                it--;
                // cout<<(*it)<<"\n";
                t=a[j]+*it;
                f[i]=max(f[i],t);
            }
        }
    }

    int res=n+1;
    rep(i,0,1<<n) 
        if(f[i]>=k) res=min(res,__builtin_popcount(i));
    if(res==n+1) res=-1;
    cout<<res<<'\n';
}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false); 
    int _=1;
    // cin>>_;
    while(_--) slove();
    return 0;
}

6. 二分图染色

链接 https://ac.nowcoder.com/acm/problem/13229

tag

dp、容斥

题意

给定一个完全二分图,图的左右两边的顶点数目相同。现要给图中的每条边染成红色、蓝色、或者绿色,并使得任意两条红边不共享端点、同时任意两条蓝边也不共享端点。
计算所有满足条件的染色的方案数,并对 1 0 9 + 7 10^9+7 109+7取模。 1 < = n < = 1 0 7 1<=n<=10^7 1<=n<=107

分析

首先考虑只有红色和绿色,令 f [ n ] f[n] f[n]为点数为n的方案数,我们可以看成一个nn的矩阵一开始都是绿色且一行或者一列最多一个红色,由n-1向n转移,如果不填红色f[n]=f[n-1],否则若只有一个红色,有2n-1个格子选择,f[n]=f[n-1](2n-1),但是这样会有重复,重复的部分为从(n-1)*(n-1)*f[n-2]。然后考虑蓝色,如果没有两者不会占据同一个格子,根据乘法原理为f[n]*f[n]但有重叠矛盾的,考虑容斥去重,对于枚举哪些行有矛盾,则这些只能选一种颜色,即 A ( n , i ) A(n,i) A(n,i) 则答案为 ∑ i = 0 n ( − 1 ) i C ( n , i ) ∗ A ( n , i ) ∗ f [ n − i ] 2 \sum_{i=0}^{n} (-1)^{i} C(n, i)*A(n,i) *f[n-i]^2 i=0n(1)iC(n,i)A(n,i)f[ni]2

参考代码

#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1e7+10;

int n,m,k;

int add(int x,int y) {
    x+=y;
    return (x%mod+mod)%mod;
}

int mul(int x,int y) {
    return 1ll*x*y%mod;
}

struct Bio {
    int fac[N],inv[N];
    int _n;
    int ksm(int a,int b,int res=1) {
        for(;b;b>>=1,a=1ll*a*a%mod) 
            if(b&1) res=1ll*res*a%mod;
        return res;
    }
    Bio(int n=N-1) : _n(n) {
        fac[0]=inv[0]=1;
        for(int i=1;i<=_n;++i) fac[i]=fac[i-1]*1ll*i%mod;
        inv[_n]=ksm(fac[_n],mod-2);
        for(int i=_n-1;i;--i) inv[i]=inv[i+1]*1ll*(i+1)%mod;
    }
    int C(int a,int b) {
        if(a<b||b<0) return 0;
        return 1ll*fac[a]*inv[b]%mod*inv[a-b]%mod; 
    }
    int A(int a,int b) {
        if(a<b||b<0) return 0;
        return 1ll*fac[a]*inv[a-b]%mod; 
    }
} b; 


int f[N];

void slove()
{
    cin>>n;
    f[0]=1,f[1]=2;
    rep(i,2,n+1)
        f[i]=add(mul(2*i, f[i-1]), -mul(mul(i-1, i-1), f[i-2]));

    int res=0;
    for(int i=0,p=1;i<=n;++i,p*=-1)  //  容斥
    {
        int t=mul(b.C(n,i), mul(b.A(n,i), mul(f[n-i], f[n-i])));
        res=add(res, mul(p, t));
    }
    cout<<res<<"\n";
}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false); 
    int _=1;
    // cin>>_;
    while(_--) slove();
    return 0;
}

7. PUS

链接 https://www.luogu.com.cn/problem/P3588

tag

线段树,拓扑排序,贪心

题意

给定一个长度为 n n n ( 1 < = n < = 1 0 5 ) (1<=n<=10^5) (1<=n<=105)的正整数序列 ,每个数都在 1 1 1 1 0 9 10^9 109 范围内,告诉你其中 s s s ( 1 < = s < = 1 0 5 ) (1<=s<=10^5) (1<=s<=105)个数,并给出 m   ( 1 < = m < = 2 ∗ 1 0 5 ) m\ (1<=m<=2*10^5) m (1<=m<=2105) 条信息,每条信息包含三个数 l   r   k l\ r\ k l r k以及接下来 k k k个正整数,表示 a l   a l + 1   . . . a r a_l\ a_{l+1}\ ... a_r al al+1 ...ar里这 k k k个数中的任意一个都比任意一个剩下的 r − l − k + 1 r-l-k+1 rlk+1个数大(严格大于,即没有等号)。
请任意构造出一组满足条件的方案,或者判断无解。

分析

差分约束问题,因为边权都是正的或者都是负的,我们可以拓扑排序实现,每个数都有上下界,可以用拓扑排序维护最小值或者维护最大值,我们这里实现的是初始化最大值,同时过程中维护最大值。分析时间复杂度,如果暴力建图,那么边数是 m 3 m^3 m3,我们考虑虚拟源点优化,对于一组限制,将这k个点向虚拟源点连一条边权为1的边,同时虚拟源点对剩下的区间连一条边权为0的边,因为是区间连边,所以可以考虑线段树优化建图,最后在跑拓扑排序的时候,注意细节特判。

参考代码

#include<bits/stdc++.h>
#define ls (u<<1)
#define rs (u<<1|1)
#define mid (l+r>>1)

#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;

int n,m,k;
vector<pair<int,int>> g[N];
int lef[N],in[N],tot;
bool st[N];
ll dis[N];

void build(int u,int l,int r)
{
    tot=max(tot,u);
    if(l==r) return lef[l]=u,void();
    g[u].emplace_back(ls,0);
    g[u].emplace_back(rs,0);
    in[ls]++,in[rs]++;
    build(ls,l,mid),build(rs,mid+1,r);
}

void modify(int u,int l,int r,int ql,int qr,int v,int w)
{
    if(ql<=l&&qr>=r)
    {
        g[v].emplace_back(u,w);
        in[u]++;
        return ;
    }
    if(ql<=mid) modify(ls,l,mid,ql,qr,v,w);
    if(qr>mid) modify(rs,mid+1,r,ql,qr,v,w);
}

bool top()
{
    queue<int> q;
    int cnt=0;
    rep(i,1,tot+1)
    {
        if(!st[i]) dis[i]=1e9;
        if(!in[i]) 
            q.emplace(i);
    }
    while(q.size())
    {
        int t=q.front();
        q.pop();

        if(dis[t]<1) return false;
        cnt++;         

        for(auto [j,w]:g[t])
        {
            ll to=dis[t]+w;
            if(!st[j]) dis[j]=min(dis[j],to);
            else if(dis[j]>to) return false;
            if(--in[j]==0) q.emplace(j);
        }
    }
    return cnt==tot;
}

void slove()
{
    int s;
    cin>>n>>s>>m;
    build(1,1,n);
    while(s--)
    {
        int id,val; cin>>id>>val;
        id=lef[id];
        st[id]=true;
        dis[id]=val;
    }

    while(m--)
    {
        int l,r,k;
        cin>>l>>r>>k;
        tot++;              //  建立虚拟源点
        int p=l;
        while(k--)
        {
            int x; cin>>x;
            g[lef[x]].emplace_back(tot,-1);
            in[tot]++;
            if(p<=x-1) modify(1,1,n,p,x-1,tot,0);
            p=x+1;
        }
        if(p<=r) modify(1,1,n,p,r,tot,0);
    }
    // cout<<tot<<"\n";
    if(!top()) cout<<"NIE\n";
    else {
        cout<<"TAK\n";
        rep(i,1,n+1) cout<<dis[lef[i]]<<" \n"[i==n];
    }
}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false); 
    int _=1;
    // cin>>_;
    while(_--) slove();
    return 0;
}

8. 树上游戏

链接 https://www.luogu.com.cn/problem/P2664

tag

dsu

题意

给你一棵树,每个点都一种颜色,定义 s ( i ,   j ) s(i,\ j) s(i, j)为i到j路径上颜色种类的数量,现求 s u m i = ∑ j = 1 n s ( i ,   j ) sum_i = \sum_{j=1}^{n} s(i,\ j) sumi=j=1ns(i, j)

分析

首先对于只有一种颜色分析,我们将为当前颜色的点除去,这样树变成了多个连通块,对于每个连通块的点的贡献就是n-它所属的连通块的大小,对于多种颜色,如果暴力枚举显然不行,考虑到只有n个点,我们处理每个点对子树的贡献,可以用树上差分维护,同时开辟一个数组维护每种颜色的极大连通块,因为是树上差分操作,我们再维护他的dfs序来查询子树中同种颜色的位置,最后特殊处理根节点把所有颜色的贡献加到根节点上。

参考代码

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=20;   
const int N=1<<bit|7;
        
int n,m,k;
vector<int> g[N];
ll a[N],sz[N],cs[N],f[N];        //  cs数组维护该子树的极大连通块
int dfn[N],cnt;
vector<int> w[N];

void dfs(int u,int fa)
{
    sz[u]=1;
    dfn[u]=++cnt;
    for(auto j:g[u])
    {
        if(j==fa) continue;
        ll t=cs[a[u]];                  //  记录原先的贡献
        dfs(j,u);
        sz[u]+=sz[j];
        ll s=sz[j]-(cs[a[u]]-t);            //  当前节点的贡献是当前子树大小-新增的贡献
        f[j]+=s;
        cs[a[u]]+=s;
        while(w[a[u]].size()&&dfn[w[a[u]].back()]>dfn[u])    //  处理子树
            f[w[a[u]].back()]-=s,w[a[u]].pop_back();
    }
    cs[a[u]]++;
    w[a[u]].emplace_back(u);
}

void dfs2(int u,int fa)
{
    f[u]+=f[fa];
    for(auto j:g[u])
        if(j!=fa) dfs2(j,u);
}

void slove()
{   
    cin>>n;
    map<int,int> h;
    rep(i,1,n+1) cin>>a[i],h[a[i]]=1;
    rep(i,1,n)
    {
        int a,b; cin>>a>>b;
        g[a].emplace_back(b);
        g[b].emplace_back(a);
    }
    dfs(1,0);
    for(auto [sa,_]:h)          //  特殊处理根节点使得包含所有颜色的贡献
    {
        f[1]+=n-cs[sa];
        for(auto u:w[sa])
            f[u]-=n-cs[sa];
    }

    dfs2(1,0);
    m=h.size();
    rep(i,1,n+1) cout<<1ll*n*m-f[i]<<"\n";
}   

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false);
    cout<<fixed<<setprecision(8); 
    int _=1;
    // cin>>_;
    while(_-->0) slove();
    return 0;
}


9. Accumulation Degree

链接 https://ac.nowcoder.com/acm/problem/51180

tag

换根dp

题意

给你一棵树,你可以选择一个点作为根,然后流量从根出发,流到每片叶子,路径上有最大流量限制,结果为所有叶子最后得到的流量之和,问你最多能得到多少流量。 2 < = n < = 1 0 5 2<=n<=10^5 2<=n<=105

分析

显然答案与根有关,所以会有一个换根dp,我们首先分析对与一个有根树怎么做,如果从根出发自上而下, 那么某个点分配流量的时候会出现问题,又因为流量或者说边权比较大,所以强行dp是不行的,我们从叶子出发,对于某个叶子的父亲贪心的做给他的流量就是他到叶子的边权,而对于这个点的父亲,流量又限制于它到父亲的边权,我们要对两者取最小值,这样我们得到dp的方程,设当前节点是u,儿子是v,边权是w,如果v是叶子,则 f [ u ] + = w f[u]+=w f[u]+=w否则
f [ u ] + = m i n ( f [ v ] , w ) f[u]+=min(f[v],w) f[u]+=min(f[v],w),然后考虑换根,我们把某个子树的贡献消去,并将当前子树的贡献加到另一颗子树的贡献也是和上面一样的方程。具体可以参考代码。

参考代码

#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=200010;

int n,m,k;
vector<pair<int,ll>> g[N];
ll f[N],res;

bool dfs(int u,int fa)
{
    f[u]=0;
    if(g[u].size()==1&&fa) return true;       //  判断是否是叶子

    for(auto [j,w]:g[u])
    {
        if(j==fa) continue;
        if(dfs(j,u)) f[u]+=w;
        else f[u]+=min<ll>(f[j],w);
    }
    return false;
}

void dfs1(int u,int fa)
{
    res=max(f[u],res);
    for(auto [j,w]:g[u])
    {
        if(j==fa) continue;
        if(g[j].size()==1) f[u]-=w;   //  如果j是叶子
        else f[u]-=min<ll>(f[j],w);
        ll t;
        if(g[u].size()==1) t=w;    //  如果 u 是叶子
        else t=min<ll>(f[u],w);
        f[j]+=t;
        dfs1(j,u);
        f[j]-=t;
        if(g[j].size()==1) f[u]+=w;
        else f[u]+=min<ll>(f[j],w);
    }
}

void slove()
{
    cin>>n;
    assert(n>1);
    rep(i,1,n+1) g[i].clear();
    res=0;
    rep(i,1,n)
    {
        int a,b,c; cin>>a>>b>>c;
        g[a].emplace_back(b,c);
        g[b].emplace_back(a,c);
    }
    dfs(1,0);
    dfs1(1,0);
    cout<<res<<"\n";
}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false); 
    int _=1;
    cin>>_;
    while(_--) slove();
    return 0;
}

10. Unfair Nim

链接 https://atcoder.jp/contests/abc172/tasks

tag

博弈,分类讨论

题意

给你一个长度为 n n n的数组 2 < = n < = 300 2<=n<=300 2<=n<=300,两人进行nim游戏,游戏开始前第一个人可以将第一堆的部分石子移动到第二堆,但不能全部移完,问最少移动多少石子使得第一个人必胜,如不能则输出 − 1 -1 1;

分析

考虑经典nim游戏,必胜为异或和不为0,则设移动后的第一堆的数量为x,第二堆的数量为y,后面的异或和为k,则 x + y = a [ 1 ]   +   a [ 2 ] x+y=a[1]\ +\ a[2] x+y=a[1] + a[2] x ⨁ y = k x\bigoplus y=k xy=k,一个加法一个异或,我们发现难以分析,考虑公式 a + b = ( a ⨁ b ) + 2 ∗ ( a ⨂ b ) a+b = (a\bigoplus b) + 2*(a \bigotimes b) a+b=(ab)+2(ab),同时令 ( u = a [ 1 ] + a [ 2 ] − k ) / 2 (u=a[1]+a[2]-k)/2 (u=a[1]+a[2]k)/2,则问题等价于 x ⨂ y = u ,   x ⨁ = k x\bigotimes y = u,\ x\bigoplus =k xy=u, x=k找到尽量大的x,这样我们可以讨论每一位来处理问题。
首先处理变形中的问题,如果u<k或者u-k不能被2整除就是无解。
基于与运算则u有的位x也要有,初始化x=u; 然后从高位开始分析,
如果某一位u,k都有那么显然无解,如果都无,则我们也不能选这一位。
如果u有,k无,那么这一位两个都要有1;
如果u无,k有,那么这一位只能有一个有1,我们基于贪心如果加上这一位有x<=a[1],那就加上否则不加。

参考代码

#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;

typedef long long ll;
const int bit=20,mod=1e9+7;
const int N=1<<bit|7;

int n,m,k;
ll a[N];

void slove()
{
    cin>>n;
    ll y=0;
    rep(i,0,n)
    {
        cin>>a[i];
        if(i>1) y^=a[i];
    }
    ll x=a[0]+a[1];
    bool nic=true;
    ll res=0;
    if(x<y) nic=false;
    else 
    {
        if((x-y)%2) nic=false;
        x=(x-y)/2;
        res=x;
        for(int i=60;~i;--i)
        {
            int sx=x>>i&1,sy=y>>i&1;
            if(sx&&sy) nic=false;
            else if(!sx&&!sy) ;
            else if(sx&&!sy) ;
            else
            {
                if((res|(1ll<<i))<=a[0]) res|=1ll<<i;
            }
        }
        nic&=!!res;                 //  处理初始化边界问题
        nic&=res<=a[0];
    }

    if(nic) cout<<a[0]-res<<"\n";
    else cout<<"-1\n";
}

signed main()
{
    cin.tie(nullptr)->ios::sync_with_stdio(false); 
    int _=1;
    // cin>>_;
    while(_--) slove();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值