BZOJ4784-4786
(直接进正题)
T1:
(敢不敢再短点)
step1:如果不是仙人掌,那显然是没有救的。。(puts("0");)
step2:如果是,先拆开环。你会发现每棵树之间的方案数是可以用乘法定理的,然后就变成了求树的问题。。
step3:(然后据说树形dp+FFT可以过,然而完全不用这么做)把剩下的非环边钦定为重边,然后你发现每个点都必须至少向外连一条边
step4:这样之后考虑每个点为根时几个儿子两两匹配的方案数就好了
dp[i]表示i个东西两两匹配的方案数。考虑递推算,dp[i]=(i-1)*dp[i-2]+dp[i-1](要么加一个点不连,要么与其中一个点连边,其余的随意(dp[i-2]))
#include
#define gc getchar()
#define ll long long
#define mod 998244353
#define N 500009
#define M N<<1
using namespace std;
ll n,m,number,dfn[N],cnt,dp[N],fa[N],vis[N],first[N],size[N];
struct edge
{
ll to,next;
void add(ll x,ll y)
{
to=y,next=first[x],first[x]=number;
}
}e[M<<1];
ll read()
{
ll x=1;
char ch;
while (ch=gc,ch<'0'||ch>'9') if (ch=='-') x=-1;
ll s=ch-'0';
while (ch=gc,ch>='0'&&ch<='9') s=s*10+ch-'0';
return s*x;
}
ll dfs(ll x)
{
dfn[x]=++cnt;
for (ll i=first[x];i;i=e[i].next)
if (!dfn[e[i].to])
{
fa[e[i].to]=x;
if (dfs(e[i].to)) return 1;
}
else
if (dfn[e[i].to]>dfn[x])
{
size[x]-=2;
for (ll y=e[i].to;y!=x;y=fa[y])
if (vis[y]) return 1;
else vis[y]=1,size[y]-=2;
}
return 0;
}
int main()
{
ll T=read();
dp[0]=dp[1]=1;
for (ll i=2;i
参考 qzh_1430586275
T2:
(概率吓得根本不敢做。。)
我们可以轻(jian)易(nan)发现,这个错误树状数组的qry就是在求后缀异或和,然后与答案相等其实就是a[l-1]=a[r](这是原数组)
当然还要特判(似乎很坑啊),当l=1时,qry(l-1)=0,然后就是r的前缀异或和=r的后缀异或和,也就是a[r]=sigma (i=1~n) a[i],这个特判显然开个线段树区间维护,单点查询就好了。
然后回到一般情况,考虑用二维线段树维护一个东西:就是两维的坐标的代表的编号(x,y),a[x]=a[y]的概率。然后考虑插入时,只需要考虑上半部分,因为qry时r显然要大于l。
考虑合并两个概率,设p1为原来相同的概率,p2为加入的相同的概率,那答案就是p=p1*p2+(1-p1)*(1-p2)。(逆元搞一搞其实提示的似乎并不需要,预处理显然更快啊)
#include
#define gc getchar()
#define ll long long
#define mod 998244353
#define N 100009
#define mid ((l+r)>>1)
#define Root rt[cur],1,n
#define Lson ls[cur],l,mid
#define Rson rs[cur],mid+1,r
#define root 1,1,n
#define lc cur<<1
#define rc lc|1
#define lson lc,l,mid
#define rson rc,mid+1,r
#define now cur,l,r
using namespace std;
int n,m,x,y,z,inv[N],rt[N<<2],ls[N<<8],rs[N<<8],cnt,sum;
struct pro
{
int p;
pro(int p=0):p(p){}
}s[N<<8],ans,tg[N<<2];
pro operator +(const pro &a,const pro &b)
{
return pro(((ll)a.p*b.p%mod+(ll)(1-a.p+mod)*(1-b.p+mod)%mod)%mod);
}
int read()
{
int x=1;
char ch;
while (ch=gc,ch<'0'||ch>'9') if (ch=='-') x=-1;
int s=ch-'0';
while (ch=gc,ch>='0'&&ch<='9') s=s*10+ch-'0';
return s*x;
}
void ins2(int &cur,int l,int r,int L,int R,pro p)
{
if (!cur) s[cur=++cnt]=pro(1);
if (L<=l&&R>=r)
{
s[cur]=s[cur]+p;
return;
}
if (L<=mid) ins2(Lson,L,R,p);
if (R>mid) ins2(Rson,L,R,p);
}
void ins1(int cur,int l,int r,int L1,int R1,int L2,int R2,pro p)
{
if (L1<=l&&R1>=r)
{
ins2(Root,L2,R2,p);
return;
}
if (L1<=mid) ins1(lson,L1,R1,L2,R2,p);
if (R1>mid) ins1(rson,L1,R1,L2,R2,p);
}
pro qry2(int cur,int l,int r,int x)
{
if (!cur) return pro(1);
if (l==r) return s[cur];
if (x<=mid) return s[cur]+qry2(Lson,x);
else return s[cur]+qry2(Rson,x);
}
pro qry1(int cur,int l,int r,int x,int y)
{
pro tmp=pro(1);
if (rt[cur]) tmp=qry2(Root,y);
if (l==r) return tmp;
if (x<=mid) return tmp+qry1(lson,x,y);
else return tmp+qry1(rson,x,y);
}
void ins(int cur,int l,int r,int L,int R,pro p)
{
if (L<=l&&R>=r)
{
tg[cur]=tg[cur]+p;
return;
}
if (L<=mid) ins(lson,L,R,p);
if (R>mid) ins(rson,L,R,p);
}
pro qry(int cur,int l,int r,int x)
{
if (l==r) return tg[cur];
if (x<=mid) return tg[cur]+qry(lson,x);
else return tg[cur]+qry(rson,x);
}
int main()
{
inv[0]=inv[1]=1;
for (int i=2;i1) ins1(root,1,y-1,y,z,tmp);
if (z
(这tm还带卡常数的撒???)
首先一个串(多项式系数)的平方,显然只剩下平方项,其他都带2。。。然后我们就get到了轻易获得一个串平方后结果的方法。
考虑对于多项式的次数倍增,每次如果次数乘2,显然就是平方。乘2加1就是在乘上一个原串。
——————————————————————————————————————————————————————————————
不妨先只考虑整个串的情况,考虑维护2^max(n+1,k)个不同的串的数量(匹配数)(额,就是维护max(n+1,k)位的各种不同串的数量)。
(之后M=max(n+1,k))(程序里M为这里-1)
先预处理出次数乘2时,结果串的前M位数和第2位到第M+1位数;
次数乘2加1时,算中间那两种(具体怎么中间看程序),然后开头可以用特殊姿势ka过去
然后你发现这样就可以轻易地从原来的串的长度=M的子串所有的串推到结果串所有长度=M的子串啦
用dp数组保存下来每个串出现的个数,然后发现结果串最后M位左右(其实我也不知道到底有几位,算算就好了)没有被统计,没错,然后暴力加就好了(手动滑稽)
最后加答案时因为(很有)可能k —————————————————————————————————————————————————————————————— 这样我们开心(绝望)地发现这道题并不是求全局的答案。不过可以转成求前缀,然后我想了n久没想出啥正常办法,想不会是一开始算的时候就已经限制好了,一看标称,还真tm是。。(晕) lim数组记录此时(大概)到维护第几位可以等效于最后的要求的前limit位 然后让我绝望的是这(居然)是大概,还要各种对答案++--。。。。。。。 开个bitset维护最后面几位,在dp转移完后,强行对答案再进行修正(具体见代码,这段抄标程的,等以后完全搞懂了有空再补吧。。) 对于上面说的开头部分,似乎可能大概是现在初始串后面加上M个零,然后前面没有统计的只会是一堆零,然后注意每次转移后去掉dp[0]的一种 —————————————————————————————————————————————————————————————— 然后讲道理这样就能过了,可我交到uoj上只有30,然后开了o2(不知道有没有效果)也就70(但多过的点全是2900ms+),我第n次绝望。。。。n次卡常数后卡过了(终于) 后来一天后发现标程似乎用了某种指针常数优化,随手加上,常数一下变为原来的五分之一。。。!!!(剧毒吧)#pragma GCC optimize("O2")
#include
>((M<<2ll)+2-i)&Max_j).to_ulong()];
o>>=M*3+1-del;
o&=Max_sq_j;
}
else
{
for (i=0;i>(M*3ll+2-i)&Max_j).to_ulong()];
o=p>>((M<<1ll|1)-del)&Max_sq_j;
}
dp[0]--,len-=del,top--;
}
for (i=0;i<(1ll<<(M-K));++i)
ans+=dp[qry|i];
for (i=0;i<=Max;i++) dp[i]=0;
return ans;
}
int main()
{
register ll T=Read(),i,j,Now,now;
while (T--)
{
n=Read(),m=Read(),K=Read()-1;
R=n*m+1-Read(),L=n*m+1-Read();
first=qry=0;
M=max(n,K);
for (i=0;i<=n;++i) first=first<<1|get();
for (i=0;i<=K;++i) qry|=get()<<(M-i);
Max=(1ll<<(M+1))-1;
Max_sq=(1ll<<((M+1)<<1))-1;
for (i=1;i<(1<<(M+1));++i)
{
Now=now=0;
for (j=0;j<=M;++j) now|=(i>>j&1)<<(j<<1);
go0[i]=now>>M;
go1[i]=(now>>(M-1))&Max;
for (j=0;j<=M;++j)
if (first>>j&1) Now^=(now<