noi.ac题目选作

$noi.ac$上好题选做

#489 shuffle NOI模拟赛第一场T2

题面描述:对于一个长度为$m$的序列$a_1,a_2,…,a_m$,小Q可以将这个序列随意打乱成序列$b_1,b_2,…,b_m$,使得$a_i=b_i(1≤i≤m)$的位置数量尽可能少。

给定一个长度为$n$的序列$s_1,s_2,…,s_n$,它有$2^n-1$个非空子序列。请对于每个$k=0,1,2,…,n$统计$s$有多少非空子序列$a$经过重排成$b$后,$a_i=b_i$的位置数量的最小可能值恰好为$k$。

答案对$10^9+7$取模,$n\leq250000$

$solution$:我们只需统计出所有$k>0$的答案,用$2^n-1$减去所有$k>0$的答案即为$k=0$的答案。

我们把一个子序列重排后$a_i=b_i$的最小可能值称为子序列的价值,根据$Hull’s\ Theorem$,设一个长度为$m$的子序列中出现次数最多的数字出现了$p$次,则子序列的价值为$max(0,2p-m)$

设一个子序列中出现次数最多的数字称为这个子序列的主数字(主数字若有多个,则根据上面分析这个子序列的价值为$0$,无需考虑)。设数字$u$在数列中出现了$N_u$次,则所有主数字为$u$的子序列的贡献可以用多项式表示:

$p_u(x)=\sum_{i=0}^{N_u}C_{N_u}^ix^i=(1+x)^{N_u}$

$q_u(x)=\sum_{i=0}^{n-N_u}C_{n-N_u}^ix^{-i}=\sum_{i=0}^{n-N_u}C_{n-N_u}^{n-N_u-i}x^{n-N_u-i}/x^{n-N_u}=(1+x)^{n-N_u}/x^{n-N_u}$

$G_u(x)=p_u(x)q_u(x)x^{N_u}=(1+x)^nx^{N_u}$的第$n+i$项系数就是这类主序列对$k=i$的答案的贡献。

所有子序列对$k>0$的每一项$k$答案的贡献为

$\sum G_u(x)=(1+x)^n(\sum_ux^{N_u})$的$n+k$次项系数

使用多项式乘法即可,由于$10^9+7$不是$NTT$模数,因此需使用$MTT$,新板子$get$

点击显示/隐藏内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
typedef int pc;
#define int ll
ll cnt=0,pre=-1,n,tot=0,i,fac[250005],invfac[250005],x[250005],a[1000005],b[1000005],ans[250005];
const ll mod=1e9+7;
ll pow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
ll C(ll n,ll m){return fac[n]*invfac[m]%mod*invfac[n-m]%mod;}
typedef long long int64;
typedef long double D;

const int MAXN = 524288;
const D PI = acos(-1);

struct complex {
D real, imag;
complex() { real = imag = 0; }
complex(D x): real(x), imag(0) {}
complex(D x, D y): real(x), imag(y) {}
inline complex conj() { return complex(real, -imag); }
inline complex operator+(complex rhs) const { return complex(real + rhs.real, imag + rhs.imag); }
inline complex operator-(complex rhs) const { return complex(real - rhs.real, imag - rhs.imag); }
inline complex operator*(complex rhs) const { return complex(real * rhs.real - imag * rhs.imag,
imag * rhs.real + real * rhs.imag); }
inline complex operator*=(complex rhs) { return (*this) = (*this) * rhs; }
//(a+bi)(c+di) = (ac-bd) + (bc+ad)i
friend inline complex operator*(D x, complex cp) { return complex(x * cp.real, x * cp.imag); }
inline complex operator/(D x) const { return complex(real / x, imag / x); }
inline complex operator/=(D x) { return (*this) = (*this) / x; }
friend inline complex operator/(D x, complex cp) { return x * cp.conj() / (cp.real * cp.real - cp.imag * cp.imag); }
inline complex operator/(complex rhs) const {
return (*this) * rhs.conj() / (rhs.real * rhs.real - rhs.imag * rhs.imag);
}
inline complex operator/=(complex rhs) { return (*this) = (*this) / rhs; }
inline D length() { return sqrt(real * real + imag * imag); }
};

inline complex get_omega(int len, bool inv) {
return inv ? complex(std::cos(2*PI / len), -std::sin(2*PI / len))
:complex(std::cos(2*PI / len), std::sin(2*PI / len));
}

inline void fft(int len, complex* A, bool inv = false) {
static int R[MAXN+10];
for(int i = 0; i < len; i++)
R[i] = ((R[i>>1]>>1) | (len >> (i&1))) & (len-1);
for(int i = 0; i < len; i++)
if(R[i] > i) std::swap(A[i], A[R[i]]);
for(int step = 1; step < len; step <<= 1)
for(int i = 0; i < len; i += (step<<1)) {
complex omega = 1, t = 0, rt = get_omega(step<<1, inv);
for(int j = 0; j < step; j++, omega *= rt) {
t = A[i+j+step] * omega;
A[i+j+step] = A[i+j] - t;
A[i+j] = A[i+j] + t;
}
}
if(inv)
for(int i = 0; i < len; i++)
A[i] /= len;
}

void mtt(int deg, int *lhs, int *rhs, int *ret,int n,int m,int MOD) {
static complex A[MAXN+10], B[MAXN+10], C[MAXN+10], D[MAXN+10],
E[MAXN+10], F[MAXN+10], G[MAXN+10], H[MAXN+10];
int len; for(len = 1; len <= 2*deg; len<<=1);
for(int i = 0; i < len; i++) {
lhs[i] %= MOD; rhs[i] %= MOD;
A[i] = lhs[i] >> 15; B[i] = lhs[i] & 0x7fff;
C[i] = rhs[i] >> 15; D[i] = rhs[i] & 0x7fff;
}
fft(len, A); fft(len, B); fft(len, C); fft(len, D);
for(int i = 0; i < len; i++) {
E[i] = A[i] * C[i]; F[i] = B[i] * C[i];
G[i] = A[i] * D[i]; H[i] = B[i] * D[i];
}
fft(len, E, true); fft(len, F, true);
fft(len, G, true); fft(len, H, true);
for(int i = 0; i < len; i++)
ret[i] = (((int64(round(E[i].real)) % MOD)<<30) % MOD + ((int64(round(F[i].real)) % MOD)<<15) % MOD
+ ((int64(round(G[i].real)) % MOD)<<15) % MOD + int64(round(H[i].real)) % MOD) % MOD;
}
pc main()
{
scanf("%lld",&n);
fac[0]=1;
for(i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
invfac[n]=pow(fac[n],mod-2LL);
for(i=n-1;i>=0;i--)invfac[i]=invfac[i+1]*(i+1)%mod;
for(i=1;i<=n;i++)scanf("%lld",&x[i]);
for(i=0;i<=n;i++)a[i]=C(n,i);
sort(x+1,x+n+1);
pre=-1;cnt=0;
for(i=1;i<=n;i++)
{
if(x[i]==pre)cnt++;
else
{
if(pre!=-1)b[cnt]++;
cnt=1;pre=x[i];
}
}
b[cnt]++;
//for(i=0;i<=n;i++)printf("%lld %lld\n",a[i],b[i]);
mtt(n,a,b,ans,n,n,1e9+7);
tot=(pow(2LL,n)-1LL+mod)%mod;
for(i=1;i<=n;i++)ans[i]=ans[n+i];
for(i=1;i<=n;i++)tot=(tot-ans[i]+mod)%mod;
printf("%lld\n",tot);
for(i=1;i<=n;i++)printf("%lld\n",ans[i]);
return 0;
}

#170 数数 NOI模拟赛第四场T1

求有多少对$1-n$的排列$(a,b)$满足$\sum_{i=1}^nmax{a_i,b_i}>m$

$n\leq 50$

考虑两个$1-n$的递增数列$a,b,a_i=b_i=i$,用$a,b$元素的互相一一配对表示排列对,最终结果乘$n!$即可

我们考虑从前往后加入那些配对,我们规定某个配对一定是扫到标号较大的那个元素时将这个配对加入(因为一个配对的价值取决于标号较大的元素)

当我们扫到$i$时,$a[1:i]$中还未配对的元素个数肯定与$b[1:i]$中还未配对的元素个数是相等的(证明显然)设$dp[i][j][k]$表示扫到$i$,前$i$个中$a,b$还各有$j$个未配对,当前所加入的配对价值和未$k$。转移显然,复杂度$O(n^4)$

点击显示/隐藏内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll n,m,i,dp[55][55][2505],j,k,ans=0;
ll M(ll a)
{
if(a>=mod)return a-mod;
if(a<0)return a+mod;
return a;
}
int main()
{
scanf("%lld%lld",&n,&m);
dp[0][0][0]=1;
for(i=0;i<n;i++)
{
for(j=0;j<=i;j++)
{
for(k=0;k<=n*n;k++)
{
if(!dp[i][j][k])continue;
dp[i+1][j+1][k]=M(dp[i+1][j+1][k]+dp[i][j][k]);
if(j>0)dp[i+1][j][k+i+1]=(dp[i+1][j][k+i+1]+j*dp[i][j][k])%mod;
if(j>0)
{
dp[i+1][j][k+i+1]=(dp[i+1][j][k+i+1]+j*dp[i][j][k])%mod;
dp[i+1][j-1][k+2*i+2]=(dp[i+1][j-1][k+2*i+2]+j*j*dp[i][j][k])%mod;
}
dp[i+1][j][k+i+1]=M(dp[i+1][j][k+i+1]+dp[i][j][k]);
}
}
}
for(i=m;i<=n*n;i++)ans=M(ans+dp[n][0][i]);
for(i=1;i<=n;i++)ans=ans*i%mod;
printf("%lld\n",ans);
return 0;
}

#442 牛羊被他抢了 NOI模拟赛第六场T2

一棵$n$个点的树,每个点有$0/1/2$权值,有多少个联通块使得这其中$0,1,2$三类点分别不超过$a,b,c$个

$n\leq 200,a+b+c\leq n/2$

第二个代价根据均值不等式等价于$abc\leq (n/6)^3$

这是个好题,考察了$dfs$序的树形完全依赖背包。

如果我们直接背包,复杂度为$O(n(abc)^2)$,不可接受

我们考虑另辟蹊径,只对包含根的联通块计数,这可以使用基于$dfs$序的树形完全依赖背包,选了某个点必须选它的所有祖先,这样,每次只加入一个点,在$dfs$回溯时涉及背包合并,一次背包的复杂度是$O(nabc)$

套个点分治即可对所有联通块计数,复杂度$O(nabclogn)$

本题我尝试了封装式的代码,可能可以当半个板子

点击显示/隐藏内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//点分治+dfs序树形依赖背包 
//by hdmmblz(虽然我知道这个码风很不像,尝试一下封装型的代码)
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define MAXN 205
using namespace std;
typedef long long ll;
ll A,B,C,i,ans=0;
const ll mod=998244353;
struct Tree
{
int n,siz[MAXN],vis[MAXN],mp[MAXN];
vector<int>s[MAXN];
vector<vector<vector<vector<ll> > > >dp;
ll a[MAXN];
ll M(ll a){return a>=mod?a-mod:a;}
void input_n(){scanf("%d",&n);mp[0]=MAXN+10;}
void input_edge()
{
int i,x,y;
for(i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
s[x].push_back(y);
s[y].push_back(x);
}
}
void dp_pre(int p,int q,int r)
{
int i,j,k,hv;
dp.resize(n+1);
for(i=0;i<=n;i++)
{
dp[i].resize(p+1);
for(j=0;j<=p;j++)
{
dp[i][j].resize(q+1);
for(k=0;k<=q;k++)
{
dp[i][j][k].resize(r+1);
for(hv=0;hv<=r;hv++)dp[i][j][k][hv]=0;
}
}
}
}
void find_centre(int &rt,int p,int al,int f)
{
int i,v;
siz[p]=1;mp[p]=0;
for(i=0;i<s[p].size();i++)
{
v=s[p][i];
if(v==f)continue;
if(vis[v])continue;
find_centre(rt,v,al,p);
siz[p]+=siz[v];
mp[p]=max(mp[p],siz[v]);
}
mp[p]=max(mp[p],al-siz[p]);
if(mp[p]<mp[rt])rt=p;
}
void into(int p,int att,int yl)
{
int i,j,k;
for(i=A;i>=0;i--)
{
for(j=B;j>=0;j--)
{
for(k=C;k>=0;k--)
{
dp[p][i][j][k]=0;
if(att==0&&i-1>=0)dp[p][i][j][k]=dp[yl][i-1][j][k];
if(att==1&&j-1>=0)dp[p][i][j][k]=dp[yl][i][j-1][k];
if(att==2&&k-1>=0)dp[p][i][j][k]=dp[yl][i][j][k-1];
}
}
}
}
void merge(int p,int f)
{
int i,j,k;
for(i=0;i<=A;i++)for(j=0;j<=B;j++)for(k=0;k<=C;k++)dp[p][i][j][k]=M(dp[p][i][j][k]+dp[f][i][j][k]);
}
void dfs(int p,int f)
{
int i,j,k,v;
into(p,a[p],f);
//printf("%d %lld %lld %lld\n",p,dp[p][0][0][1],dp[p][0][1][0],dp[p][1][0][0]);
siz[p]=1;
for(i=0;i<s[p].size();i++)
{
v=s[p][i];
if(v==f||vis[v])continue;
dfs(v,p);
siz[p]+=siz[v];
}
merge(f,p);
}
void upd()
{
int i,j,k;
for(i=0;i<=A;i++)
{
for(j=0;j<=B;j++)
{
for(k=0;k<=C;k++)
{
if(i+j+k==0)continue;
ans=M(ans+dp[0][i][j][k]);
dp[0][i][j][k]=0;
}
}
}
}
void node_divide(int p,int sz)
{
int i,rt=0,v;
find_centre(rt,p,sz,0);
vis[rt]=1;
dp[0][0][0][0]=1;
dfs(rt,0);
upd();
//printf("%d %lld\n",rt,ans);
for(i=0;i<s[rt].size();i++)
{
v=s[rt][i];
if(vis[v])continue;
node_divide(v,siz[v]);
}
}
}T;
int main()
{
T.input_n();scanf("%d%d%d",&A,&B,&C);
for(i=1;i<=T.n;i++)scanf("%d",&T.a[i]);
T.input_edge();
T.dp_pre(A,B,C);
T.node_divide(1,T.n);
printf("%lld\n",ans);
return 0;
}

#443 老头子的话 NOI模拟赛第六场T3

$n$个小屁孩,每次随机给某个小孩一个糖,期望几次所有小孩都有至少$k$个糖

$n\leq50,k\leq1000$

来一波套上期望线性性的$min-max$容斥

设$p_x=E(min(T)),|T|=x$,这是因为所有大小相等的集合都是等价的

$E(max(S))=\sum_{i=1}^n(-1)^{i-1}C_n^ip_i$

设只在$x$个小孩中随机发糖,一旦有一个小孩拿到$k$个糖就停止,期望发糖$q_x$次,则$p_x=\frac{nq_x}{x}$

我们考虑求所有的$q_x$

首先钦定一个小孩拿了$k$个糖,且最后一步拿了第$k$颗,其他$k-1$颗可在步骤中随意分配,其他的$x-1$个小孩拿了少于$k$颗糖,设$q_{xy}$表示$x$个小孩发$y$次糖的概率,

首先钦定一个小孩拿了$k$个糖,且最后一步拿了第$k$颗,其他$k-1$颗可在步骤中随意分配,其他的$x-1$个小孩拿了少于$k$颗糖,设$q_{x,y}$表示$x$个小孩发$y$次糖的概率,一共$n$个小孩,背包容量$nk$,每种小孩的糖有$n$种可能,复杂度$O(n^2k^2)$,无法通过。

背包使用$NTT$优化,设$f_x$为$q_{xy}$的指数型生成函数,$A=\sum_{i=0}^{k-1}\frac{x^i}{i!}$,那么$f_{x+1}=Af_x$

需要做$n​$次长度递增的$NTT​$,复杂度$O(n^2klog(nk))​$

注意:当需要多次$NTT$时,一定要记得清空数组,而且要清空到$2(n+m)$项

点击显示/隐藏内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
ll ans=0,n,k,i,j,u,fac[100005],sum=0,invfac[100005],inv[100005],dp[100005],b[100005],pq;
const ll mod=998244353;
int pow(int x,int y,int p)
{
int ans=1;
while(y)
{
if(y&1)ans=1LL*ans*x%p;
x=1LL*x*x%p;
y>>=1;
}
return ans;
}
int M(int a,int b){return a>=b?a-b:a;}
void fft(ll *x,int type,int limit,int *r,int g,int p)
{
int wn,w,mid,u,v,rr,i,j,k;
for(i=1;i<limit;i++)if(r[i]>i)swap(x[r[i]],x[i]);
for(mid=1;mid<limit;mid<<=1)
{
wn=(type==1)?pow(g,(p-1)/2/mid,p):pow(g,p-1-(p-1)/2/mid,p);
for(j=0,rr=mid<<1;j<limit;j+=rr)
{
w=1;
for(k=0;k<mid;k++,w=1LL*w*wn%p)
{
u=x[j+k],v=1LL*w*x[j+mid+k]%p;
x[j+k]=M(u+v,p);
x[j+mid+k]=M(u-v+p,p);
}
}
}
if(type==-1)
{
int inv=pow(limit,p-2,p);
for(i=0;i<=limit;i++)x[i]=1LL*inv*x[i]%p;
}
}
void coef(ll *a,ll *b,int n,int m)
{
int i;
//for(i=0;i<=n;i++)printf("%lld ",a[i]);printf("\n");
//for(i=0;i<=m;i++)printf("%lld ",b[i]);printf("\n");
const int g=3,p=998244353;
int limit=1,l=0,r[100005],x,y;
while(limit<=n+m)limit<<=1,l++;
for(i=1;i<limit;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
fft(a,1,limit,r,g,p);
fft(b,1,limit,r,g,p);
for(i=0;i<limit;i++)a[i]=1LL*a[i]*b[i]%p;
fft(a,-1,limit,r,g,p);
}
ll mypow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
ll C(ll n,ll m){return fac[n]*invfac[m]%mod*invfac[n-m]%mod;}
ll neg(ll p){return p&1?mod-1LL:1LL;}
int main()
{
scanf("%lld%lld",&n,&k);
fac[0]=1;
for(i=1;i<=100000;i++)fac[i]=fac[i-1]*i%mod;
invfac[100000]=mypow(fac[100000],mod-2LL);
for(i=99999;i>=0;i--)invfac[i]=invfac[i+1]*(i+1)%mod;
for(i=1;i<=100000;i++)inv[i]=mypow(i,mod-2LL);
dp[k-1]=invfac[k-1];
//printf("%lld\n",fac[k-1]*invfac[k-1]%mod);
for(i=0;i<k;i++)b[i]=invfac[i];
for(i=1;i<=n;i++)
{
sum=0;
//printf("$$%lld %lld %lld %lld %lld\n",dp[2],dp[3],dp[4],inv[2],inv[4]);
for(j=k-1,pq=mypow(mypow(i,j),mod-2LL);j<=(k-1)*i;j++,pq=pq*inv[i]%mod)sum=(sum+(j+1)*dp[j]%mod*fac[j]%mod*pq)%mod;
//printf("%lld %lld %lld\n",sum,5LL*inv[2]%mod,ans);
sum=sum*n%mod*inv[i]%mod;
//printf("%lld %lld\n",sum,15LL*inv[4]%mod);
sum=sum*C(n,i)%mod*neg(i-1)%mod;
//printf("*%lld %lld\n",sum,26LL*inv[9]%mod);
ans=(ans+sum)%mod;
//printf("%lld %lld\n",(mod-45LL*inv[4]%mod+18LL)%mod,ans);
if(i==n)break;
for(j=0;j<k;j++)b[j]=invfac[j];
for(j=k;j<=2LL*i*(k-1);j++)b[j]=0;
//for(j=0;j<=15;j++)printf("**%lld %lld\n",dp[j],b[j]);
coef(dp,b,(int)i*(k-1),(int)(k-1));

}
printf("%lld\n",ans);
return 0;
}