• 2022杭电多校第七场题解


    2022杭电多校第七场

    在这里插入图片描述

    Independent Feedback Vertex Set(思维)

    题意
    将一个无向图 G G G的顶点 V V V分成两个互补子集 V I V_I VI V F V_F VF,其中 V I V_I VI是独立集,诱导子图 G [ V F ] G[V_F] G[VF]是森林。初始状态下,图 G G G是含有三个顶点的完全图,之后会添加若干个顶点,添加的顶点会向图 G G G中存在的一条边的两个顶点各连一条边。图 G G G的每个顶点都有一个权值,求 V I V_I VI中顶点权值和的最大值。
    森林:无环的无向图。
    独立集:图中的一个顶点集,对于图中任意两个顶点,没有连接这两个顶点的边。

    分析
    图上每增加一个顶点,相当于增加了一个三元环,因此图 G G G是由若干个三元环构成。对于每一个三元环,至少要选择一个顶点作为独立集中的顶点,否则会破坏森林约束,选两个及以上的点会破坏独立集约束,于是每个三元环必须选择一个点作为独立集中的顶点。如果一个顶点确定选做独立集中的顶点,其他顶点选或不选的情况就确定了,分三种情况讨论即可。时间复杂度 O ( n ) O(n) O(n)

    AC代码

    typedef long long ll;
    const int N=1e5+10;
    struct node { int x,y; }p[N];
    ll a[N],b[N];
    
    int main()
    {
    	int T;
    	cin>>T;
    	while(T--)
    	{
    		int n;
    		cin>>n;
    		for(int i=1;i<=n;i++) cin>>a[i];
    		for(int i=4;i<=n;i++) cin>>p[i].x>>p[i].y;
    		ll ans=0,sum;
    		b[1]=b[2]=0,b[3]=1;
    		sum=a[3];
    		for(int i=4;i<=n;i++)
    		{
    			int x=p[i].x,y=p[i].y;
    			if(b[x]||b[y]) b[i]=0;
    			else b[i]=1,sum+=a[i];
    		}
    		ans=max(ans,sum);
    		
    		b[1]=b[3]=0,b[2]=1;
    		sum=a[2];
    		for(int i=4;i<=n;i++)
    		{
    			int x=p[i].x,y=p[i].y;
    			if(b[x]||b[y]) b[i]=0;
    			else b[i]=1,sum+=a[i];
    		}
    		ans=max(ans,sum);
    		
    		b[2]=b[3]=0,b[1]=1;
    		sum=a[1];
    		for(int i=4;i<=n;i++)
    		{
    			int x=p[i].x,y=p[i].y;
    			if(b[x]||b[y]) b[i]=0;
    			else b[i]=1,sum+=a[i];
    		}
    		ans=max(ans,sum);
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    Counting Stickmen(树形DP)

    题意
    给定一棵树,求树中火柴人的数量,其中火柴人的形状如下图红色结点所示。

    分析
    火柴人可以分为4个部分:头(2)、脖子(3)、手(4-6和9-10)、身体(5-7-8),其中只有脖子和其他三个部位都相连,因此从脖子入手,枚举可以作为脖子的位置。下面分析每个部位的特征,任何一个结点都可以作为头或者脖子,度数大于等于2的结点可以作为手,度数大于等于3的结点可以作为身体。对于一个火柴人,先确定脖子的位置,手有两个,而且两只手不能共用结点,身体只有一个,可以先确定身体的位置,之后确定手,两只手不能占用身体的位置,这就需要去重,具体可以参考下面的代码,最后确定头的位置。时间复杂度 O ( n ) O(n) O(n)

    AC代码

    typedef long long ll;
    const int N=5e5+10;
    const int mod=998244353;
    vector<int> v[N];
    ll d[N],body[N],hand[N];
    ll ans;
    int n;
    
    ll C(ll x) { return x*(x-1)/2%mod; }
    
    void dfs(int x,int p)
    {
        ll b=0,h=0;
        ll res=0;
        for(auto y:v[x])
        {
            b=(b+body[y])%mod;
            h=(h+hand[y])%mod;
            if(y==p) continue;
            dfs(y,x);
        }
        if(d[x]<4) return ;
        for(auto y:v[x])
        {
            res=(res+body[y]*((C(h-hand[y])-(b-body[y]))%mod)%mod*(d[x]-3)%mod)%mod;
        }
        res=(res%mod+mod)%mod;
        ans=(ans+res)%mod;
    }
    
    int main()
    {
        int T;
        cin>>T;
        while(T--)
        {
            cin>>n;
            for(int i=1;i<=n;i++) v[i].clear();
            for(int i=1;i<=n;i++) d[i]=hand[i]=body[i]=0;
            for(int i=1;i<n;i++)
            {
                int x,y;
                cin>>x>>y;
                v[x].push_back(y);
                v[y].push_back(x);
                d[x]++,d[y]++;
            }
            for(int i=1;i<=n;i++)
            {
                hand[i]=d[i]-1;
                if(d[i]>2) body[i]=C(d[i]-1);
            }
            ans=0;
            dfs(1,0);
            cout<<ans<<endl;
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    注意
    运算过程中注意取模,中间结果可能爆long long

    Black Magic(贪心)

    题意
    HoshiYo有 n n n个积木,每个积木的左右两边被涂成黑色或白色。HoshiYo将积木按照一定的顺序排成一排,但不旋转,然后释放黑魔法。对于任意两个相邻的积木,如果左边积木的右边和右边积木的左边都被涂成黑色,那么这两个积木就会被粘在一起,使两个积木变成一个。HoshiYo想知道释放黑魔法后他能得到的最小和最大数量的积木。

    分析
    对于连通块最多的情况,要让能够相连的方块对尽量少。可以发现,所有L可以全放在最左边,所有R可以全放在最右边,这样他们就不会和中间的方块相连,之后尽可能用E把B隔开即可。对于连通块最少的情况,要让能够相连的方块对尽可能多。可以先把所有B拼在一起,之后可以把一个L和一个R分为一组拼起来。注意如果有至少一组LR并且也有至少一块B的话,可以把一组LR拆开拼在B的两边,这样可以再减少一个连通块。时间复杂度 O ( 1 ) O(1) O(1)

    AC代码

    int main()
    {
    	int T;
    	cin>>T;
    	while(T--)
    	{
    		int e,l,r,b;
    		cin>>e>>l>>r>>b;
    		int n=e+l+r+b;
    		if(b)
    		{
    			int ans=n;
    			ans-=b-1;
    			int z=max(0,min(l,r)-1);
    			ans-=z;
    			l-=z,r-=z;
    			if(l) ans--;
    			if(r) ans--;
    			cout<<ans<<" ";
    		}
    		else
    		{
    			cout<<n-min(l,r)<<" ";
    		}
    		cout<<n-max(0,b-e-1)<<endl;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    Sumire(数位DP)

    题意
    计算 ∑ i = l r f k ( i , B , d ) \sum_{i=l}^{r}f^k(i,B,d) i=lrfk(i,B,d) f ( x , B , d ) f(x,B,d) f(x,B,d)表示数字 d d d 出现在 x x x(忽略前导0)的 B B B 进制表示下的次数。在本题中有 0 0 = 0 0^0=0 00=0.

    分析
    在这里插入图片描述

    AC代码

    const int mod=1e9+7;
    typedef long long ll;
    ll k,B,d,l,r;
    
    using mint=Modint<mod>; //mint:Modint
    mint dp[70][2][2][70];
    bool vis[70][2][2][70];
    int a[105];
    int pos;
    
    mint dfs(int cur,int limit,bool zero,int cnt)
    {
    	if(cur==pos&&!cnt) return 1;
    	if(cur==pos) return 0;
    	if(cnt<0) return 0;
    	if(vis[cur][limit][zero][cnt]) return dp[cur][limit][zero][cnt];
    	vis[cur][limit][zero][cnt]=1;
    	dp[cur][limit][zero][cnt]=0;
    	int z=limit?a[cur]:B-1;
    	int used=0,v=0;
    	int cost=(v==d);
    	if(zero&&(d==0)) cost=0;
    	dp[cur][limit][zero][cnt]+=dfs(cur+1,limit&&(a[cur]==v),zero&&(v==0),cnt-cost);
    	used++;
    	if(v!=d&&d<=z)
        {
            used++; v=d;
            dp[cur][limit][zero][cnt]+=dfs(cur+1,limit&&(a[cur]==v),zero&&(v==0),cnt-1);
        }
        if(v!=z)
        {
            used++; v=z;
            dp[cur][limit][zero][cnt]+=dfs(cur+1,limit&&(a[cur]==v),zero&&(v==0),cnt);
        }
        v=B;
        dp[cur][limit][zero][cnt]+=dfs(cur+1,limit&&(a[cur]==v),zero&&(v==0),cnt)*max(0,z-used+1);
        return dp[cur][limit][zero][cnt];
    }
    
    mint solve(ll n)
    {
    	pos=0;
    	while(n) a[pos++]=n%B,n/=B;
    	memset(vis,0,sizeof(vis));
    	reverse(a,a+pos);
    	mint ans=0;
    	for(int i=1;i<=pos;i++)
    	{
    		ans+=dfs(0,1,1,i)*(mint(i)^k);
    	}
    	if(!d) return ans+1;
    	return ans;
    }
    
    int main()
    {
    	int T;
    	cin>>T;
    	while(T--)
    	{
    		cin>>k>>B>>d>>l>>r;
    		cout<<(int)(solve(r)-solve(l-1))<<endl;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    Triangle Game(博弈论)

    题意
    Kate和Emilico正在玩一个游戏,有3个整数a,b,c,保证存在一个边长分别为a,b,c的非退化三角形。游戏过程如下:玩家轮流在这3个整数中的一个上减去某个正整数。如果在某位玩家的操作之后不存在边长为a,b,c的非退化三角形,该玩家就输了。Kate先手,如果两人都以最优策略进行游戏,Kate会赢吗?

    分析
    (1,1,1)是必败态,哪位玩家能到达这个状态就必胜,否则必败,这就相当于石子个数 − 1 -1 1的Nim游戏,判断 ( a − 1 ) ⊕ ( b − 1 ) ⊕ ( c − 1 ) (a-1) \oplus (b-1) \oplus (c-1) (a1)(b1)(c1)是否为 0 0 0即可。但是游戏要求每次操作后三角形是非退化的,而Nim游戏没有这个限制,下面证明这个结论仍然是正确的。

    在这里插入图片描述

    AC代码

    int main()
    {
    	int T;
    	cin>>T;
    	while(T--)
    	{
    		int a,b,c;
    		cin>>a>>b>>c;
    		if((a-1)^(b-1)^(c-1)) cout<<"Win"<<endl;
    		else cout<<"Lose"<<endl;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    MyBatis笔记——参数处理
    MATLAB算法实战应用案例精讲-【优化算法】榛树搜索算法(HTS)(附MATLAB代码实现)
    Vue3中props的原理与使用
    Linux提权方法总结
    Knife4j使用教程(一) -- 在不同版本SpringBoot,选用不同的Knife4j相关的jar包
    linux的常用命令及常用工具安装
    G2plot图表在Vue中使用-获取新数据后二次渲染图表-案例
    离散数学 学习 之 递推方程和生成函数
    VSCode常用插件
    【LeetCode刷题日志】622.设计循环队列
  • 原文地址:https://blog.csdn.net/weixin_46155777/article/details/126677406