[省选集训2022] 模拟赛12

高维游走

题目描述

考虑以下在 \(m\) 维空间的游走过程:初始时你在原点,即每一维坐标为 \(0\) 的位置。接下来依次有 \(\sum_{i=0}^m t_i\) 次操作,分为 \(m+1\) 个阶段。第 \(0\) 个阶段有 \(t_0\) 次操作,每次操作可以不动或者选择任意一维向其正方向前进 \(1\) 个单位长度。第 \(i(1\leq i\leq m)\) 个阶段有 \(t_i\) 次操作,每次操作可以不动或者提升 \(i\) 点疲倦度的同时向第 \(i\) 维负方向前进 \(1\) 个单位长度。

\(f(x)\) 表示所有操作结束后回到游走起点的方案中疲劳度为 \(x\) 的方案数,定义两个方案不同当且仅当某一次操作的决策不同。你需要求 \(\sum_{i=0}^{\infty}\{f_i\bmod2\}\) 的值。

多组数据:\(1\leq T\leq 200\)\(1\leq m\leq 10,1\leq t_i<2^{31}\)

解法

\(x_i\) 表示最终向第 \(i\) 维走了 \(x_i\) 步,那么它对应的方案数是:

\[\prod_{i=1}^m {t_i\choose x_i}\cdot {t_0\choose x_1,x_2...x_m,t_0-\sum x} \]

那么贡献为 \(1\) 的充要条件是:\(t_i\and x_i=x_i\)\(\{x\}\)\(t_0\) 二进制位的拆分。后面的性质可以用归纳法证明,首先要满足 \(t_0\and x_1=x_1\),那么 \(x_1\) 相当于把 \(t_0\) 的二进制位拆了一些出来,\(x_2\) 继续拆分 \(t_0-x_1\),那么就可以证明了。

那么我们考虑规划合法的 \(\{x\}\),不同的方案以疲劳度来区分。考虑疲劳度是 \(\sum_{i=1}^m x_i\cdot i\),那么我们依次考虑 \(t_0\) 的每个二进制位,再决策这一位分配给 \(x\)\(0\sim m\) 的哪一个即可(如果分配 \(0\) 代表这一位不选),对于以前的位都相同的方案,我们在第一个导致数位不同的地方它们区分出来

\(dp[i][s]\) 表示考虑前 \(i\) 位,进位到 \(2^{i+1}\) 的集合是 \(s\)(如果 \(s_j=1\) 代表进位得到 \(j\cdot 2^{i+1}\) 是可行的)的方案数,那么转移考虑枚举 \(t_0\)\(i\) 位要分配给 \(0\sim m\) 中的哪一个。注意两种方案如果此后进位还是相同那么需要抵消,所以我们可以维护两个集合 \(s_1,s_2\),表示第 \(i\) 位疲劳值是 \(1/0\) 转移到的状态,用异或的方式来抵消即可。

时间复杂度 \(O(T\cdot 2^{m}\cdot m^2\cdot \log t)\)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 50;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,t[M],vis[M],mp[M][M],dp[M][1<<10];
void work()
{
	memset(vis,0,sizeof vis);
	memset(mp,0,sizeof mp);
	memset(dp,0,sizeof dp);
	m=read();
	for(int i=0;i<=m;i++) t[i]=read();
	for(int i=1;i<=m;i++)
	{
		n=0;
		while(t[i]) mp[n++][i]=t[i]&1,t[i]>>=1;
	}
	n=0;while(t[0]) vis[n++]=t[0]&1,t[0]>>=1;
	for(int i=0;i<n;i++) mp[i][0]=1;
	int w=0,ans=0,lim=(1<<10);
	dp[0][1]=1;
	for(int i=0;i<n;i++)
	{
		w^=1;memset(dp[w],0,sizeof dp[w]);
		int up=vis[i]?m:0;
		for(int j=0;j<lim;j++)
		{
			int s1=0,s2=0;
			for(int k=0;k<10;k++) if(j>>k&1)
				for(int l=0;l<=up;l++) if(mp[i][l])
				{
					if(k+l&1) s1^=(1<<(k+l>>1));
					else s2^=(1<<(k+l>>1));
				}
			dp[w][s1]+=dp[w^1][j];
			dp[w][s2]+=dp[w^1][j];
		}
	}
	for(int i=0;i<lim;i++)
		for(int j=0;j<10;j++)
			if(i>>j&1) ans+=dp[w][i];
	printf("%lld\n",ans);
}
signed main()
{
	freopen("travel.in","r",stdin);
	freopen("travel.out","w",stdout);
	T=read();
	while(T--) work();
}

过山车

题目描述

\(1\leq n\leq 150,1\leq m\leq 30,0\leq w[i][j]\leq 100\)

解法

考虑最后答案的形式:若干个哈密顿圈,转角的点会产生贡献。

显然哈密顿圈是 \(\tt np\) 问题,但因为这是平面图,所以关键性质是它可以黑白染色

那么我们把平面图黑白染色,尝试转化为二分图问题。首先考虑如何判定,可以给每个需要覆盖的点(下文称之为关键点) \(2\) 的流量,如果两个关键点相邻那么连一条流量为 \(1\) 的边,对它跑网络流,满流是有解的充要条件。

那么我们考虑魔改上面的图来完成最大化权值的目的,注意到弯道是竖直方向和水平方向的拼接,那么我们可以考虑拆点,对于每个 \(x\) 我们再新建 \(x_1,x_2\) 表示竖直点和水平点,那么我们在同时选取竖直点和水平点的时候计算贡献:

上图展示了建图的一部分,另一部分是:如果 \(x,y\) 竖直相连,那么把 \(x_1,y_1\) 连一条流量为 \(1\) 的边;如果 \(x,y\) 水平相连,那么把 \(x_2,y_2\) 连一条流量为 \(1\) 的边。然后我们对这个图跑最小费用流,再用总权值去减就可以得到答案。因为如果同时使用 \(x_1,x_2\) 得到的权值是 \(0\) 最后的贡献就是 \(w\),如果只使用一边那么最后的贡献是 \(0\)

好像这题只有 \(\tt spfa\) 才能通过 \(...\)

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 20005;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[155][35],b[155][35],id[155][35][3];
int tot,S,T,ans,f[M],dis[M],flow[M],pre[M],lst[M];
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
struct edge{int v,f,c,next;}e[M*100];
void add(int u,int v,int F,int c)
{
	e[++tot]=edge{v,F,c,f[u]},f[u]=tot;
	e[++tot]=edge{u,0,-c,f[v]},f[v]=tot;
}
int bfs()
{
	queue<int> q;
	for(int i=S;i<=T;i++)
		dis[i]=inf,flow[i]=pre[i]=lst[i]=0;
	dis[S]=0;flow[S]=inf;q.push(S);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v,c=e[i].c;
			if(dis[v]>dis[u]+c && e[i].f>0)
			{
				dis[v]=dis[u]+c;
				flow[v]=min(flow[u],e[i].f);
				pre[v]=u;lst[v]=i;
				q.push(v);
			}
		}
	}
	return flow[T]>0;
}
signed main()
{
	freopen("roller.in","r",stdin);
	freopen("roller.out","w",stdout);
	n=read();m=read();
	//initialize
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			a[i][j]=read();
			if(!a[i][j]) for(int k=0;k<3;k++)
				id[i][j][k]=++T;
		}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			b[i][j]=read();
	int A=0,B=0,sum=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(i+j&1) A+=(!a[i][j]);
			else B+=(!a[i][j]);
		}
	if(A!=B) {puts("-1");return 0;}
	S=0;T++;tot=1;
	//build the graph
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) if(i+j&1)
			for(int k=0;k<4;k++)
			{
				int x=i+dx[k],y=j+dy[k];
				if(x<1 || y<1 || x>n || y>m) continue;
				if(a[i][j]+a[x][y]) continue;
				add(id[i][j][k%2],id[x][y][k%2],1,0);
			}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) if(!a[i][j])
		{
			int x=id[i][j][2],w=b[i][j];ans+=w;
			if(i+j&1)
			{
				sum+=2;
				add(S,x,2,0);
				add(x,id[i][j][0],1,0);
				add(x,id[i][j][0],1,w);
				add(x,id[i][j][1],1,0);
				add(x,id[i][j][1],1,w);
			}
			else
			{
				add(x,T,2,0);
				add(id[i][j][0],x,1,0);
				add(id[i][j][0],x,1,w);
				add(id[i][j][1],x,1,0);
				add(id[i][j][1],x,1,w);
			}
		}
	while(bfs())
	{
		sum-=flow[T];
		ans-=flow[T]*dis[T];
		int u=T;
		while(u!=S)
		{
			e[lst[u]].f-=flow[T];
			e[lst[u]^1].f+=flow[T];
			u=pre[u];
		}
	}
	if(sum) {puts("-1");return 0;}
	printf("%lld\n",ans);
}

木棍

题目描述

\(n\) 根木棍,所有木棍的长度都为 \(k\),现在要把木棍排列到数轴上,第 \(i\) 个木棍的左端点 \(\geq a_i\),右端点 \(\leq b_i\),并且要求任意两个木棍之间不能有公共长度(但是可以点交)

此外还有 \(m\) 个限制,第 \(i\) 条形如: 左端点大于等于 \(x_i\),右端点小于等于 \(y_i\) 的木棍至多有 \(c_i\) 条。问是否存在解,注意只需要回答存不存在,而不需要构造解。

\(n\leq 1000,m\leq 1000,k\leq 10^9\)

解法

首先把限制都转成左端点的限制,只需要把右端点减去 \(k\) 即可。

如果不考虑 \(m\) 个限制并且 \(k=1\),那么剩下的限制就是每个木棍的可放置范围是一个区间,那么显然可以木棍到点连出一个二分图,然后跑网络流判断是否有解。当然可扩展的方式是使用 \(\tt Hall\) 定理,我们可以把所有端点离散化,那么只保留区间的限制就是充分的

限制形如这段区间的木棍数量 \(\geq x\),可以用差分约束来表示限制,设 \(s_i\) 表示前缀 \(i\) 放置的木棍个数,那么 \(s_r-s_{l-1}\geq x\)

发现 \(k\not=1\) 也是可以考虑的,也就是 \(s_{i+k}-s_i\leq 1\),那么离散化后就是 \(s_{a_i}-s_{a_{i-1}}\leq\lceil\frac{a_i-a_{i-1}}{k}\rceil\)\(m\) 的限制同样是可以考虑的,就是 \(s_{y_i}-s_{x_i-1}\leq c_i\);最后由于合法还需要添加一类边:就是 \(s_i-s_{i-1}\geq 0\)

然后暴力 \(\tt spfa\) 即可(如果时间用完了直接无解),注意离散化时我们要把左端点 \(-1\) 丢进去。

posted @ 2022-02-28 20:38  C202044zxy  阅读(465)  评论(0编辑  收藏  举报