HAOI2018 题解

rush了两天终于写完了,CJ那群神仙咋写的那么快??跪烂
不过HAOI暴力分好多啊。。

BZOJ5302奇怪的背包

Problem

BZOJ

Solution

首先 v i v_i vi 能凑出来的满足 gcd ⁡ ( v i , P ) ∣ x \gcd(v_i,P)|x gcd(vi,P)x 。那么首先可以把所有的物品都取 gcd ⁡ \gcd gcd,由于 P P P 最多仅有1000左右个因数,它们只有1000左右个。不妨记 d s n [ i ] dsn[i] dsn[i] P P P 的第 i i i 个因数。想到这里,不难设计出一个dp方程,设 f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i v i v_i vi gcd ⁡ \gcd gcd d s n [ j ] dsn[j] dsn[j] 的方案数,只要枚举这个因数取或不取即可进行转移。

那么接下来要考虑怎么回答询问,显然暴力是不可取的。每次询问其实就是求 ∑ d s n [ i ] ∣ w f [ c n t ] [ i ] \sum_{dsn[i]|w}f[cnt][i] dsn[i]wf[cnt][i],其实就相当于 ∑ d s n [ i ] ∣ gcd ⁡ ( w , P ) f [ c n t ] [ i ] \sum_{dsn[i]|\gcd(w,P)} f[cnt][i] dsn[i]gcd(w,P)f[cnt][i],暴力预处理即可做到 O ( 1 ) O(1) O(1) 回答。

复杂度大概是 O ( 100 0 2 log ⁡ 1000 + m ) O(1000^2\log 1000+m) O(10002log1000+m)

Code

#include <algorithm>
#include <cstdio>
#include <map>
using namespace std;
typedef long long ll;
const int maxn=2010,maxm=1000010,mod=1e9+7;
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;
}
int n,m,p,tot,cnt,v[maxm],mi[maxm],c[maxn],g[maxn],dsn[maxn],f[maxn][maxn];
map<int,int> ma;
map<int,int>::iterator itr;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
void input()
{
	read(n);read(m);read(p);
	for(int i=1;i*i<=p;i++)
	  if(p%i==0)
	  {
		dsn[++tot]=i;
		if(i*i!=p) dsn[++tot]=p/i;
	  }
	sort(dsn+1,dsn+tot+1);
	for(int i=1;i<=tot;i++) ma[dsn[i]]=i;
	for(int i=1;i<=n;i++){read(v[i]);v[i]=gcd(v[i],p);}
	sort(v+1,v+n+1);
	for(int i=1;i<=n;i++)
	{
		if(v[i]!=v[i-1]){v[++cnt]=v[i];c[cnt]=1;}
		else ++c[cnt];
	}
	mi[0]=1;
	for(int i=1;i<=1000000;i++) mi[i]=pls(mi[i-1],mi[i-1]);
}
int main()
{
	input();
	for(int i=0;i<cnt;i++)
	{
		int pos=ma[v[i+1]];
		f[i+1][pos]=dec(mi[c[i+1]],1);
		for(int j=1;j<=tot;j++)
		{
			f[i+1][j]=pls(f[i+1][j],f[i][j]);
			pos=ma[gcd(dsn[j],v[i+1])];
			f[i+1][pos]=(f[i+1][pos]+(ll)f[i][j]*dec(mi[c[i+1]],1))%mod;
		}
	}
	for(int i=1;i<=tot;i++)
	  for(int j=1;j<=i;j++)
	    if(dsn[i]%dsn[j]==0)
	      g[i]=pls(g[i],f[cnt][j]);
	while(m--)
	{
		int w;read(w);w=ma[gcd(w,p)];
		printf("%d\n",g[w]);
	}
	return 0;
}

BZOJ5303反色游戏

Problem

BZOJ

Solution

如果把点的状态压成01串,边就可以看作基,那么题目就是问异或为0的方案数。

首先各个联通块是相互独立的,如果一个联通块中有奇数个黑点,那么显然无解。然后在联通块中搞一棵生成树,就对应于一个满的线性基,其他的边可以选或不选,因此记 t o t tot tot 为联通块个数,那么答案即为 2 m − n + t o t 2^{m-n+tot} 2mn+tot

再考虑删去一个点的方案数怎么计算,首先边数会减少其度数条,点数减少1,然后我们就需要统计会分裂出多少个新的联通块,在tarjan求割点时可以求出。最复杂的就是判定分裂出的各个联通块是否有奇数个黑点,对于分裂出的子联通块可以在tarjan时求出,代码中 s b l sbl sbl 表示的就是它子联通块的黑点个数, b l bl bl 则表示生成树的子树中黑点个数。

另外代码难调,感觉我作为一个码力不行的选手,考场上不如写暴力走人。。

时间复杂度 O ( T ( n + m ) ) O(T(n+m)) O(T(n+m))

Code

#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=100010,mod=1e9+7;
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[maxn<<1];
int z,n,m,p,dfc,rt,ir,tot,ans,mi[maxn],head[maxn],d[maxn],dfn[maxn],low[maxn];
int id[maxn],cut[maxn],mark[maxn],son[maxn],bl[maxn],sbl[maxn];
char s[maxn];
int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
void insert(int u,int v)
{
	edge[++p]=(data){v,head[u]};head[u]=p;
	edge[++p]=(data){u,head[v]};head[v]=p;
}
void tarjan(int x)
{
	dfn[x]=low[x]=++dfc;son[x]=(x!=rt);
	id[x]=rt;bl[x]=(s[x]=='1');
	for(int i=head[x];i;i=edge[i].nxt)
	{
		if(!dfn[edge[i].v])
		{
			tarjan(edge[i].v);
			getmin(low[x],low[edge[i].v]);
			bl[x]+=bl[edge[i].v];
			if(low[edge[i].v]>=dfn[x])
			{
				++son[x];sbl[x]+=sbl[edge[i].v];
				if(bl[edge[i].v]&1) mark[x]=1;
				if(x^rt) cut[x]=1;
			}
		}
		else getmin(low[x],dfn[edge[i].v]);
	}
	if(x==rt&&edge[head[x]].nxt) cut[x]=1;
}
void input()
{
	int u,v;
	p=dfc=tot=ir=0;
	read(n);read(m);
	for(int i=1;i<=n;i++)
	{
		head[i]=d[i]=dfn[i]=low[i]=cut[i]=0;
		id[i]=mark[i]=son[i]=bl[i]=sbl[i]=0;
	}
	for(int i=1;i<=m;i++)
	{
		read(u);read(v);
		++d[u];++d[v];
		insert(u,v);
	}
	scanf("%s",s+1);
	for(int i=1;i<=n;i++)
	  if(!dfn[i])
	  {
	  	tarjan(rt=i);
	  	++tot;ir+=bl[i]&1;
	  }
}
int main()
{
	read(z);mi[0]=1;
	for(int i=1;i<=100000;i++) mi[i]=pls(mi[i-1],mi[i-1]);
	while(z--)
	{
		input();
		printf("%d ",(ir?0:mi[m-n+tot]));
		for(int i=1;i<=n;i++)
		{
			ans=mi[m-n+tot-d[i]+son[i]];
			if(mark[i]) ans=0;
			if((bl[id[i]]-sbl[i]-(s[i]=='1'))&1) ans=0;
			if(ir-(bl[id[i]]&1)) ans=0;
			printf("%d",ans);
			putchar(i==n?'\n':' ');
		}
	}
	return 0;
}

BZOJ5304字串覆盖

Problem

BZOJ

Solution

感觉这题不是很好,就不是很想写,口胡份题解好了

数据范围明示分类讨论。对于 l e n &gt; 50 len&gt;50 len>50 ,可以先对A串建出SAM,拿B串跑LCS,记录一下B各个位置的对应节点。这样在parent树上倍增就可以 O ( log ⁡ n ) O(\log n) O(logn) 找到询问的P所对应的节点了。然后主席树处理出right集合,把这些right集合拿出来,一边二分一边匹配即可。

对于 l e n ≤ 50 len\leq 50 len50 ,我们枚举 1 1 1 ~ 50 50 50 的长度 l l l,然后通过hash值来找出每个位置 i i i 后面的第一个位置 j j j,使得 j j j i i i 前面长度为 l l l 的字符串匹配且不相交。每个 i i i 最多有一个 j j j ,形成了一个树结构,把询问离线下来,然后dfs树时在祖先上二分即可。

时间复杂度 O ( 50 n + q n l e n log ⁡ n ) O(50n+q\frac n {len} \log n) O(50n+qlennlogn) ,后面的复杂度按照数据的特殊约束是跑得过的。。

BZOJ5305苹果树

Problem

BZOJ

Solution

f [ i ] f[i] f[i] 表示大小为 i i i 的树的所有情况两两距离和, g [ i ] g[i] g[i] 表示大小为 i i i 的树的所有情况深度和,根的深度为1。

大小为 i i i 的树,有 i + 1 i+1 i+1 条伸出来的链,那么大小为 i i i 的树的形态有 i ! i! i! 种,因为第 i i i 个节点有 i i i 个位置可以选。

那么直接枚举左子树大小,计数即可,要给左子树分配标号,用组合数。注意左右子树深度除了要乘另一边的形态个数之外,还需要乘上另一边的子树大小。

时间复杂度 O ( n 2 ) O(n^2) O(n2)

Code

#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=2010;
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;
}
int n,mod,L,R,c[maxn][maxn],f[maxn],g[maxn];
ll fac[maxn];
int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
int main()
{
	read(n);read(mod);
	fac[0]=1ll;g[1]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
	for(int i=0;i<=n;i++)
	{
		c[i][0]=1;
		for(int j=1;j<=i;j++) c[i][j]=pls(c[i-1][j-1],c[i-1][j]);
	}
	for(int i=2;i<=n;i++)
	{
		g[i]=(ll)fac[i]*i%mod;
		for(int j=0;j<i;j++)
		{
			L=j;R=i-j-1;
			g[i]=(g[i]+(g[L]*fac[R]+g[R]*fac[L])%mod*c[i-1][j]%mod)%mod;
			f[i]=(f[i]+((f[L]+(ll)g[L]*(R+1))%mod*fac[R])%mod*c[i-1][j])%mod;
			f[i]=(f[i]+((f[R]+(ll)g[R]*(L+1))%mod*fac[L])%mod*c[i-1][j])%mod;
		}
	}
	printf("%d\n",f[n]);
	return 0;
}

BZOJ5306染色

Problem

BZOJ

Solution

看到恰好,明示容斥。。

那么要计算 g [ i ] g[i] g[i] 表示有至少 i i i 种出现了 s s s 次。

g [ i ] = ( m i ) A n i s ( s ! ) i ( m − i ) n − i s g[i]=\binom m i \frac {A_{n}^{is}} {(s!)^i}(m-i)^{n-is} g[i]=(im)(s!)iAnis(mi)nis

g [ i ] = ∑ j = i m ( j i ) f [ j ] g[i]=\sum_{j=i}^m\binom j i f[j] g[i]=j=im(ij)f[j]

f [ i ] = ∑ j = i m ( − 1 ) j − i ( j i ) g [ j ] f[i]=\sum_{j=i}^m (-1)^{j-i}\binom j i g[j] f[i]=j=im(1)ji(ij)g[j]

把组合数一拆,NTT加速二项式反演即可。时间复杂度 O ( m log ⁡ m ) O(m\log m) O(mlogm)

Code

#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=300010,mod=1004535809,G=3;
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;
}
int n,m,s,N,l,ans,w[maxn],f[maxn],g[maxn],h[maxn],rev[maxn];
int fac[10000010],inv[10000010];
int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
int c(int n,int m){return m>n?0:(ll)fac[n]*inv[m]%mod*inv[n-m]%mod;}
int power(int x,int y)
{
	int res=1;
	for(;y;y>>=1,x=(ll)x*x%mod)
	  if(y&1)
	    res=(ll)res*x%mod;
	return res;
}
void init()
{
	const static int N=max(n,m);
	fac[0]=1;
	for(int i=1;i<=N;i++) fac[i]=(ll)fac[i-1]*i%mod;
	inv[N]=power(fac[N],mod-2);
	for(int i=N-1;~i;i--) inv[i]=(ll)inv[i+1]*(i+1)%mod;
}
void input()
{
	int iv=1;
	read(n);read(m);read(s);
	for(int i=0;i<=m;i++) read(w[i]);
	init();
	for(int i=0;i<=m;i++)
	{
		if(i*s>n) break;
		g[i]=(ll)c(m,i)*c(n,i*s)%mod*fac[i*s]%mod*iv%mod*power(m-i,n-i*s)%mod;
		iv=(ll)iv*inv[s]%mod;
	}
}
void NTT(int *a,int f)
{
	for(int i=1;i<N;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
	for(int i=1;i<N;i<<=1)
	{
		int gn=power(G,(mod-1)/(i<<1));
		for(int j=0;j<N;j+=(i<<1))
		{
			int g=1,x,y;
			for(int k=0;k<i;++k,g=(ll)g*gn%mod)
			{
				x=a[j+k];y=(ll)g*a[j+k+i]%mod;
				a[j+k]=pls(x,y);a[j+k+i]=dec(x,y);
			}
		}
	}
	if(f==-1)
	{
		int inv=power(N,mod-2);reverse(a+1,a+N);
		for(int i=0;i<N;i++) a[i]=(ll)a[i]*inv%mod;
	}
}
int main()
{
	input();
	for(int i=0;i<=m;i++) g[i]=(ll)g[i]*fac[i]%mod;
	for(int i=0;i<=m;i++) h[i]=((i&1)?dec(0,inv[i]):inv[i]);
	reverse(h,h+m+1);
	for(N=1,l=0;N<=(m+m);N<<=1) ++l;
	for(int i=1;i<N;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
	NTT(g,1);NTT(h,1);
	for(int i=0;i<N;i++) f[i]=(ll)g[i]*h[i]%mod;
	NTT(f,-1);
	for(int i=0;i<=m;i++) ans=pls(ans,(ll)w[i]*f[i+m]%mod*inv[i]%mod);
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值