test20231102

T1

这是一道简单题,考虑从后往前 dp。复杂度是 \(n^2\) 的。但是可以考虑前缀和优化,因为前缀和有单调性,所以直接上二分。

int n,L;
int a[N];
int f[N],sum[N]; 
signed main(){
	n=read();L=read();
    up(i,1,n){
    	a[i]=read();
		sum[i]=sum[i-1]+a[i];
	}
	f[n]=1;
	dn(i,n-1,1){
		int l=i,r=n,ans;
		while(l<=r){
			int mid=(l+r)>>1;
			if(sum[mid]-sum[i-1]<=L){
				ans=mid;
				l=mid+1;
			}
			else r=mid-1;
		}
		f[i]=f[ans+1]+1;
	}
	up(i,1,n){
		cout<<f[i]<<" ";
	}
    return 0;
}

T2

同样是简单题,看起来就很 dp 的样子。

\(dp_{i,j,op}\) 为当前节点为 \(i\),里面有 \(j\) 个属于 \(1\) 县城,\(i\) 号节点属于 \(op\) 县城。

那么答案就是 \(\max(dp_{1,n/2,op})\)

转移方程还是比较简单的,可以看我代码:

inline void dfs(int u,int from){
	sz[u]=1;
	for(auto i:g[u]){
		int v=i.fi,w=i.se;
		if(v==u)continue;
		dfs(v,u);
		sz[u]+=sz[v];
	}
	if(sz[u]==1){
		dp[u][1][1]=0;
		dp[u][0][0]=0;
	}
	else{
		up(i,0,min(sz[u],n/2)){
			if(sz[u]==2){
				dp[u][0][0]=max(dp[u][0][0],dp[u<<1][0][0]+d[u<<1]);
				dp[u][1][0]=max(dp[u][1][0],dp[u<<1][1][1]);
				dp[u][1][1]=max(dp[u][1][1],dp[u<<1][0][0]);
				dp[u][2][1]=max(dp[u][2][1],dp[u<<1][1][1]+d[u<<1]);
				continue;
			}
			up(j,0,i){
				int t=i-1;
				if(j<=t&&i>=1){
					dp[u][i][1]=max(dp[u<<1][j][0]+dp[u<<1|1][t-j][0],dp[u][i][1]);
					dp[u][i][1]=max(dp[u<<1][j][1]+dp[u<<1|1][t-j][0]+d[u<<1],dp[u][i][1]);
					dp[u][i][1]=max(dp[u<<1][j][0]+dp[u<<1|1][t-j][1]+d[u<<1|1],dp[u][i][1]);
					dp[u][i][1]=max(dp[u<<1][j][1]+dp[u<<1|1][t-j][1]+d[u<<1]+d[u<<1|1],dp[u][i][1]);
				}
				dp[u][i][0]=max(dp[u<<1][j][1]+dp[u<<1|1][i-j][1],dp[u][i][0]);
				dp[u][i][0]=max(dp[u<<1][j][0]+dp[u<<1|1][i-j][1]+d[u<<1],dp[u][i][0]);
				dp[u][i][0]=max(dp[u<<1][j][1]+dp[u<<1|1][i-j][0]+d[u<<1|1],dp[u][i][0]);
				dp[u][i][0]=max(dp[u<<1][j][0]+dp[u<<1|1][i-j][0]+d[u<<1]+d[u<<1|1],dp[u][i][0]);
			}
		}
	}
}
signed main(){
	n=read();
	up(i,1,n){
		d[i]=read();
		g[i/2].push_back({i,d[i]});
	} 
	memset(dp,-0x3f,sizeof dp);
	dfs(1,0);
	cout<<max(dp[1][n/2][1],dp[1][n/2][0]);
    return 0;
}

T3

有点意思的一道题,当时考场上没有想出来,打了个搜索就滚了,现在想一想,好像还挺简单的。

因为已经知道叶子的情况,有一个比较显然的东西,就是说每一个子树,如果红色多于蓝色,那么其一定属于红色,如果蓝色多于红色,那么一定属于蓝色。

所以我们可以从叶子开始向上递归,最后判断根节点的颜色。

这样 \(55\) 分就到手了。

如果根节点是红色,那么所有的点都可以选,输出情况也好考虑。

如果根节点是蓝色,那么输出 \(-1\)

那么如果根节点无法确定怎么办呢?

此时,红子就需要一步把这个节点边红,那么有两种可能,一种是把一个不确定变为红色,一种是把蓝色变为不确定,向下递归就可以了。

int n,f[N];
vector<int>g[N];
inline void dfs1(int u){
    if(!g[u].size())return;
    int cnt[2]={0,0};
    for(auto v:g[u]){
        dfs1(v);
        if(f[v]!=-1)cnt[f[v]]++;
    }
    if(cnt[0]>cnt[1])f[u]=0;
    else if(cnt[0]==cnt[1])f[u]=-1;
    else f[u]=1;
}
vector<int>ans;
inline void dfs2(int u){
    if(!g[u].size()){
        if(f[u]==-1)ans.push_back(u);
        return;
    }
    if(f[u]==-1){
        for(auto v:g[u]){
            if(f[v]==0)continue;
            dfs2(v);
        }
    }
    else if(f[u]==1){
        int cnt[2]={0,0};
        for(auto v:g[u]){
            if(f[v]!=-1)cnt[f[v]]++;
        }
        if(cnt[1]-cnt[0]==1){
            for(auto v:g[u]){
                if(f[v]==0)continue;
                dfs2(v);
            }
        }
    }
}
inline void solve(){
    n=read();
    int x;
    up(i,1,n){
        x=read();
        g[x].push_back(i);
    }
    up(i,1,n)f[i]=read();
    dfs1(1);
    if(f[1]==1){
        puts("-1");
        return;
    }
    else if(f[1]==0){
        up(i,1,n)if(g[i].size()==0&&f[i]==-1)ans.push_back(i);
    }
    else dfs2(1);
    sort(ans.begin(),ans.end());
    write(ans.size(),0);
    for(auto v:ans)write(v,0);
    printf("\n");
}
inline void clear(){
    ans.clear();
    memset(f,0,sizeof f);
    up(i,1,n)g[i].clear();
}
signed main(){
    freopen("rab.in","r",stdin);
    freopen("rab.out","w",stdout);
    int T=read();
    while(T--){
        clear();
        solve();
    }
    return 0;
}

T4

也是有意思的一道博弈论。

看着似乎完全无法下手的样子,因为它要求所有的的子集的方案数。

一般遇到这种情况,就要考虑一下,是不是有什么特殊的性质。

由于后一个人可以重复上一个人选择的堆,每堆苹果的数量对 \(a+b\) 取模不改变胜负状态。

这种判断可以运用在许多博弈论里面。

\(a<b\):

  1. \(x_i < a\),这一堆删除不影响胜负状态。
  2. \(a \le x_i\le b\),只要存在这样的堆,那么取 \(a\) 的人必胜,为什么?

把所有的堆全部 \(\mod a+b\),然后把小于 \(a\) 的堆除去,然后发现剩下的可以分为 \([a,b)\)\([b+1,a+b)\),两类,对于第二类,每一堆只会被取一次,无论是 a还是 \(b\),所以 \(a,b\) 依次取,那么 \(a\) 肯定获胜。

  1. \(b\le x_i < 2a\) 胜负状态和这样的堆的奇偶性有关。记这样的堆的个数为 \(M\) 个。

  2. \(2a\le x_i\),存在至少 \(2\) 个这样的堆则取 \(a\) 的人必胜。存在 \(1\) 个这样的堆且 \(M\) 为偶数则先手必胜,存在 \(1\) 个且 \(M\) 为奇数则 \(a\) 必胜,不存在这样的堆且 \(M\) 为奇数则先手必胜,不存在且这样的堆且 \(M\) 为偶数则后手必胜。

一句一句分析,为什么?

首先,如果是 \(a\) 先手,那么 \(a\) 取一次直接到情况二上,此时必胜,如果是后手,\(b\) 先取一次,此时如果还有一个这样的堆,那么同样可以到状态二。

不存在这样的堆且 \(M\) 为奇数则先手必胜:情况三的堆一人取一次,先手必胜。

否则则后手必胜。

int fpow(int x,int b){
	if(b<0) return 0;
	if(x==0) return 0;
	if(b==0) return 1;
	int res=1;
	while(b>0){
		if(b&1)	res=1LL*res*x%mod;
		x=1LL*x*x%mod;
		b>>=1;
	}
	return res;
}
int fpow2(int x,int b){
	if(b<0) return 1;
	if(x==0) return 0;
	if(b==0) return 1;
	int res=1;
	while(b>0){
		if(b&1)	res=1LL*res*x%mod;
		x=1LL*x*x%mod;
		b>>=1;
	}
	return res;
}
int n,A,B,cnt[3],ans[4]; 
void solve(){
	cin>>n>>A>>B;
	int swp=0,coef_0=1;
	if(A>B) swap(A,B),swp=1;
	while(n--){
		int x;
		cin>>x;
		x%=(A+B);
		if(x<A) coef_0=2LL*coef_0%mod;
		else if(x<B) cnt[0]++;
		else if(x<2*A) cnt[1]++;
		else cnt[2]++;
	}
	ans[0]=(fpow(2,cnt[0])-1)*fpow(2,cnt[1]+cnt[2])%mod;
	ans[0]=(ans[0]+cnt[2]*fpow(2,cnt[1]-1))%mod;
	ans[0]=(ans[0]+(fpow(2,cnt[2])-1-cnt[2])*fpow(2,cnt[1]))%mod;
	ans[2]=fpow(2,cnt[1]-1);
	ans[2]=(ans[2]+fpow2(2,cnt[1]-1)*cnt[2])%mod;
	ans[3]=fpow2(2,cnt[1]-1);
	if(swp) swap(ans[0],ans[1]);
	for(int i=0;i<4;i++) cout<<1LL*ans[i]*coef_0%mod<<" ";
	cout<<"\n";
}

放张图:
image

posted @ 2023-11-02 14:17  LiQXing  阅读(48)  评论(0)    收藏  举报