BZOJ Nov. Monthly

A 组题

给定一个长度为 $n$ 的有正有负的数字序列,求长度 $\geq k$ 的子串平均值的最大值,输出最简分数。

$n\leq10^5$

签到题,NOIP 老套路。

二分答案,对于一个平均值通过前缀和判断是否存在符合条件的子串,并且记录分子分母。

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
#include<iostream>
#include<cstdio>
typedef long long ll;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}

const int MAXN=1e5+5;
const double eps=1e-6,INF=1e13;

int N,K;
int op,ed;

int na[MAXN];
double sum[MAXN];
inline bool chk(double x)
{
double mnv=INF;int i,mnp=0;sum[0]=0;
for(i=1;i<=N;i++) sum[i]=sum[i-1]+(double)na[i]-x;
for(i=K;i<=N;i++)
{
if(sum[i-K]<mnv) mnv=sum[i-K],mnp=i-K;
if(sum[i]-mnv>=0) {op=mnp+1,ed=i;return true;}
}
return false;
}

int main()
{
int i;
scanf("%d%d",&N,&K);
for(i=1;i<=N;i++) scanf("%d",na+i);
double l=-INF,r=INF;
while(r-l>eps)
{
double mid=(l+r)/2;
if(chk(mid)) l=mid;
else r=mid;
}
ll p=0,q=ed-op+1;
for(i=op;i<=ed;i++) p+=na[i];
int g=gcd(p,q);p/=g,q/=g;
if(q<0) p*=-1,q*=-1;
printf("%lld/%lld",p,q);
return 0;
}

##B 摘苹果

给订一张 $n$ 个点 $m$ 条边的无向图和一个正整数数列 $a$,设点 $i$ 的度数为 $d_i$,初始时随机移动到一个点上,移动到第 $i$ 个点的概率为 $$\frac{d_i}{2m}$$ . 之后还要移动 $k$ 次,每次随机选择一条与当前点连接的一条道路,移动到另一点 $i$ 上,得到 $a_i$ 的权值(多次移动到同一点上可重复得到权值)。求得到权值的期望。

$n\leq10^5,m\leq2\times10^5$

考虑第一次移动(不是初始时)到某一点的概率,某一点 $j$ 可以由所有与其联通的所有点 $i$ 到达,而初始时在点 $i$ 的概率为 $$\frac{d_i}{2m}$$ ,而本次从 $i$ 移动到 $j$ 的概率为 $\frac{1}{2m}$ ,而对于点 $j$ 来说,所有与其联通的 $d_j$ 个点都有 $\frac{1}{2m}$ 的概率到达。综上所述,第一次移动到点 $j$ 的概率为 $$\frac{d_j}{2m}$$ 。

我们发现其实每次移动在某一点上的概率并没有变化,那么答案就是 $$k\sum_{i=1}^na_i\frac{d_i}{2m}$$

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
#include<iostream>
#include<cstdio>
typedef long long ll;

const int MAXN=1e5+5,MAXM=2e5+5,P=1e9+7;

int qpow(int a,int x)
{
int res=1;
for(;x;x>>=1,a=(ll)a*a%P)
if(x&1) res=(ll)res*a%P;
return res;
}

int n,m,k;

int a[MAXN],d[MAXN];
int main()
{
int i;scanf("%d%d%d",&n,&m,&k);
for(i=1;i<=n;i++) scanf("%d",a+i);
for(i=1;i<=m;i++)
{
int u,v;scanf("%d%d",&u,&v);
d[u]++,d[v]++;
}
int ans=0;
for(i=1;i<=n;i++) ans=(ans+(ll)a[i]*d[i]%P)%P;
ans=(ll)ans*qpow(2*m,P-2)%P*k%P;
printf("%d",ans);
return 0;
}

C 分割序列

对于一长度为 $L$ 的非负整数序列 $(a_1,a_2,\dots,a_L)$ :设 $S[l,r]$ 为 $a_l\oplus a_{l+1}\oplus\dots\oplus a_r$,定义序列的能量为 $\max\limits_{i=0}^nS[1,i]+S[i+1,L]$ .

现在给定一长度为 $n$ 的非负整数序列 $(a_1,a_2,\dots,a_n)$ ,求其每个前缀的能量。

$n\leq2\times10^5,a_i\leq10^6$

这种异或的题目一般都是按位分别处理。

先将能量值的定义转化一下:设 $s_i$ 为前缀异或和,则所谓的能量值为 $\max\limits_{i=0}^ns_i+s_i\oplus s_L$

注意到这样一个规律,对于第 $k$ 位:

  • 如果 $\Large s_{L_k}$$=0$ , 那么当且仅当 $\Large s_{i_k}$$=1$ 时答案最大;
  • 如果 $\Large s_{L_k}$$=0$ , 那么 $\Large s_{i_k}$ 的值对答案没有影响。

所以我们求 $a$ 的每一个前缀能量时可以从高位到低位动态规划,设 $f_S$ 为满足 $S\in s_i$ 的 $i$ 的最小值,那么对一个前缀 $x$ 的某一位决策时只要满足 $f_S\leq x$ 就可以取这一位,然后就得到了一个对于这一前缀最优的 $s_i$ ,那么这一前缀的能量就得到了。

现在问题变成了如何求 $f$ ,其实只要对于原序列进行预处理即可。

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
#include<iostream>
#include<cstdio>
#include<cstring>
using std::min;
inline void umn(int &a,int b){a=min(a,b);}
const int MAXN=3e5+5;

int n;
int f[1<<21],s[MAXN];

int main()
{
int i,k;scanf("%d",&n);
memset(f,0x3f,sizeof f);f[0]=0;
for(i=1;i<=n;i++) scanf("%d",s+i);
for(i=1;i<=n;i++) s[i]^=s[i-1];
for(i=1;i<=n;i++) umn(f[s[i]],i);
for(k=0;k<=20;k++)
for(i=1;i<1<<21;i++) if(!((i>>k)&1))
umn(f[i],f[i|(1<<k)]);
for(i=1;i<=n;i++)
{
int si=s[i],sj=0;
for(k=20;~k;k--)
if( !( (si>>k)&1 ) && f[sj|(1<<k)]<=i) sj|=1<<k;
printf("%d\n",sj+(sj^si));
}
return 0;
}

##D 图的价值

给定 $n,k$ , 求所有 $n$ 个点的带标号的简单无向图的价值之和。答案 $998244353$ 取模。

$n\leq10^9,k\leq2\times10^5$

显然每个点对答案的贡献相同,可以计算出 $1​$ 号点的贡献然后 $\times n​$ . 枚举 $1​$ 号点的度数 $d​$ , 则要从 $n-1​$ 个点中选 $d​$ 个点与它连边,并且剩下 $n-1​$ 个点可以随意连边。

答案就是
$$
n2^{(n-1)(n-2)/2}\sum_{i=0}^{n-1}\binom{n-1}{i}i^k
$$
式子不难列出,问题就在于这种恐怖的数据范围下解决幂指数求和问题,一般的求法肯定不行了,我们需要继续往下推公式。

注意到这个式子与一般的幂指数求和不同,系数上有了组合数,那通常的 Bernoulli 数 求和公式就不在适用了。

不过还是有一种类似的解法,提起系数上有组合数的自然数幂指数求和,想到还有个 第二类斯特林数 。关于这个数列详见 《组合数学及其应用》读书笔记 。 $S_2$ 的定义式为 $$x^k=\sum_{i=1}^ki!\binom{x}{i}S_2(k,i)$$

那么可以用这个公式化简答案:$$n2^{(n-1)(n-2)/2}\sum_{i=0}^{n-1}\binom{n-1}{i}\sum_{j=1}^kj!\binom{i}{j}S_2(k,j)$$

交换求和顺序得: $$n2^{(n-1)(n-2)/2}\sum_{j=1}^kj!S_2(k,j)\sum_{i=0}^{n-1}\binom{n-1}{i}\binom{i}{j}$$

发现最右边的两个组合数和式变成了喜闻乐见的形式,考虑其组合意义:先选择一个集合 $A$ 的子集 $B$ ,在从 集合 $B$ 中选一个子集 $C$ 的方案数;其实也就等于:从集合 $A$ 中选出集合 $C$ ,$\complement_AC$ 中的元素自由选择是否在集合 $B$ 中的方案数。转化为公式就是:$$\sum_{i=0}^{n-1}\binom{n-1}{i}\binom{i}{j}=2^{n-1-j}\binom{n-1}{j}$$

那么原式就可以化简为:
$$
n2^{(n-1)(n-2)/2}\sum_{j=1}^kj!2^{n-1-j}\binom{n-1}{j}S_2(k,j)
$$
然后各种预处理,其中 $S_2$ 可以通过卷积形式用 NTT 求出,就可以做到 $O(k\log_2k)$ 的复杂度了。

附:$$S_2(x,y)=\sum_{i=0}^b\frac{(-1)^i}{i!}\cdot\frac{(b-i)^a}{(b-i)!}$$

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
#include<iostream>
#include<cstdio>
typedef long long ll;
const int P=998244353;
int qpow(int a,ll x)
{
int res=1;
for(;x;x>>=1,a=(ll)a*a%P)
if(x&1) res=(ll)res*a%P;
return res;
}
int exgcd(int a,int b,int &x,int &y)
{
if(!b) {x=1,y=0;return a;}
int res=exgcd(b,a%b,y,x);y-=a/b*x;return res;
}
int cinv(int a)
{
int x,y;
exgcd(a,P,x,y);
return (x+P)%P;
}

class NTT
{
private:
static const int MAXN=1e6+5,g=3;
int pos[MAXN],n,m;
int a[MAXN],b[MAXN];
void trans(int *A,int ty)
{
int i,j,k;
for(i=1;i<n;i++) if(i<pos[i])
std::swap(A[i],A[pos[i]]);
for(i=1;i<n;i<<=1)
{
int wn=qpow(g,(P-1)/(i<<1));
if(ty) wn=cinv(wn);
for(j=0;j<n;j+=i<<1)
{
int w=1;
for(k=0;k<i;k++,w=(ll)w*wn%P)
{
int x=A[j+k],y=(ll)A[i+j+k]*w%P;
A[j+k]=(x+y)%P,A[i+j+k]=(x+P-y)%P;
}
}
}
}
public:
void mul(int *A,int AI,int *B,int BI,int *ans)
{
int i;
for(i=0;i<=AI;i++) a[i]=A[i];
for(i=0;i<=BI;i++) b[i]=B[i];
m=AI+BI;int bL=0;for(n=1;n<=m;n<<=1) ++bL;
for(i=1;i<n;i++) pos[i]=(pos[i>>1]>>1)|((i&1)<<(bL-1));

trans(a,0);trans(b,0);
for(i=0;i<n;i++) a[i]=(ll)a[i]*b[i]%P;
trans(a,1);

int nInv=cinv(n);
for(i=0;i<=m;i++) ans[i]=(ll)a[i]*nInv%P;
}
} ntt;

const int MAXK=2e5+5;

int fac[MAXK],inv[MAXK],facInv[MAXK];
int f[MAXK],g[MAXK],S2[MAXK],C[MAXK];
void preTab(int n,int k)
{
int i;
for(fac[0]=i=1;i<=k;i++) fac[i]=(ll)fac[i-1]*i%P;
for(inv[1]=1,i=2;i<=k;i++) inv[i]=(ll)(P-P/i)*inv[P%i]%P;
for(facInv[0]=i=1;i<=k;i++) facInv[i]=(ll)facInv[i-1]*inv[i]%P;
for(i=0;i<=k;i++) {f[i]=facInv[i];if(i&1) f[i]=(P-f[i])%P;}
for(i=0;i<=k;i++) g[i]=(ll)qpow(i,k)*facInv[i]%P;

ntt.mul(f,k,g,k,S2);

for(C[0]=i=1;i<=k;i++) C[i]=(ll)C[i-1]*(n-i)%P*inv[i]%P;
}

int solve(int n,int k)
{
preTab(n,k);
int ans=(ll)n*qpow(2,(ll)(n-1)*(n-2)>>1)%P;
int sum=0;
for(int i=1;i<=k;i++)
{
int res=(ll)S2[i]*fac[i]%P*C[i]%P*qpow(2,n-1-i)%P;
sum=(sum+res)%P;
}
ans=(ll)ans*sum%P;
return ans;
}

int main()
{
int n,k;scanf("%d%d",&n,&k);
printf("%d",solve(n,k));
return 0;
}

E 硬盘检测

有 $n$ 个数字,给出从中选择 $10^4$ 个数的情况,估计 $n$ 的数量级。

可以根据输入的数字中本质不同的数判断,由于给出数的个数是固定的,可以直接在本地随机模拟。

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
#include<iostream>
#include<cstdio>
#include<set>
typedef unsigned int uint;

const int MAXNODE=1e4+5,SZ=32,PR[10]={2,3,5,7,11,13,17,19,23,29};
class HT
{
struct HTN
{
uint key;
bool occ;
int c[SZ];
} t[MAXNODE];int tcnt;
bool find(int o,int lv,uint key)
{
if(t[o].occ) if(t[o].key==key) return true;
int pos=key%PR[lv];
if(!t[o].c[pos]) return false;
return find(t[o].c[pos],lv+1,key);
}
void ist(int o,int lv,uint key)
{
if(!t[o].occ) {t[o].key=key,t[o].occ=true;return;}
int pos=key%PR[lv];
if(!t[o].c[pos]) t[o].c[pos]=++tcnt;
ist(t[o].c[pos],lv+1,key);
}
public:
bool find(uint key){return find(1,0,key);}
void ist(uint key){ist(1,0,key);}
HT(){tcnt=1;t[1].occ=true,t[1].key=-1;};
} ht;

int m;

int main()
{
int i;
scanf("%d",&m);
int cnt=0;
for(i=1;i<=m;i++)
{
uint a;scanf("%u",&a);
if(!ht.find(a)) cnt++,ht.ist(a);
}
int ans;
if(cnt<=10) ans=10;
else if(cnt<=100) ans=100;
else if(cnt<=1000) ans=1000;
else if(cnt<=9000) ans=1e4;
else if(cnt<=9900) ans=1e5;
else if(cnt<=9990) ans=1e6;
else ans=1e7;
printf("%d",ans);
return 0;
}

##F 座位安排

有 $n$ 个人 $m$ 个座位,现在要为这 $n$ 个人分排座位,要求满足:

  • 两个人不能相邻;
  • 每个人不能坐在他不想坐的座位上;
  • 两个人不能坐在正对面的位置上(当然 $m$ 为奇数时就不存在正对面的位置)

$n\leq10^9,k\leq2\times10^5$

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
 #include<iostream>
#include<cstdio>
#define bmv(x) (1<<(x-1))
const int MAXN=11,MAXM=1005,P=998244353;

inline void add(int &a,int b){a=(a+b%P)%P;}

int n,m;bool modd;

int f[MAXM][bmv(MAXN)][3][3],g[MAXN][MAXM];

char s[MAXM];
int main()
{
int i,j,a,b;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
scanf("%s",s+1);
for(j=1;j<=m;j++) if(s[j]=='1') g[i][j]=1;
}
if(m&1) modd=true;
else
{
m>>=1;
for(i=1;i<=n;i++) for(j=1;j<=m;j++)
g[i][j]|=g[i][j+m]<<1;
}

for(i=1;i<=n;i++)
{
if(g[i][1]&1) f[1][bmv(i)][0][0]=1;
if(g[i][1]&2) f[1][bmv(i)][1][1]=1;
}
f[1][0][2][2]=1;
for(i=2;i<=m;i++) for(int S=0;S<1<<n;S++)
{
int (&la)[3][3]=f[i-1][S];
for(j=1;j<=n;j++) if(!(S&bmv(j)))
{
int (&o)[3][3]=f[i][S|bmv(j)];
if(g[j][i]&1) for(a=0;a<=2;a++)
add(o[a][0],la[a][1]+la[a][2]);
if(g[j][i]&2) for(a=0;a<=2;a++)
add(o[a][1],la[a][0]+la[a][2]);
}
int (&o)[3][3]=f[i][S];
for(a=0;a<=2;a++) for(b=0;b<=2;b++)
add(o[a][2],la[a][b]);
}
int (&o)[3][3]=f[m][(1<<n)-1],ans=0;
add(ans,o[0][modd]+o[0][2]);add(ans,o[1][modd^1]+o[1][2]);
for(b=0;b<=2;b++) add(ans,o[2][b]);
printf("%d\n",ans);
return 0;
}

G 删数游戏

给定两个长度为 $n$ 的数列,保证没有重复元素,在不改变各数列内顺序得情况下组合出一个新的数列,使得其最长上升长度最大。现在可以删除一些数,给出删除每个数的代价。问删除一些数使得最优情况下新序列的最长上升子序列长度变小的最小代价。

$n\leq100$

记 $\mathit{LIS}(a)$ 为 ${a}$ 的最长上升子序列长度,不难发现:对于两个数列 ${a},{b}$ 、最优情况下组合出的新数列 ${c}$ 满足 $\mathit{LIS}(c)=\mathit{LIS}(a)+\mathit{LIS}(b)$。

那么问题就变成了:对于使任意一个序列的最长上升子序列长度变小的代价。

其实这个问题很容易转化为点权的最小割用最大流算法解决。

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
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using std::min;
inline void umn(int &a,int b){a=min(a,b);}
using std::max;
inline void umx(int &a,int b){a=max(a,b);}

const int MAXN=155,INF=1061109567;

int n,m;
int a[MAXN],wa[MAXN],b[MAXN],wb[MAXN];

const int MAXV=MAXN<<1,MAXE=MAXN*MAXN<<1;
int S,T;

struct E{int next,to,cap;} e[MAXE<<1];int ecnt,G[MAXV];
void addEdge(int u,int v,int c)
{
e[++ecnt]=(E){G[u],v,c};G[u]=ecnt;
e[++ecnt]=(E){G[v],u,0};G[v]=ecnt;
}

int dfn[MAXV];
std::queue<int> que;
bool calDfn(int u)
{
memset(dfn,-1,sizeof dfn);
dfn[S]=0;que.push(S);
while(!que.empty())
{
int u=que.front();que.pop();
for(int i=G[u];i;i=e[i].next) if(e[i].cap>0)
{
int v=e[i].to;
if(~dfn[v]) continue;
dfn[v]=dfn[u]+1;
que.push(v);
}
}
return ~dfn[T];
}

int iter[MAXV];
int calF(int u,int f)
{
if(u==T) return f;
for(int &i=iter[u];i;i=e[i].next) if(e[i].cap>0)
{
int v=e[i].to;
if(dfn[v]!=dfn[u]+1) continue;
int res=calF(v,min(f,e[i].cap));
if(res>0)
{
e[i].cap-=res,e[i^1].cap+=res;
return res;
}
}
return 0;
}

int maxFlow()
{
int f,res=0;
while(calDfn(S))
{
memcpy(iter,G,sizeof G);
while(f=calF(S,INF)) res+=f;
}
return res;
}

int f[MAXN],seq[MAXN];
inline int calc(int n,int *na,int *w)
{
int i,j;
ecnt=1;memset(G,0,sizeof G);
memset(seq,0x3f,sizeof seq);
for(i=1;i<=n;i++)
{
int p=std::lower_bound(seq+1,seq+n+1,na[i])-seq;
f[i]=p;
seq[p]=na[i];
}
int LIS=0;for(i=1;i<=n;i++) umx(LIS,f[i]);

S=(n+1)<<1,T=(n+1)<<1|1;
for(i=1;i<=n;i++)
{
addEdge(i<<1,i<<1|1,w[i]);
if(f[i]==1) addEdge(S,i<<1,INF);
if(f[i]==LIS) addEdge(i<<1|1,T,INF);
}
for(i=1;i<n;i++)
for(j=i+1;j<=n;j++)
if(na[i]<na[j]&&f[i]+1==f[j])
addEdge(i<<1|1,j<<1,INF);
return maxFlow();
}

int main()
{
int i;
scanf("%d",&n);
for(i=1;i<=n;i++) scanf("%d",a+i);
for(i=1;i<=n;i++) scanf("%d",wa+i);
scanf("%d",&m);
for(i=1;i<=m;i++) scanf("%d",b+i);
for(i=1;i<=m;i++) scanf("%d",wb+i);
printf("%d",min(calc(n,a,wa),calc(m,b,wb)));
return 0;
}