图上(dp)大全
最终求的是概率,但是显然这个题应该是计数题
可以得出(2)的根节点必然是(1)
设(f[i][j])表示有(i)个节点,最大深度为(j)的方案数
四层循环枚举当前点数、当前深度、(2)子树内的点数、(2)子树内的深度
仙人掌!考虑先建出圆方树
定义(f[x][i])表示(x)个点,上面给他的入度为(i),以(x)为根子树内给边定向方案数
如果(x)是圆点,父亲也是圆点
(f[x][0])表示它的父亲边指向父亲,(f[x][1])表示父亲边指向自己
如果(x)是方点
(f[x][0/1/2])表示环上最靠上的两条边,不指向圆方树上(fa)的边的数量
如果(x)是圆点,父亲是方点
(f[x][0/1/2])表示所在环上与自己直接相连的两条边,指向自己的数量
如果(x)是圆点,那么转移形如
(f[x][0]=prodlimits_{k_1+k_2+……+k_ple a[x]}f[t][sum_t-k_i])
观察到是一个背包计数,可以分治(NTT)转移
如果(x)是方点
设(g[i][0/1])表示处理到环上第(i)个点,其中((i,i+1))是否指向(i+1)
(g[i+1][0]=g[i][0]*f[t][1]+g[i][1]*f[t][2])
(g[i+1][1]=g[i][0]*f[t][0]+g[i][1]*f[t][1])
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
inline int read()
{
int x=0;char ch,f=1;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') f=0,ch=getchar();
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return f?x:-x;
}
const int N=2e5+10,p=998244353,gi=332748118;
int n,m,newnode;
int a[N];
int f[N][3],g[N][2];
typedef vector<int> vector;
vector t[N];
struct graph
{
int cnt,head[N],nxt[N<<1],to[N<<1];
graph(){cnt=1;}
inline void link(int x,int y)
{
nxt[++cnt]=head[x];
to[cnt]=y;
head[x]=cnt;
}
}G,P;
inline int fast(int x,int k)
{
int ret=1;
while(k)
{
if(k&1) ret=ret*x%p;
x=x*x%p;
k>>=1;
}
return ret;
}
int dfn[N],low[N],st[N],top,idx;
inline void tarjan(int now,int from)
{
dfn[now]=low[now]=++idx;
st[++top]=now;
for(int i=P.head[now];i;i=P.nxt[i])
{
int t=P.to[i];
if(!dfn[t])
{
tarjan(t,i^1);
low[now]=min(low[now],low[t]);
if(low[t]==dfn[now])
{
G.link(now,++newnode);
do G.link(newnode,st[top]);
while (st[top--]!=t);
}
else if(low[t]>low[now])
G.link(now,t),--top;
}
else if(i!=from) low[now]=min(low[now],dfn[t]);
}
}
int limit,len,_a[N<<2],_b[N<<2],pos[N<<2];
inline void ntt(int *a,int inv)
{
for(int i=0;i<limit;++i)
if(i<pos[i]) swap(a[i],a[pos[i]]);
for(int mid=1;mid<limit;mid<<=1)
{
int Wn=fast(inv?3:gi,(p-1)/(mid<<1));
for(int r=mid<<1,j=0;j<limit;j+=r)
{
int w=1;
for(int k=0;k<mid;++k,w=w*Wn%p)
{
int x=a[j+k],y=w*a[j+k+mid]%p;
a[j+k]=(x+y)%p;
a[j+k+mid]=(x-y)%p;
if(a[j+k+mid]<0) a[j+k+mid]+=p;
}
}
}
}
inline vector operator * (const vector &a,const vector &b)
{
if(!a.size()) return b;
if(!b.size()) return a;
limit=1,len=0;
while(limit<=(int)(a.size()+b.size())) limit<<=1,++len;
for(int i=0;i<(int)a.size();++i) _a[i]=a[i];
for(int i=0;i<(int)b.size();++i) _b[i]=b[i];
for(int i=a.size();i<limit;i++) _a[i]=0;
for(int i=b.size();i<limit;i++) _b[i]=0;
for(int i=0;i<limit;++i) pos[i]=(pos[i>>1]>>1)|((i&1)<<(len-1));
ntt(_a,1);ntt(_b,1);
for(int i=0;i<limit;++i) _a[i]=_a[i]*_b[i]%p;
ntt(_a,0);
int inv=fast(limit,p-2);
vector c(a.size()+b.size()-1);
for(int i=0;i<(int)c.size();++i) c[i]=_a[i]*inv%p;
return c;
}
vector solve(int l,int r)
{
if(l==r) return t[l];
int mid=(l+r)>>1;
vector tl=solve(l,mid),tr=solve(mid+1,r);
return tl*tr;
}
inline void dfs(int now)
{
for(int i=G.head[now];i;i=G.nxt[i])
{
int t=G.to[i];
dfs(t);
}
if(now<=n)
{
if(!G.head[now])
{
f[now][0]=a[now]>=0,f[now][1]=a[now]>=1,f[now][2]=a[now]>=2;
return;
}
int cnt=0;
for(int i=G.head[now];i;i=G.nxt[i])
{
int v=G.to[i];
t[++cnt].clear();
if(v>n) t[cnt].push_back(f[v][2]);
t[cnt].push_back(f[v][1]),t[cnt].push_back(f[v][0]);
}
vector tmp=solve(1,cnt);
tmp.resize(a[now]+1);
for(int i=1;i<(int)tmp.size();++i) tmp[i]=(tmp[i]+tmp[i-1])%p;
if(a[now]>=0) f[now][0]=tmp[a[now]];
if(a[now]>=1) f[now][1]=tmp[a[now]-1];
if(a[now]>=2) f[now][2]=tmp[a[now]-2];
}
else
{
vector son;
for(int i=G.head[now];i;i=G.nxt[i])
{
int v=G.to[i];
son.push_back(v);
}
for(int pre=0;pre<2;++pre)
{
g[0][pre]=1,g[0][pre^1]=0;
for(int i=0,v;i<(int)son.size();++i)
{
v=son[i];
g[i+1][0]=(g[i][0]*f[v][1]+g[i][1]*f[v][2])%p;
g[i+1][1]=(g[i][0]*f[v][0]+g[i][1]*f[v][1])%p;
}
if(pre) f[now][2]+=g[son.size()][0]%=p,(f[now][1]+=g[son.size()][1])%=p;
else (f[now][1]+=g[son.size()][0])%=p,(f[now][0]+=g[son.size()][1])%=p;
}
}
}
inline void main()
{
n=read(),m=read();
for(int x,y,i=1;i<=m;++i)
{
x=read(),y=read();
P.link(x,y);
P.link(y,x);
}
for(int i=1;i<=n;++i) a[i]=read();
newnode=n;tarjan(1,0);dfs(1);
printf("%lld
",f[1][0]);
}
}
signed main()
{
red::main();
return 0;
}
考虑在已经处理好的图中新增一点(y),路径数怎么变化
显然新增加的路径数量要以(y)点结尾
设(f_x)是以(x)为结尾的交错路数量,那么新增加的交错路数量为
我们最后只关心这个结果的奇偶性
所以同色点和(f_x)为偶数的点都可以随意连边,对答案无影响,关键在于有多少(f_x)为奇数的点
我们可以设(dp[i][x][y])表示处理到(y)有(x)个黑色的奇数点和(y)个白色的奇数点,(O(n^3)dp)
更加仔细地观察,我们发现最后答案只和(x+y)的奇偶性有关
设(dp[i][j][x][y])表示前(i)个点,交错路径奇偶性、有没有(f_x)为奇数的黑点,有没有(f_x)为奇数的白点
考虑转移,如果下一个是白点
当(x=0),那么不管怎么连边,路径数都会加上一个奇数(因为黑点向白点的贡献都是偶数,白点本身又贡献了(1)),奇偶性改变
(dp[i+1][1-j][x][y]+=2^i*dp[i][j][x][y])
当(x=1),那么路径奇偶性变或不变都是(2^{i-1})种情况,只要考虑要不要把钦定的那个奇数点连上
(dp[i+1][1-j][x][1]+=2^{i-1}*dp[i][j][x][y])
(dp[i+1][j][x][y]+=2^{i-1}*dp[i][j][x][y])
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
inline int read()
{
int x=0;char ch,f=1;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') f=0,ch=getchar();
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return f?x:-x;
}
const int N=2e5+10,mod=998244353;
int n,opt;
int col[N];
int f[N][2][2][2],pw[N];
inline void add(int &x,int y)
{
x=x+y;
if(x>=mod) x-=mod;
}
inline void main()
{
n=read(),opt=read();
for(int i=pw[0]=1;i<=n;++i)
{
col[i]=read();
if(col[i]<0) col[i]=3;
else ++col[i];
pw[i]=pw[i-1]*2%mod;
}
f[0][0][0][0]=1;
for(int i=0;i<n;++i)
{
for(int j=0;j<2;++j)
{
for(int x=0;x<2;++x)
{
for(int y=0;y<2;++y)
{
if(f[i][j][x][y])
{
if(col[i+1]&1)//下一个点可以是黑
{
if(!y)//没有奇数的白点
{
add(f[i+1][j^1][1][y],f[i][j][x][y]*pw[i]%mod);
}
else
{
add(f[i+1][j][x][y],f[i][j][x][y]*pw[i-1]%mod);
add(f[i+1][j^1][1][y],f[i][j][x][y]*pw[i-1]%mod);
}
}
if(col[i+1]&2)
{
if(!x)
{
add(f[i+1][j^1][x][1],f[i][j][x][y]*pw[i]%mod);
}
else
{
add(f[i+1][j][x][y],f[i][j][x][y]*pw[i-1]%mod);
add(f[i+1][j^1][x][1],f[i][j][x][y]*pw[i-1]%mod);
}
}
}
}
}
}
}
int ans=f[n][opt][0][0]+f[n][opt][1][0]+f[n][opt][0][1]+f[n][opt][1][1];
printf("%lld
",ans%mod);
}
}
signed main()
{
red::main();
return 0;
}