2023牛客暑期多校训练营5「竞赛图+兰道定理」「dp」「拆位算贡献」

C-Cheeeeen the Cute Cat_2023牛客暑期多校训练营5 (nowcoder.com)

题意:

给定一个二分图,总边数为 n ∗ ( n − 1 ) 2 \frac{n*(n-1)}{2} 2n(n1),i与i+n不会互联,若i到j+n有一条边,则j到i+n不会有边,询问最大匹配数

有关芝士:

竞赛图:无向图的每一条边加上一个方向。

强连通分量(SCC):在有向图中的任意两点可以互相到达。

竞赛图性质:

  1. 竞赛图一定有哈密顿通路
  • 证:当竞赛图为DAG时显然可以拓扑,并且有唯一起点,考虑每一个点出队时,该点和所有没有出现过的点都有连边,当把该点的连边删掉时,剩下的还是一个竞赛DAG,如此递归可以找到唯一终点,即找到了通路;当竞赛图全图成环时,说明构成了一个哈密顿回路,显然存在哈密顿通路;当竞赛图既不是DAG又不是整环时,可以考虑缩点,将每一个小环都缩成点,完成之后该图会变成DAG,显然可以继续构造哈密顿通路,只需要在经过缩点时绕环一圈即可。
  1. 竞赛图有哈密顿回路的充要条件是强连通
  • 证:归纳法,当n=3时显然成立,当n=k成立时,k个点可以形成哈密顿回路,第k+1点如果想维持强连通,就需要该点对于前k个点的连边是两个方向的,如果前k个点对k+1点的连边都是出或入,显然不满足互相到达。在原k个点的哈密顿回路中,枚举每一条连边 u → v u \to v uv,一定存在一条连边可满足 u → k + 1 → v u\to k+1\to v uk+1v,如果所有的连边都不满足,说明前k个点对k+1点的连边都是单向的,与上述条件冲突。
  1. 竞赛图如果有环,最小环一定是三元环(一定会有)。
  • 证:如果存在多元环,环中每三个点都相互连边,必有三元环。(手玩四元环即可)
  1. 兰道定理:对n个点的出度/入度序列(s)升序排序,那么其能构成竞赛图的充要条件是,对于任意的k属于[1,n],都有 ∑ i = 1 n s i > = k ∗ ( k − 1 ) 2 \sum_{i=1}^ns_i>= \frac{k*(k-1)}{2} i=1nsi>=2k(k1),k=n时必取等。
思路:

如果把二分图转化为n*n的图,i向j+n连边即i向j连有向边,则该图会是一个竞赛图,且保证无自环,无二元环(两点之间有单边),所以完美匹配的最小答案一定是n-1,当该竞赛图强连通的时候会构成哈密顿回路,此时完美匹配会达到n。问题转化为如何判断该竞赛图是否强连通。(用Targan也可以过,但是我们要学糕端的trick。

竞赛图度数满足C(k,2)。scc首先是一个竞赛图,对于一个不可扩大的scc,他对余点的连边都是单向的,导致余点对他的出度/入度是无影响的,所以当出现第一个可取等C(k,2)的度数前缀时,说明遇到了一个不可扩大的scc,在竞赛图中有了一个不可扩大的scc,说明整张图一定不是scc。特别地,如果竞赛图是DAG,显然没有强连通,此时兰道定理的每一个i都可取等,即每个点都只能跟自己强连通。

出题人的意思是,兰道定理有一个性质,即每当遇到一个k可以取等时,说明再次之前的那一段是一个自己的强连通分量,如果一个竞赛图可以分成若干个scc那么就可以一个环一个环地匹配,但是如果出现了自己一个点是一个scc(其实是小于3个点成为scc,但题目保证不会出现二元环),那么就说明至少有一个点是无法这样匹配的,那么就可以走哈密顿通路匹配,保证最多只有一个点失配。

AC代码
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数

void work() {
    int n;cin>>n;
    vector<int>d(n+1);
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            char x;cin>>x;
            if(x=='1')d[i]++;
        }
    }
    sort(d.begin(),d.end());
    int sum=0;
    for(int i=1;i<n;++i){
        sum+=d[i];
        if(sum==i*(i-1)/2){
                cout<<n-1<<'\n';return;
        }
    }
    cout<<n<<'\n';
}

signed main() {
    io;
    int t=1;
    //cin >> t;
    while (t--) {
        work();
    }
    return 0;
}

H-Nazrin the Greeeeeedy Mouse_2023牛客暑期多校训练营5 (nowcoder.com)

题意:

有n块奶酪,每个体积为 a i a_i ai,价值为 b i b_i bi,m个背包,背包容量递增,每次可以顺着拿奶酪,但是面对一个奶酪如果不拿就毁掉,下次不能再拿,即奶酪必须按顺序拿,问最大价值。

注意:m范围比n大。

思路:

因为一个奶酪不能装到两个包里,所以真正有意义的背包只有后n个,01背包一下,另外需要dp一下每个包和前面的包的关系,一个包即使不拿东西,他也有前i-1个包的价值,需要把最大价值传递到最后一层。

dp[ i ] [ j ]表示第i个包的体积为j时,前i个包能装最高价值。

AC代码:
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
int dp[300][300];

void work() {
    int n,m;cin>>n>>m;
    vector<int>a(n+1),b(n+1);
    vector<int>sz(m+1);
    for(int i=1;i<=n;++i){
        cin>>a[i]>>b[i];
    }
    for(int i=1;i<=m;++i){
        cin>>sz[i];
    }
    if(n<m){
        sz.erase(sz.begin(),sz.end()-n-1);
        sz[0]=0;
        m=n;
    }
    for(int i=1;i<=n;++i){//枚举物品
        for(int j=1;j<=m;++j){//枚举包
            for(int k=sz[j];k>=a[i];--k){
                dp[j][k]=max(dp[j][k],dp[j][k-a[i]]+b[i]);
            }
        }

        for(int j=1;j<=m;++j){
            for(int k=0;k<=sz[j];++k){
                dp[j][0]=max(dp[j][0],dp[j-1][k]);
            }
        }
    }
    int ans=0;
    for(int i=1;i<=sz[m];++i){
        ans=max(ans,dp[m][i]);
    }
    cout<<ans<<'\n';
}

signed main() {
    io;
    int t=1;
    //cin >> t;
    while (t--) {
        work();
    }
    return 0;
}

E-Red and Blue and Green_2023牛客暑期多校训练营5 (nowcoder.com)

题意:

给你m组数据,每组数据{l,r,w}表示在[l,r]区间中逆序对的数量为奇(w=1)/偶(w=0),请构造一个n的排列p满足所有m对关系,若无法构造,输出-1。

思路:

按照区间大小排序,保证优先操作小区间

交换的方式:从l开始,若l没交换过,则l和l + 1 交换;若l已经交换过,则用ne[l] + 1 的数与 ne[l] 交换;初始ne[i]=i,每次遍历到一个区间都要把ne[l]=r,确保之后的操作不会影响到之前的区间。

不存在的情况有两种:

  1. l=r&&w=1
  2. 需要操作的时候ne[l]>=r
AC代码:
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 1e3+ 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
struct node{
    int l,r,w;
}a[N];
int ne[N],ans[N],cnt[N],an[N];
bool cmp(node x,node y){
    if(x.r-x.l==y.r-y.l)return x.l<y.l;
    return x.r-x.l<y.r-y.l;
}

void work() {
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=m;++i){
        cin>>a[i].l>>a[i].r>>a[i].w;
    }
    for(int i=1;i<=n;++i){
        ans[i]=ne[i]=i;cnt[i]=0;
    }
    sort(a+1,a+1+m,cmp);
    for(int i=1;i<=m;++i){
        int res=0;
        if(a[i].l==a[i].r&&a[i].w){
            cout<<"-1\n";return;
        }
        for(int j=a[i].l;j<=a[i].r;++j){
            res+=cnt[j];
        }
        if(res%2!=a[i].w){
            if(ne[a[i].l]>=a[i].r){
                cout<<"-1\n";return;
            }
            swap(ans[ne[a[i].l]],ans[ne[a[i].l]+1]);
            cnt[a[i].l]++;
        }
        ne[a[i].l]=a[i].r;
    }
    for(int i=1;i<=n;++i){
        an[ans[i]]=i;
    }
    for(int i=1;i<=n;++i){
        cout<<an[i]<<" \n"[i==n];
    }
}

signed main() {
    io;
    int t=1;
    //cin >> t;
    while (t--) {
        work();
    }
    return 0;
}

I-The Yakumo Family_2023牛客暑期多校训练营5 (nowcoder.com)

题意:

任选三段区间[l,r],求所有情况的异或和的积之和。

思路:

对于求某一段的 ∑ 1 < = l < r < = i i X O R ( l , r ) \sum_{1<=l<r<=i}^iXOR(l,r) 1<=l<r<=iiXOR(l,r),可以考虑前缀异或和(a[i]),对于某一段 X O R ( l , r ) = a [ r ] ⊕ a [ l − 1 ] XOR(l,r)=a[r]\oplus a[l-1] XOR(lr)=a[r]a[l1]

拆位计算。

在[l1,r1]中,对于第bit位,当且仅当 a [ r ] ⊕ a [ l − 1 ] = = 1 a[r]\oplus a[l-1]==1 a[r]a[l1]==1时会在当前位产生贡献( 2 b i t 2^{bit} 2bit),我们考虑固定某位r,即所有的以r为右边界的区间的贡献是0->r-1的a中这一位与r不同的数量(p[(a[i]>>bit&1)^1])乘上每次的贡献,遍历时也要顺便加上自己的贡献数量,我们可以找到所有位的贡献,求出S[i],表示以i为右边界的所有区间的贡献,对S求前缀和,可以得到T[r],表示1->r内所有区间的异或和

在[l2,r2]中,与上段唯一的不同在于每一位产生的贡献是 2 b i t ∗ T l 2 − 1 2^{bit}*T_{l2-1} 2bitTl21,所以在处理时,我们的贡献数量增加的不是1,而是加一次当前这位在上次T中的结果,每次有异或不同的位时,产生的贡献是之前所有的T之和乘当前位权。之后依次这样求即可得到答案。

AC代码:
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 2e5+ 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
ll a[N],s[N],T[N];

void work() {
    int n;cin>>n;
    for(int i=1;i<=n;++i){
        cin>>a[i];
        a[i]^=a[i-1];
    }
    for(int i=1;i<=n;++i){
        T[i]=1;//第一次加T[i]时应该加1,即只有自己的数量
    }
    for(int t=1;t<=3;++t){//枚举三轮
        for(int bit=0;bit<31;++bit){//先枚举位权
            ll p[2]={0,0};
            if(t==1){
                p[0]=1;//只有第一轮初始的0的数量为1
            }
            for(int i=1;i<=n;++i){
                s[i]=(s[i]+(p[(a[i]>>bit&1)^1]*1ll<<bit)%mod)%mod;
                p[(a[i]>>bit&1)]=(p[(a[i]>>bit&1)]+T[i])%mod;
            }
        }
        T[0]=0;
        for(int i=1;i<=n;++i){
            T[i]=(T[i-1]+s[i])%mod;s[i]=0;
        }
    }
    cout<<T[n]<<'\n';
}

signed main() {
    io;
    int t=1;
    //cin >> t;
    while (t--) {
        work();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值