2022“杭电杯”中国大学生算法设计超级联赛(7)

Black Magic 分类讨论

链接:Black Magic

题意

给出 n n n个方块,每个方块的左/右都可能是黑或白。将这些方块排成一列,如果两个相邻方块相连接的面都是黑色,那么这两个方块会连在一起。求连通块的最大/最小数量。 0 ≤ n ≤ 1 0 5       T ( 1 ≤ T ≤ 4 × 1 0 3 ) 0≤n≤10^5 ~~~~~T (1≤T≤4×10^3) 0n105     T(1T4×103)

分析:

最小数量:要尽可能的将 L , R L,R L,R凑成一对,对于凑不成的只能单独一个,因此直接找 m a x ( L , R ) max(L,R) max(L,R)即可, B B B可以放在 L / R L/R L/R的旁边就能形成一个。
最大数量 L L L L R R R R LLLLRRRR LLLLRRRR这样排列每个 L L L R R R都是独立的,如果有 B B B的话,可以选一个 B B B放在 L L L R R R的交界处,剩下的 B B B只能与 E E E交错着放。

#include<iostream>
using namespace std;
typedef long long ll;
void solve()
{
    int e,l,r,b;
    cin>>e>>l>>r>>b;
    int t=max(l,r);
    int minn;
    if(t==0)
        minn=e+min(1,b);
    else
        minn=t+e;
    int maxn;
    if(e==0)
        maxn=l+r+min(1,b);
    else
    {
        if(b<=2)
            maxn=l+r+b+e;
        else
        {
            e--;
            b-=2;
            maxn=l+r+3;
            if(b>e) maxn+=e*2;
            else maxn+=b+e;
        }
    }
    cout<<minn<<" "<<maxn<<endl;
}
signed main()
{
    int t;
    t=1;
    cin>>t;
    for(int i=1;i<=t;i++)
    {
        solve();
    }
    return 0;
}


Triangle Game 博弈论

链接:Triangle Game

题意:

给一个合法的三角形的三边长度,你和另一个人轮流让三角形的某边递减且仍然是三角形,如果某个人无法继续操作了就输了,你先手。 T ( 1 ≤ T ≤ 1 0 4 ) a , b , c ( 1 ≤ a , b , c ≤ 1 0 9 ) T (1≤T≤10^4) a,b,c (1≤a,b,c≤10^9) T(1T104)a,b,c(1a,b,c109)

分析:
这是一个NIM游戏的变形博弈论。
规律: 只有在 ( a − 1 ) (a-1) (a1) ^ ( b − 1 ) (b-1) (b1) ^ ( c − 1 ) ! = 0 (c-1)!=0 (c1)!=0的时候,先手必胜。
证明: 对任意的三角形三边 a , b , c a,b,c a,b,c,这里不妨假设 a > = b > = c a>=b>=c a>=b>=c,有 b + c > a b+c>a b+c>a
变形有 b − 1 + c − 1 > = a − 1 b-1 + c-1 >=a-1 b1+c1>=a1
为了方便,用 a b c abc abc取代公式里的 a − 1 , b − 1 , c − 1 a-1,b-1,c-1 a1b1c1,即 b + c > = a b+c>=a b+c>=a
假设 a a a ^ b b b ^ c ! = 0 c!=0 c!=0
那么一定存在一个数 x x x,使得 a a a ^ ( b − x ) (b-x) (bx) ^ c = 0 c=0 c=0,即 b − x = a b-x=a bx=a ^ c c c
又因为 a + c > = a a+c>=a a+c>=a ^ c > = a − c c>=a-c c>=ac(这就是为什么一开始要-1,取到>=的原因)
就有 a + c > = b − x > = a − c a+c>=b-x>=a-c a+c>=bx>=ac
可以发现在减掉一个 x x x之后三条边仍然满足三角形的性质且 a a a ^ ( b − x ) (b-x) (bx) ^ c = 0 c=0 c=0
这里证明的是 b + c > a b+c>a b+c>a,那么 a + c > b a+c>b a+c>b a + b > c a+b>c a+b>c也是类似的证明
那么留给对手的就是一个异或和等于0的状态,对手只会产生两种结果:
1.无法操作,输掉游戏
2.操作到异或和不等于 0 0 0的状态,那么我们就可以重复上面的操作,最终对方会输。
因此在 ( a − 1 ) (a-1) (a1) ^ ( b − 1 ) (b-1) (b1) ^ ( c − 1 ) ! = 0 (c-1)!=0 (c1)!=0是一个必胜状态。

#include<iostream>
#include<cstring>
#include<algorithm>
//#define int long long
using namespace std;
typedef long long ll;
void solve()
{
    int a,b,c;
    cin>>a>>b>>c;
    if((a-1)^(b-1)^(c-1)) puts("Win");
    else puts("Lose");
}
signed main()
{
    int t;
    t=1;
    cin>>t;
    for(int i=1;i<=t;i++)
    {
        solve();
    }
    return 0;
}

Counting Stickmen 组合数学

链接:Counting Stickmen

题意:

给你一棵树,求这棵树上形成的火柴人的形状有多少个,只要两个火柴人存在两个点不一样,就认为这两个火柴人是不同的。 T ( 1 ≤ T ≤ 15 ) n ( 1 ≤ n ≤ 5 × 1 0 5 ) T (1≤T≤15) n (1≤n≤5×10^5) T(1T15)n(1n5×105)
图中红色的就是一个火柴人, 3 3 3是身子, 2 2 2是头, 5 , 7 , 8 5,7,8 578是下半身, 4 , 6 4,6 46 9 , 10 9,10 910是俩胳膊

分析:
只要找出来火柴人的核心即可,方法有很多。相对容易的就是找连接头,胳膊和腿的点为核心,例如上图中的3号点,只需要预处理出来每个点作为核心点所形成的胳膊数量即可,头的个数很好找(3号点的邻接点数量-3),在枚举每条边的时候会枚举出来核心点和腿,最后用乘法原理就能实现。注意要减去腿对核心点形成胳膊的贡献。

#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
typedef long long ll;
const int N=5e5+10,M=2*N,mod=998244353;
int cnt[N],ng[N],ngc[N];
int e[M],ne[M],h[N],idx;
int n;
struct Edge
{
    int a,b;
}edge[N];
void add(int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
int res;
int calc(int x)
{
    return x*(x-1)/2%mod;
}
void solve()
{
    idx=res=0;
    cin>>n;
    for(int i=1;i<=n;i++)
        h[i]=-1,cnt[i]=0,ng[i]=0,ngc[i]=0;
    for(int i=1;i<n;i++)
    {
        int a,b;
        scanf("%d %d",&a,&b);
        edge[i]={a,b};
        add(a,b);
        add(b,a);
        cnt[a]++;
        cnt[b]++;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=h[i];j!=-1;j=ne[j])
        {
            int u=e[j];
            ng[i]+=cnt[u]-1;
            ngc[i]=(ngc[i]+calc(cnt[u]-1))%mod;
        }
        //cout<<i<<" "<<ng[i]<<" "<<ngc[i]<<endl;
    }
    for(int i=1;i<n;i++)
    {
        int x=edge[i].a,y=edge[i].b;
        int foot=calc(cnt[y]-1);
        int hand=calc(ng[x]-cnt[y]+1);
        hand=hand-ngc[x]+calc(cnt[y]-1);
        int head=cnt[x]-3;
       // if(hand<0||head<0||foot<0);
       // else
        res=(res+head*hand%mod*foot%mod)%mod;
      //  cout<<x<<" "<<y<<"hand"<<hand<<"head"<<head<<"foot"<<foot<<endl;

        x=edge[i].b,y=edge[i].a;
        foot=calc(cnt[y]-1);
        hand=calc(ng[x]-cnt[y]+1);
        hand=hand-ngc[x]+calc(cnt[y]-1);
        head=cnt[x]-3;
      //  if(hand<0||head<0||foot<0);
      //  else
        res=(res+head*hand%mod*foot%mod)%mod;
      //  cout<<x<<" "<<y<<"hand"<<hand<<"head"<<head<<"foot"<<foot<<endl;
    }
    cout<<res<<endl;
}
signed main()
{
    int t;
    t=1;
    cin>>t;
    for(int i=1;i<=t;i++)
    {
        solve();
    }
    return 0;
}

Independent Feedback Vertex Set 三染色

链接:Independent Feedback Vertex Set

题意:

给一个图有 n n n个点,每个点都有权值,图初始是由三个顶点组成 1 , 2 , 3 1,2,3 1,2,3和三个边 ( 1 , 2 ) , ( 2 , 3 ) , ( 3 , 1 ) (1,2), (2,3), (3,1) (1,2),(2,3),(3,1)组成的一个三元环,之后会再给出 n − 3 n-3 n3行,每行给出两个点 u , v u,v uv,表示连接 ( i + 3 , u ) (i+3,u) (i+3,u) ( i + 3 , v ) (i+3,v) (i+3,v)的两条边,保证给出的 u , v u,v uv是有边的。给出图之后,让你将这个图分成森林和独立集,且独立集中顶点的权值之后最大。

分析:

可以发现这个图是由许多三元环构成的,要想变成森林还要构成独立集的话,那么每个环必须且只能分出一个点。
因为一个点都不选则会破坏森林约束,选两个则会破坏独立集约束。
可以发现,在初始的三元环中选出一个点的话,那么后面选出的点是唯一且固定的,也就是说该图的三染色方案是唯一的,最后找三染色图的哪个色的权值和最大即可。

#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
typedef long long ll;
const int N=1e5+10,M=2*N,mod=998244353;
int w[N],color[N];
int n;
void solve()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        color[i]=0;
        scanf("%lld",&w[i]);
    }
    for(int i=1;i<=3;i++) color[i]=i;
    for(int i=4;i<=n;i++)
    {
        int a,b;
        scanf("%lld %lld",&a,&b);
        color[i]=6-color[a]-color[b];
    }
    int res[4]={0,0,0,0};
    for(int i=1;i<=n;i++)
        res[color[i]]+=w[i];
    cout<<max(res[1],max(res[2],res[3]))<<endl;
}
signed main()
{
    int t;
    t=1;
    cin>>t;
    for(int i=1;i<=t;i++)
    {
        solve();
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值