NOIP2023前模拟组题

最提瓦特的一集,第二弹!

A. 魔环上的树(tree)

题目简介

原题链接:https://codeforces.com/problemset/problem/1172/B

给定一棵树,将其结点映射到一个有 个结点的环上,要求边两两不交,求出映射的方案总数(位置不同也算)。对 取模。

数据范围:

考虑到位置不同实际上就是转方向,所以我们固定 后对答案乘 就可以了。

然后我们考虑现在已经固定了根结点,但是要考虑子树的答案。那么我们设 表示只考虑 的子树内的答案的方案数。

如果 是叶结点的话,那么 显然为

然后考虑非叶结点。

因为题目中的对于边的限制,那么对于一棵子树,其在环中一定是一个区间,所以直接有转移:

而显然地,对于 的若干子树,其排列不同最终形成的方案也不同,所以最后需要乘上一个 ,这里的 指的是子结点个数。 是将本身计算入内。

有一个特判是在 时不需要这个 ,因为我们的根结点是强制固定了的。

时间复杂度

AC Code
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
// ----- Eternally question-----
// Problem: B. Nauuo and Circle
// Contest: Codeforces - Codeforces Round 564 (Div. 1)
// URL: https://codeforces.com/problemset/problem/1172/B
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Written by: Eternity
// Time: 2023-11-14 15:03:04
// ----- Endless solution-------

#include<bits/stdc++.h>
#define re register
typedef long long ll;
template<class T>
inline void read(T &x)
{
x=0;
char ch=getchar(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1){ read(x),read(x1...); }
template<class T>
inline void write(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
template<>
inline void write(bool x){ putchar(x?'1':'0'); }
template<>
inline void write(char c){ putchar(c); }
template<>
inline void write(char *s){ while(*s!='\0') putchar(*s++); }
template<>
inline void write(const char *s){ while(*s!='\0') putchar(*s++); }
template<class T,class ...T1>
inline void write(T x,T1 ...x1){ write(x),write(x1...); }
template<class T>
inline bool checkMax(T &x,T y){ return x<y?x=y,1:0; }
template<class T>
inline bool checkMin(T &x,T y){ return x>y?x=y,1:0; }
const int MAXN=2e5+10;
const int Mod=998244353;
template<class T1,class T2>
inline void mul(T1 &x,T2 y){ x=x*y%Mod; }
int N;
struct G
{ int next,to; }Edge[MAXN<<1];
int Head[MAXN],Total;
inline void addEdge(int u,int v)
{
Edge[++Total]=(G){Head[u],v};Head[u]=Total;
Edge[++Total]=(G){Head[v],u};Head[v]=Total;
}
ll Dp[MAXN],Mul[MAXN];
void dfsTree(int x,int last)
{
int son_cnt=0;Dp[x]=1;
for(int e=Head[x],v;e;e=Edge[e].next)
{
if((v=Edge[e].to)==last) continue;
dfsTree(v,x);++son_cnt;
mul(Dp[x],Dp[v]);
}
mul(Dp[x],Mul[son_cnt+(x!=1)]);
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(N);Mul[0]=1;
for(int i=1;i<=N;++i) Mul[i]=Mul[i-1]*i%Mod;
for(int i=2,u,v;i<=N;++i) read(u,v),addEdge(u,v);
dfsTree(1,0);mul(Dp[1],N);
write(Dp[1]);
return 0;
}
/*

*/

B. 序列舞蹈(dance)

题目简介

原题链接:https://codeforces.com/problemset/problem/1137/E

初始存在一个长度为 的序列,权值为 ,维护 次操作:

  • 在序列开头插入
  • 在序列末尾插入
  • 对序列中第 个位置加

每次操作之后求出最小值以及最小值第一次出现的位置。

数据范围:

原题很困难,但是这道题数据很水,暴力能过。

这个修改是加一个斜率为正的一次函数,所以实际上所有可能为答案的地方只有可能是操作中切分的区间中的第一个。所以可以直接维护这个,但是时间复杂度最劣情况下是 的,考场能过,原题过不了。

但是很多次操作之后这个图像十分熟悉,所以我们直接维护一个下凸包,元素值从前往后递减。然后动态维护凸包。

维护全局斜率加和截距加的标记 ,答案是凸包上所有点 ,对应 的最大值。

  • 对于操作 ,那么这个一定是答案,所以清空凸包保留新加入的
  • 对于操作 ,从后往前检查凸包合并;
  • 更新标记,并检查凸包合理性。

由于每一个点至多被加入一次,也至多被弹出一次,所以总时间复杂度

AC Code
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
// ----- Eternally question-----
// Problem: E. Train Car Selection
// Contest: Codeforces - Codeforces Round 545 (Div. 1)
// URL: https://codeforces.com/problemset/problem/1137/E
// Memory Limit: 512 MB
// Time Limit: 2000 ms
// Written by: Eternity
// Time: 2023-11-14 14:47:46
// ----- Endless solution-------

#include<bits/stdc++.h>
#define re register
typedef long long ll;
template<class T>
inline void read(T &x)
{
x=0;
char ch=getchar(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1){ read(x),read(x1...); }
template<class T>
inline void write(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
template<>
inline void write(bool x){ putchar(x?'1':'0'); }
template<>
inline void write(char c){ putchar(c); }
template<>
inline void write(char *s){ while(*s!='\0') putchar(*s++); }
template<>
inline void write(const char *s){ while(*s!='\0') putchar(*s++); }
template<class T,class ...T1>
inline void write(T x,T1 ...x1){ write(x),write(x1...); }
template<class T>
inline bool checkMax(T &x,T y){ return x<y?x=y,1:0; }
template<class T>
inline bool checkMin(T &x,T y){ return x>y?x=y,1:0; }
const int MAXN=3e5+10;
int Q,Top;
ll Len,Btag,Ktag;
struct Vector
{
ll k,b;
Vector(ll _k=0,ll _b=0):k(_k),b(_b){}
inline Vector operator-(Vector x){ return Vector(k-x.k,b-x.b); }
inline ll operator^(Vector x){ return k*x.b-b*x.k; }
inline ll get(){ return k*Ktag+b+Btag; }
}Stk[MAXN];
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(Len,Q);Stk[Top=1]=Vector(0,0);
for(int opt;Q--;)
{
read(opt);
if(opt==1)
{
int k;read(k);
Len+=k,Ktag=Btag=0,Stk[Top=1]=Vector(0,0);
}
else if(opt==2)
{
int k;read(k);Len+=k;
if(Stk[Top].get()!=0)
{
auto tmp=Vector(Len-k,-Ktag*(Len-k)-Btag);
while(Top>1&&((Stk[Top]-Stk[Top-1])^(tmp-Stk[Top-1]))<0) --Top;
Stk[++Top]=tmp;
}
}
else
{
ll b,k;read(b,k);Btag+=b,Ktag+=k;
while(Top>1&&Stk[Top].get()>=Stk[Top-1].get()) --Top;
}
write(Stk[Top].k+1,' ',Stk[Top].get(),'\n');
}
return 0;
}
/*

*/

C. 脱单计划(offsheet)

题目简介

类点 ,第 个点可以被匹配 次,同理有 类点 ,第 个点可以被匹配 次。

匹配可以产生 的贡献。求出最大贡献。

数据范围:

一个经典的匹配,所以是网络流模型。

但注意到流量是匹配次数,而贡献却是曼哈顿距离,所以想到费用流。

然后是一个更经典的 ,我们将绝对值的四种情况讨论出来,如果要求最大费用,一定会取到最大的那一个去,也正好是其绝对值。

所以建网络流:

  • 从源点 类点连边,流量为 ,无费用;
  • 类点向汇点 连边,流量为 ,无费用。
  • 类点向拆绝对值的店 连边,再向 类点连边,流量 ,费用为对应贡献。

然后跑最大费用最大流即可。

AC Code
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
#include<bits/stdc++.h>
#define re register
typedef long long ll;
typedef unsigned long long ull;
template<class T>
inline void read(T &x)
{
x=0;
char ch=getchar(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
x*=t?-1:1;
}
template<class T,class ...Arc>
inline void read(T &x,Arc &...arc){ read(x),read(arc...); }
template<class T>
inline void write(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+48);
}
inline void write(char c){ putchar(c); }
inline void write(bool x){ putchar(x?'1':'0'); }
inline void write(char *s){ while(*s!='\0') putchar(*s++); }
inline void write(const char *s){ while(*s!='\0') putchar(*s++); }
template<class T,class ...Arc>
inline void write(T x,Arc ...arc){ write(x),write(arc...); }
template<class T>
inline bool checkMax(T &x,T y){ return x<y?x=y,1:0; }
template<class T>
inline bool checkMin(T &x,T y){ return x>y?x=y,1:0; }
const int MAXN=1e3+10,MAXC=12,MAXV=3e3+10,MAXE=1e6+10;
const int Inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
int N,S,T;
struct Net
{
int next,to;
ll val,cost;
}Edge[MAXE<<1];
int Head[MAXV],Cur[MAXV],Total=1;
inline void addEdge(int u,int v,int w,int c)
{
Edge[++Total]=(Net){Head[u],v,w,c};Head[u]=Total;
Edge[++Total]=(Net){Head[v],u,0,-c};Head[v]=Total;
}
ll Dist[MAXV],ret;
bool Vis[MAXV];
inline bool Spfa()
{
std::memset(Dist,0x3f,sizeof(Dist)),
std::memset(Vis,0,sizeof(Vis));
std::memcpy(Cur,Head,sizeof(Head));
std::queue<int>Q;Q.push(S);
Dist[S]=0;
while(!Q.empty())
{
int u=Q.front();Q.pop();
Vis[u]=0;
for(int e=Head[u],v;e;e=Edge[e].next)
{
v=Edge[e].to;
if(Edge[e].val&&checkMin(Dist[v],Dist[u]+Edge[e].cost))
if(!Vis[v]) Q.push(v),Vis[v]=1;
}
}
return Dist[T]!=INF;
}
ll Dfs(int x,ll inf)
{
if(x==T) return inf;
ll flow=0;Vis[x]=1;
for(int e=Cur[x],v;e&&flow<inf;e=Edge[e].next)
{
v=Edge[e].to;Cur[x]=e;
if(!Vis[v]&&Edge[e].val&&Dist[v]==Dist[x]+Edge[e].cost)
{
ll k=Dfs(v,std::min(Edge[e].val,inf-flow));
if(k)
{
Edge[e].val-=k,Edge[e^1].val+=k,flow+=k;
ret+=k*Edge[e].cost;
}
}
}
Vis[x]=0;
return flow;
}
inline ll SSP()
{
ll r=0,flow;
while(Spfa()) while((flow=Dfs(S,INF))) r+=flow;
return r;
}
int main()
{
freopen("offsheet.in","r",stdin);
freopen("offsheet.out","w",stdout);
read(N);
S=0,T=N*3+4;
for(int i=1,x,y,c;i<=N;++i)
{
read(x,y,c);
addEdge(S,i,c,0);
addEdge(i,N*2+1,Inf,x+y),addEdge(i,N*2+2,Inf,x-y),
addEdge(i,N*2+3,Inf,-x+y),addEdge(i,N*2+4,Inf,-x-y);
}
for(int i=1,x,y,c;i<=N;++i)
{
read(x,y,c);
addEdge(i+N,T,c,0);
addEdge(N*2+1,i+N,Inf,-x-y),addEdge(N*2+2,i+N,Inf,-x+y);
addEdge(N*2+3,i+N,Inf,x-y),addEdge(N*2+4,i+N,Inf,x+y);
}
SSP();
write(-ret);
return 0;
}
/*

*/

D. 连通性(floyd)

题目简介

我们知道求连通性的 算法是下面这么写的:

示例代码

1
2
3
4
5
6
7
inline void Floyd(int n)
{
for(int k=1;k<=n;++k)
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
f[i][j]|=f[i][k]&f[k][j];
}

但是由于宇宙射线,这段代码变成了:

示例代码

1
2
3
4
5
6
7
inline void Floyd(int n)
{
for(int k=1;k<=n-m;++k)
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
f[i][j]|=f[i][k]&f[k][j];
}

给定 ,求出所有包含 个点的无向图中(一共 种情况),有多少种使得两份代码的结果一样。对 取模。多测。

数据范围:

很好的题,每一个部分分都值得研究(暴力除外),

时,两份代码没有异端,所以直接统计有 个结点的无向图个数,也就是

时,相当于所有的连通块都必须是团(即二连通,或者三两两连通),答案是贝尔数第 项,有递推公式:

正解是一个很牛逼的转换,但是感觉题解写的有些问题,没有 std 我也一直没过,所以直接贴官方题解。