BZOJ3515 Evenpaths

Problem

BZOJ

Solution

看到数据范围,考虑折半搜索。按照拓扑序,把关键点集平分为左右两边来考虑。

如果我们dp出0到点x的路径条数 p x p_x px,点x到1的路径条数 s x s_x sx,那么被这个点计数的路径条数显然是 p x × s x p_x\times s_x px×sx。对于一条路径,我们用第一次经过的右半边的关键点(可能没有障碍)来计数,因此顺便把1号点也设为关键点。这样 p x p_x px 只和左边障碍点状态有关, s x s_x sx 只和右边障碍点状态有关。

我们可以暴力枚举左半边的障碍放置情况,然后dp出当前情况下右半边关键点的 p x p_x px。同样地,对右半边处理出 s x s_x sx。注意到我们只关心路径条数的奇偶性,所以可以把所有关键点的 p x p_x px 模2,再把这些放在一起即可得到01串。

注意到把各个位同时进行模2意义下的乘法,其真值表和按位与相同。那么我们把这些01串的状态用桶存起来,然后拼接就可以用FWT加速。这样在最终局面下,有偶数个1就说明共有偶数条路径,计数即可。

设障碍物个数为 m m m,时间复杂度 O ( ( n + m ) 2 m 2 ) O((n+m)2^\frac m 2) O((n+m)22m)

因为一个细节调了半天感觉药丸。。

Code

#include <algorithm>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn=60,maxm=150000;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
struct data{int v,nxt;}edge[510];
int n,p,tot,N,mi[32],t[maxn],head[maxn],in[maxn],top[maxn],pos[maxn];
int ban[maxn],f[maxn],mark[maxn],cnt[maxm];
ll ans,tl[maxm],tr[maxm];
char s[maxn];
queue<int> q;
int cmp(const int &a,const int &b){return pos[a]<pos[b];}
void insert(int u,int v){edge[++p]=(data){v,head[u]};head[u]=p;}
void fwt_and(ll *a,int f)
{
	for(int i=1;i<N;i<<=1)
	  for(int j=0;j<N;j+=(i<<1))
	    for(int k=0;k<i;++k)
	      a[j+k]=(f==1?a[j+k]+a[j+k+i]:a[j+k]-a[j+k+i]);
}
void input()
{
	int x,now=0;mi[0]=1;
	read(n);
	scanf("%s",s+1);
	for(int i=1;i<=30;i++) mi[i]=mi[i-1]<<1;
	for(int i=1;i<=n;i++) if(s[i]=='?') t[++tot]=i;
	N=1<<(tot-(tot>>1)+1);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		for(int j=1;j<=n;j++) if(s[j]=='Y'){insert(i,j);++in[j];}
	}
	for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
	while(!q.empty())
	{
		x=q.front();q.pop();pos[x]=++now;top[now]=x;
		for(int i=head[x];i;i=edge[i].nxt)
		{
			--in[edge[i].v];
			if(!in[edge[i].v]) q.push(edge[i].v);
		}
	}
	sort(t+1,t+tot+1,cmp);
	for(int i=(tot>>1)+1;i<=tot;i++) mark[t[i]]=1;
}
void workl()
{
	int m=tot>>1,x,tmp;
	for(int s=0;s<mi[m];s++)
	{
		memset(f,0,sizeof(f));
		memset(ban,0,sizeof(ban));
		tmp=0;f[1]=1;
		for(int i=1;i<=m;i++) if(s&(1<<i-1)) ban[t[i]]=1;
		for(int i=1;i<=n;i++)
		  if(!ban[top[i]]&&!mark[top[i]]&&f[top[i]])
		  {
		  	x=top[i];
		  	for(int j=head[x];j;j=edge[j].nxt) f[edge[j].v]^=f[x];
		  }
		for(int i=(tot>>1)+1;i<=tot;i++) tmp|=f[t[i]]<<(i-m);
		if(f[2]) tmp|=1;
		++tl[tmp];
	}
}
void workr()
{
	int m=tot-(tot>>1),x,tmp;
	for(int s=0;s<mi[m];s++)
	{
		memset(f,0,sizeof(f));
		memset(ban,0,sizeof(ban));
		tmp=0;f[2]=1;
		for(int i=1;i<=m;i++) if(s&(1<<i-1)) ban[t[(tot>>1)+i]]=1;
		for(int i=n;i>=1;i--)
		  if(!ban[top[i]])
		  {
		  	x=top[i];
		  	for(int j=head[x];j;j=edge[j].nxt) f[x]^=f[edge[j].v];
		  }
		for(int i=(tot>>1)+1;i<=tot;i++) tmp|=f[t[i]]<<(i-(tot>>1));
		if(f[2]) tmp|=1;
		++tr[tmp];
	}
}
int main()
{
	input();
	workl();
	workr();
	fwt_and(tl,1);fwt_and(tr,1);
	for(int i=0;i<N;i++) tl[i]*=tr[i];
	fwt_and(tl,-1);
	for(int i=0;i<N;i++)
	{
		cnt[i]=cnt[i>>1]+(i&1);
		if(~cnt[i]&1) ans+=tl[i];
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符串,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值