发现好久没写题解了,补几场cf的题解。
略
略
略
吐了,辛辛苦苦推了一年依次问 1 ∼ n 1\sim n 1∼n的算法,结果交上去WA了,又分析了半天发现只有当 n > 5 n>5 n>5的时候才能保证正确,于是怒膜题解去了。
题解的询问方式是先依次问 n − 1 , n − 2 , . . . , 3 n-1,n-2,...,3 n−1,n−2,...,3,然后再依次问 1 , 2 , 1 1,2,1 1,2,1。
注意到我们问了 1 1 1两次之后,一定能得到 a 1 a_1 a1(第二次问时考虑triplet的增量为 ( a 1 + 1 2 ) \binom{a_1+1}{2} (2a1+1),这个是可以唯一还原 a 1 a_1 a1的)。同时第一次问 1 1 1时的straight增量为 a 2 ⋅ ( a 3 + 1 ) a_2\cdot (a_3+1) a2⋅(a3+1),而第二次问 1 1 1时的straight增量为 ( a 2 + 1 ) ⋅ ( a 3 + 1 ) (a_2+1)\cdot (a_3+1) (a2+1)⋅(a3+1),因此容易得到 a 2 a_2 a2和 a 3 a_3 a3。
再注意到对于 i ≥ 4 i\geq 4 i≥4,若我们已经得到了 a 1 ∼ a i − 1 a_1\sim a_{i-1} a1∼ai−1,那么容易根据问 i − 2 i-2 i−2时的straight增量得到 a i a_i ai(减掉已知部分剩下 ( a i − 1 + 1 ) ⋅ a i (a_{i-1}+1)\cdot a_i (ai−1+1)⋅ai,直接除即可)。
这样就可以在 n n n次操作后问出 a 1 ∼ a n a_1\sim a_n a1∼an了。时间复杂度为 O ( n ) \mathcal O(n) O(n)。
#include
using namespace std;
int a[105],b[105];
int num[105];
int main() {
int n;
scanf("%d",&n);
int x,y;
scanf("%d%d",&x,&y);
for(int i=n-1;i>2;i--) {
printf("+ %d\n",i);
fflush(stdout);
scanf("%d%d",&a[i],&b[i]);
}
printf("+ %d\n",1);
fflush(stdout);
scanf("%d%d",&a[1],&b[1]);
printf("+ %d\n",2);
fflush(stdout);
scanf("%d%d",&a[2],&b[2]);
printf("+ %d\n",1);
fflush(stdout);
scanf("%d%d",&x,&y);
x-=a[2];
y-=b[2];
a[2]-=a[1];
b[2]-=b[1];
a[1]-=a[3];
b[1]-=b[3];
for(int i=3;i<n;i++) {
a[i]-=a[i+1];
b[i]-=b[i+1];
}
for(int i=2;i<=n+1;i++)
if ((i*(i-1)>>1)==x) num[1]=i-1;
num[3]=y-b[1]-1;
num[2]=b[1]/(num[3]+1);
assert(num[1]>=0&&num[2]>=0&&num[3]>=0);
for(int i=4;i<=n;i++) {
int s=b[i-2];
if (i>4) s-=num[i-3]*num[i-4];
s-=(num[i-3]+(i==4))*(num[i-1]+1);
num[i]=s/(num[i-1]+1)-(i<n);
assert(num[i]>=0);
}
printf("! ");
for(int i=1;i<=n;i++) printf("%d ",num[i]);
printf("\n");
fflush(stdout);
return 0;
}
/*
5
1 6
1 15
4 20
5 24
5 40
8 48
*/
这题好神啊,膜了一天题解。
显然只用考虑线性基里的元素。令线性基大小为 k k k,只用求出只使用线性基里元素的答案再乘上 2 n − k 2^{n-k} 2n−k即可。我们对线性基进行一点处理,使得每个里面的元素都在某一列为主元(该列只有它为 1 1 1),此时元素为 a 1 ∼ a k a_1\sim a_k a1∼ak。
当 k k k小的时候显然有一个dfs所有元素的算法,用__builtin_popcountll()即可做到 O ( 2 k ) \mathcal O(2^k) O(2k)求解。
当 k k k大的时候一个算法是对于非主元的部分采用DP。大致是依次枚举每个数,记录 F [ S ] [ i ] F[S][i] F[S][i]表示考虑到当前数字,非主元部分异或和为 S S S,主元部分有 i i i个(也即恰好选了 i i i个)的方案数,转移直接考虑这个数选不选即可。时间复杂度为 O ( k 2 2 m − k ) \mathcal O(k^22^{m-k}) O(k22m−k)。平衡一下两个算法可以做到 O ( n m + m ⋅ 2 m 2 ) \mathcal O(nm+m\cdot 2^{\frac{m}{2}}) O(nm+m⋅22m)的复杂度,只能通过E1。
考虑优化 k k k大时的算法。
考虑用FWT求解。现在要求 p c p_c pc,令线性基元素能表出的集合幂级数为 F F F, c c c位二进制整数对应的集合幂级数为 G c G_c Gc,则 p c = ( [ x 0 ] I F W T ( F W T ( F ) ⋅ F W T ( G c ) ) ) ⋅ 2 n − k p_c=([x^0]IFWT(FWT(F)\cdot FWT(G_c)))\cdot 2^{n-k} pc=([x0]IFWT(FWT(F)⋅FWT(Gc)))⋅2n−k(这里集合幂级数的乘法是点乘)。考虑 F W T ( F ) FWT(F) FWT(F),根据FWT的公式, [ x S ] F W T ( F ) = ∑ i ( − 1 ) ∣ i ∧ S ∣ ( [ x i ] F ) [x^S]FWT(F)=\sum_{i}(-1)^{|i\land S|}([x^i]F) [xS]FWT(F)=∑i(−1)∣i∧S∣([xi]F)。注意到 [ x i ] F [x^i]F [xi]F只有当 i i i能被线性表出的时候为 1 1 1,否则为 0 0 0。
那么考虑把 i i i拆成 a a a中元素的异或,此时可以把 ( − 1 ) ∣ i ∧ S ∣ (-1)^{|i\land S|} (−1)∣i∧S∣拆开,容易证明 [ x S ] F W T ( F ) [x^S]FWT(F) [xS]FWT(F)只有在 ∀ 1 ≤ i ≤ k , ∣ S ∧ a i ∣ ≡ 0 ( m o d 2 ) \forall 1\leq i\leq k,|S\land a_i|\equiv 0(\bmod \ 2) ∀1≤i≤k,∣S∧ai∣≡0(mod 2)时为 2 k 2^k 2k,其余时候均为 0 0 0。
也即,若将线性基里的元素写成一个 k ⋅ m k\cdot m k⋅m的矩阵 A A A,将 S S S按位写成长为 m m m的列向量 x x x,那么 [ x S ] F W T ( F ) = 2 k [x^S]FWT(F)=2^k [xS]FWT(F)=2k当且仅当 A x = 0 Ax=0 Ax=0( m o d 2 \bmod \ 2 mod 2意义下),也即 x x x在 A A A的解空间中。由于 r a n k ( A ) = k rank(A)=k rank(A)=k,根据一点简单的线代知识, A A A的解空间的秩为 m − k m-k m−k,且可知这样的 S S S为 m − k m-k m−k个数的张成。
又可以注意到 [ x S ] G c [x^S]G_c [xS]Gc只跟 ∣ S ∣ |S| ∣S∣有关,可以预先 O ( m 3 ) \mathcal O(m^3) O(m3)DP出 g i , j g_{i,j} gi,j表示当 c = i , ∣ S ∣ = j c=i,|S|=j c=i,∣S∣=j时 [ x S ] G [x^S]G [xS]G的值。那么直接dfs所有可能的 S S S,算出每种 ∣ S ∣ |S| ∣S∣有多少个数,再乘上对应的系数即可。这部分时间复杂度为 O ( 2 m − k + m 3 ) \mathcal O(2^{m-k}+m^3) O(2m−k+m3)。
那么与 k k k小时的算法平衡一下即可做到 O ( n m + m 3 + 2 m 2 ) \mathcal O(nm+m^3+2^{\frac{m}{2}}) O(nm+m3+22m)的时间复杂度,可以通过E2。
#include
#define MOD 998244353
#define inv2 499122177
using namespace std;
typedef long long ll;
ll pow_mod(ll x,int k) {
ll ans=1;
while (k) {
if (k&1) ans=ans*x%MOD;
x=x*x%MOD;
k>>=1;
}
return ans;
}
ll C[60][60];
void pre(int n) {
for(int i=0;i<=n;i++) C[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
}
namespace LB {
ll num[60];
bool insert(ll x,int k) {
for(int i=k-1;i>=0;i--)
if ((x>>i)&1) {
if (!num[i]) {
num[i]=x;
return 1;
}
else x^=num[i];
}
return 0;
}
void gause(int k) {
for(int i=k-1;i>=0;i--)
if (num[i]) {
for(int j=i+1;j<k;j++)
if ((num[j]>>i)&1) num[j]^=num[i];
}
}
}
int ans[60];
ll a[60],b[60];
int up;
void dfs1(int d,ll s) {
if (d>up) {
ans[__builtin_popcountll(s)]++;
return;
}
dfs1(d+1,s);
dfs1(d+1,s^a[d]);
}
int cnt[60];
void dfs2(int d,ll s) {
if (d>up) {
cnt[__builtin_popcountll(s)]++;
return;
}
dfs2(d+1,s);
dfs2(d+1,s^b[d]);
}
bool vis[60];
int main() {
int n,m;
scanf("%d%d",&n,&m);
pre(m);
for(int i=1;i<=n;i++) {
ll x;
scanf("%lld",&x);
LB::insert(x,m);
}
LB::gause(m);
int sz=0;
for(int i=m-1;i>=0;i--)
if (LB::num[i]) {
a[++sz]=LB::num[i];
vis[i]=1;
}
if (sz*2<=m) {
up=sz;
dfs1(1,0);
}
else {
for(int i=0;i<m;i++)
if (!vis[i]) {
b[++up]=(1LL<<i);
for(int j=i+1;j<m;j++)
if (LB::num[j]&&(((LB::num[j])>>i)&1)) b[up]^=(1LL<<j);
}
dfs2(1,0);
for(int i=0;i<=m;i++)
for(int j=0;j<=i;j++)
for(int k=0;k<=m-i;k++)
ans[j+k]=(ans[j+k]+C[i][j]*C[m-i][k]%MOD*((j&1)?MOD-1:1)%MOD*cnt[i])%MOD;
ll inv=pow_mod(inv2,up);
for(int i=0;i<=m;i++) ans[i]=ans[i]*inv%MOD;
}
ll v=pow_mod(2,n-sz);
for(int i=0;i<=m;i++) printf("%lld ",ans[i]*v%MOD);
printf("\n");
return 0;
}
跟NOI2018 D2T2很像,做过那个题的话思维上不存在太大的难度。
分选的两条链LCA是否相同讨论。
若LCA不相同,一定满足较浅的LCA是较深的LCA的祖先。那么枚举较深的LCA的链,显然另一条链只会跟它到LCA某侧的路径有交,因此一个端点在某个距LCA距离为 k k k的点的子树中,而另一个端点在LCA的子树外,这可以用线段树合并方便地统计答案。
若LCA相同,如果此时选的两条链有一个端点为LCA或者不在两边都有交跟上面情况类似,只考虑两条链两个端点都在某两个特定子树中的情况。考虑把这些链在一边子树中的端点拿出来建一个虚树。考虑枚举两条链这边端点的LCA,做一个启发式合并,那么固定了一条链和当前枚举的LCA的话,就知道另一边LCA深度的下界了,此时跟它有贡献的链在另一边的端点在某个子树中,数据结构维护即可。
实际实现的时候可以用链分治替代启发式合并。具体来说考虑dfs虚树,dfs到一个点的时候先递归重儿子,再加入一端为这个点的链,再dfs轻儿子,这样只需要对dfs序开 O ( log n ) \mathcal O(\log n) O(logn)个树状数组即可。
时间复杂度 O ( n log n + q log 2 n ) \mathcal O(n\log n+q\log^2n) O(nlogn+qlog2n)。
#include
#define FR first
#define SE second
#define lowbit(x) (x&-x)
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
namespace SGT {
const int Maxn=20000000;
int ch[Maxn][2],size[Maxn],tot;
inline int cpynode(int o) {
tot++;
ch[tot][0]=ch[o][0];ch[tot][1]=ch[o][1];
size[tot]=size[o];
return tot;
}
int update(int l,int r,int o,int p) {
o=cpynode(o);
size[o]++;
if (l==r) return o;
else {
int m=((l+r)>>1);
if (m>=p) ch[o][0]=update(l,m,ch[o][0],p);
else ch[o][1]=update(m+1,r,ch[o][1],p);
return o;
}
}
int merge(int l,int r,int x,int y) {
if (!x) return y;
if (!y) return x;
int o=++tot;
size[o]=size[x]+size[y];
if (l==r) return o;
else {
int m=((l+r)>>1);
ch[o][0]=merge(l,m,ch[x][0],ch[y][0]);
ch[o][1]=merge(m+1,r,ch[x][1],ch[y][1]);
return o;
}
}
int query(int l,int r,int o,int lx,int rx) {
if (!o) return 0;
if (l>=lx&&r<=rx) return size[o];
else {
int m=((l+r)>>1);
if (m>=rx) return query(l,m,ch[o][0],lx,rx);
if (m<lx) return query(m+1,r,ch[o][1],lx,rx);
return query(l,m,ch[o][0],lx,rx)+query(m+1,r,ch[o][1],lx,rx);
}
}
}
struct BIT {
int sumv[200005];
void add(int x,int n,int num) {
for(;x<=n;x+=lowbit(x)) sumv[x]+=num;
}
int sum(int x) {
int s=0;
for(;x;x-=lowbit(x)) s+=sumv[x];
return s;
}
int sum(int l,int r) {
return sum(r)-sum(l-1);
}
};
BIT tr[20];
vector <int> e[200005];
int fa[200005][20],dep[200005];
int dfn[200005],ed[200005],dfs_cnt;
int size[200005];
void dfs1(int x) {
dfn[x]=++dfs_cnt;
size[x]=1;dep[x]=dep[fa[x][0]]+1;
for(int i=0;i<e[x].size();i++)
if (e[x][i]!=fa[x][0]) {
int u=e[x][i];
fa[u][0]=x;
for(int j=1;j<20;j++) fa[u][j]=fa[fa[u][j-1]][j-1];
dfs1(u);
size[x]+=size[u];
}
ed[x]=dfs_cnt;
}
int lca(int x,int y) {
if (dep[x]<dep[y]) swap(x,y);
int d=dep[x]-dep[y];
for(int i=0;i<20;i++)
if ((d>>i)&1) x=fa[x][i];
if (x==y) return x;
for(int i=19;i>=0;i--)
if (fa[x][i]!=fa[y][i]) {
x=fa[x][i];
y=fa[y][i];
}
return fa[x][0];
}
int jump(int x,int d) {
for(int i=0;i<20;i++)
if ((d>>i)&1) x=fa[x][i];
return x;
}
ll ans;
int n,k;
vector <pr> q[200005];
vector <int> vt1[200005];
int root[200005];
map <pr,int> mp;
vector <pr> vt2[200005];
pr num[200005];
bool cmp(int x,int y) {
return dfn[x]<dfn[y];
}
int st[200005],val[200005];
vector <int> ee[200005],vt3[200005],vt4[200005];
void dfs3(int x,int top,int d,int rt) {
int son=0;
for(int i=0;i<ee[x].size();i++)
if (size[ee[x][i]]>size[son]) son=ee[x][i];
if (son) dfs3(son,top,d,rt);
for(int i=0;i<vt3[x].size();i++) {
int u=vt3[x][i];
vt4[top].push_back(u);
int s=k-(dep[x]-dep[rt]);
if (s<=0) ans+=tr[d].sum(dfn[rt],ed[rt]);
else if (dep[u]-dep[rt]>=s) {
int t=jump(u,dep[u]-dep[rt]-s);
ans+=tr[d].sum(dfn[t],ed[t]);
}
tr[d].add(dfn[u],n,1);
}
for(int i=0;i<ee[x].size();i++)
if (ee[x][i]!=son) {
int u=ee[x][i];
dfs3(u,u,d+1,rt);
for(int i=0;i<vt4[u].size();i++) {
int v=vt4[u][i];
int s=k-(dep[x]-dep[rt]);
if (s<=0) ans+=tr[d].sum(dfn[rt],ed[rt]);
else if (dep[v]-dep[rt]>=s) {
int t=jump(v,dep[v]-dep[rt]-s);
ans+=tr[d].sum(dfn[t],ed[t]);
}
}
for(int i=0;i<vt4[u].size();i++) {
int v=vt4[u][i];
vt4[top].push_back(v);
tr[d].add(dfn[v],n,1);
tr[d+1].add(dfn[v],n,-1);
}
vector<int>().swap(vt4[u]);
}
vector<int>().swap(ee[x]);
vector<int>().swap(vt3[x]);
}
void solve(int rt,int d) {
int u=num[d].FR,v=num[d].SE;
int sz=0;
for(int i=0;i<vt2[d].size();i++) {
val[++sz]=vt2[d][i].FR;
vt3[vt2[d][i].FR].push_back(vt2[d][i].SE);
}
sort(val+1,val+sz+1,cmp);
int top=0;
st[++top]=u;
for(int i=1;i<=sz;i++)
if (val[i]!=st[top]) {
int x=val[i],p=lca(x,st[top]);
for(;;) {
if (dep[p]<=dep[st[top-1]]) {
ee[st[top-1]].push_back(st[top]);
top--;
}
else if (dep[p]<dep[st[top]]) {
ee[p].push_back(st[top]);
st[top]=p;
}
else break;
}
if (st[top]!=x) st[++top]=x;
}
for(;top>1;top--) ee[st[top-1]].push_back(st[top]);
dfs3(u,u,1,rt);
for(int i=0;i<vt4[u].size();i++) tr[1].add(dfn[vt4[u][i]],n,-1);
vector<int>().swap(vt4[u]);
}
void dfs2(int x) {
for(int i=0;i<e[x].size();i++)
if (e[x][i]!=fa[x][0]) {
int u=e[x][i];
dfs2(u);
root[x]=SGT::merge(1,n,root[x],root[u]);
}
for(int i=0;i<vt1[x].size();i++)
root[x]=SGT::update(1,n,root[x],dfn[vt1[x][i]]);
ll s=0;
int sz=0;
for(int i=0;i<q[x].size();i++) {
int u=q[x][i].FR,v=q[x][i].SE;
int t1=((u==x)?x:jump(u,dep[u]-dep[x]-1)),t2=((v==x)?x:jump(v,dep[v]-dep[x]-1));
if (dep[u]-dep[x]>=k) {
int rt=root[jump(u,dep[u]-dep[x]-k)];
ans+=SGT::query(1,n,rt,1,n)-SGT::query(1,n,rt,dfn[x],ed[x]);
s+=SGT::query(1,n,rt,dfn[x],ed[x]);
if (t1!=x) s-=SGT::query(1,n,rt,dfn[t1],ed[t1]);
if (t2!=x) s-=SGT::query(1,n,rt,dfn[t2],ed[t2]);
if (v==x) s--;
}
if (dep[v]-dep[x]>=k) {
int rt=root[jump(v,dep[v]-dep[x]-k)];
ans+=SGT::query(1,n,rt,1,n)-SGT::query(1,n,rt,dfn[x],ed[x]);
s+=SGT::query(1,n,rt,dfn[x],ed[x]);
if (t1!=x) s-=SGT::query(1,n,rt,dfn[t1],ed[t1]);
if (t2!=x) s-=SGT::query(1,n,rt,dfn[t2],ed[t2]);
if (u==x) s--;
}
if (u!=x&&v!=x) {
if (!mp.count(pr(t1,t2))) {
mp[pr(t1,t2)]=++sz;
num[sz]=pr(t1,t2);
}
vt2[mp[pr(t1,t2)]].push_back(pr(u,v));
}
}
ans+=(s>>1);
for(int i=1;i<=sz;i++)
solve(x,i);
for(int i=1;i<=sz;i++) vector<pr>().swap(vt2[i]);
mp.clear();
sz=0;
}
int main() {
int m;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<n;i++) {
int x,y;
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
dfs1(1);
for(int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
if (dfn[x]>dfn[y]) swap(x,y);
q[lca(x,y)].push_back(pr(x,y));
vt1[x].push_back(y);
vt1[y].push_back(x);
}
dfs2(1);
printf("%lld\n",ans);
return 0;
}
/*
5 3 2
1 2
2 3
1 4
4 5
1 4
2 5
4 3
*/