COCI2016/2017 Round2T6 Burza

本文探讨了一种树上博弈问题,通过分析特定条件下的必胜策略,讲解了如何运用深度优先搜索(DFS)和状态压缩动态规划(状压DP)解决复杂问题。文章深入解析了在限制步数内封堵对手移动路径的策略,并提供了详细的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录


(你问我为什么没有T5,因为我调不过啊XD)

题目

题目1
题目2
(围住神经猫树上博弈版)

分析

注意你不知道对手如何移动,只知道树的样子,所以你不能根据他走的地方来堵结点,而题目的意思是问是否有必胜策略,即按照你设计的堵法,对手无论如何都移动不了 K K K次(博弈)。

显然,可以把深度超过 K K K的点先砍掉,当然,深度不到 K K K的枝叶也要砍掉。
发现,每一层(即同一深度)你只用堵一个结点,因为对手只能往下走,不能往上。


如果叶结点的数量不大于 K K K,有必胜策略,即堵住所有叶节点。
在这种必胜策略下, N N N最多是多少?是 K 2 + 1 K^2+1 K2+1,即下图这种情况:
图1
这意味着当 N ≤ K 2 + 1 N\leq K^2+1 NK2+1时,一定有堵完叶子结点的必胜策略。


于是我们只需要处理 K ≤ N K\leq\sqrt N KN 的情况了,这时 K K K的数据范围是 K ≤ 20 K\leq 20 K20,状压DP直接来。

下面提到的叶结点从左至右的顺序就是dfs中访问到的顺序。
集合 S S S表示每层堵不堵(前面已说到每层只用堵一个), d p [ i ] [ S ] dp[i][S] dp[i][S]表示在 S S S的情况下,能否恰好堵住前 i i i个叶结点。由于是恰好,所以 i i i一定是堵了的某个结点 u u u的最右边的叶结点,如:
在这里插入图片描述
l e f t [ u ] left[u] left[u]表示 u u u的最左端儿子在叶子中的编号 d e p [ u ] dep[u] dep[u] u u u的深度,枚举每一个 u u u,则有转移方程: d p [ i ] [ S ]   ∣ = d p [ l e f t [ u ] − 1 ] [ S − ( 1 &lt; &lt; d e p [ u ] ) ] dp[i][S]\ |=dp[left[u]-1][S-(1&lt;&lt;dep[u])] dp[i][S] =dp[left[u]1][S(1<<dep[u])]

代码

vector r e v [ i ] rev[i] rev[i]存最右边叶结点为 i i i的所有结点即可。

#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}

#define MAXK 20
#define MAXN 400
int N,K;
int G[MAXN+5][MAXN+5],fa[MAXN+5],dep[MAXN+5];

vector<int> Rev[MAXN+5];
int Leaves[MAXN+5],tot;
int Left[MAXN+5],Right[MAXN+5];
bool dp[MAXN+5][(1<<MAXK)+5];

bool Cut(int u,int f,int d){
    int cnt=0;
    for(int v=1;v<=N;v++)
        if(v!=f&&G[u][v]){
            if(d==K)
				G[u][v]=G[v][u]=0;
            else if(Cut(v,u,d+1))
				G[u][v]=G[v][u]=0;
            cnt+=G[u][v];
        }
    return !cnt&&d<K;
}
void Get(int u,int f,int d){
	fa[u]=f,dep[u]=d;
	if(dep[u]==K)
		Leaves[++tot]=u;
	for(int i=1;i<=N;i++)
		if(G[u][i]&&i!=f)
			Get(i,u,d+1);
}

int main(){
    freopen("burza.in" ,"r", stdin);
    freopen("burza.out","w",stdout);
    N=read(),K=read();
    if(K*K>=N)
        return puts("DA"),0;//直接必胜
    for(int i=1;i<=N-1;i++){
        int u=read(),v=read();
        G[u][v]=G[v][u]=1;
    }
    Cut(1,-1,0);
	Get(1,-1,0);
	//要分开写,我合在一起写有问题
    for(int i=1;i<=tot;i++){
    	int u=Leaves[i];
    	while(u!=-1&&!Left[u])
    		Left[u]=i,u=fa[u];
	}
	for(int i=tot;i>=1;i--){
		int u=Leaves[i];
		while(u!=-1&&!Right[u])
			Right[u]=i,Rev[i].push_back(u),u=fa[u];
	}
	//rihgt和left类似,但在后面没有用
	dp[0][0]=1;
	for(int i=1;i<=tot;i++)
		for(int S=2;S<=(1<<(K+1))-1;S+=2)
			for(int k=0;k<int(Rev[i].size());k++){
				int anc=Rev[i][k];
				if((S>>dep[anc])&1)
					dp[i][S]|=dp[Left[anc]-1][S^(1<<dep[anc])];
			}
	for(int S=0;S<=(1<<(K+1))-1;S++)
		if(dp[tot][S])
			return puts("DA"),0;
	puts("NE");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值