rush了两天终于写完了,CJ那群神仙咋写的那么快??跪烂
不过HAOI暴力分好多啊。。
BZOJ5302奇怪的背包
Problem
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
Solution
如果把点的状态压成01串,边就可以看作基,那么题目就是问异或为0的方案数。
首先各个联通块是相互独立的,如果一个联通块中有奇数个黑点,那么显然无解。然后在联通块中搞一棵生成树,就对应于一个满的线性基,其他的边可以选或不选,因此记 t o t tot tot 为联通块个数,那么答案即为 2 m − n + t o t 2^{m-n+tot} 2m−n+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
Solution
感觉这题不是很好,就不是很想写,口胡份题解好了
数据范围明示分类讨论。对于 l e n > 50 len>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 len≤50 ,我们枚举 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
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
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(m−i)n−is
g [ i ] = ∑ j = i m ( j i ) f [ j ] g[i]=\sum_{j=i}^m\binom j i f[j] g[i]=j=i∑m(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=i∑m(−1)j−i(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;
}