2024/08/11
集训接近尾声了,室友今天回家了
拉格朗日插值
很神奇的插值法,NOIP2022 微信步数 考过
上过小学三年级的的同学都知道:给定
n
n
n 个点,可以确定一个经过这所有点的函数
f
f
f
拉格朗日给出的答案是:
f ( x ) = ∑ i = 1 n ( y i ∏ j ≠ i x − x j x i − x j ) f(x) = \sum_{i = 1}^n (y_i \prod_{j \not = i} \frac{x - x_j}{x_i - x_j}) f(x)=i=1∑n(yij=i∏xi−xjx−xj)
证明:我不会
我们 OIer 就只做感性理解了,对于任何一个 ( x , y ) (x,y) (x,y) 二元对
我们令 f i ( x i ) = y i ∏ j ≠ i x − x j x i − x j f_i(x_i) = y_i \prod_{j \not = i} \frac{x - x_j}{x_i - x_j} fi(xi)=yi∏j=ixi−xjx−xj
如果对于 第 k k k 个二元对 ( x k , y k ) (x_k,y_k) (xk,yk),我们会发现 除 f k ( x k ) = y k f_k(x_k) = y_k fk(xk)=yk 以外,其他的 f j ( x k ) = 0 ( j ≠ k ) f_j(x_k) = 0(j \not = k) fj(xk)=0(j=k),都被这个 ∏ j ≠ i x − x j x i − x j \prod_{j \not = i} \frac{x - x_j}{x_i - x_j} ∏j=ixi−xjx−xj 干成 0 0 0 了
请记住ta!
P8867 [NOIP2022] 建造军营
缩点 + tarjan + 树形dp
很典的题
图上做dp肯定不好
那么用tarjan缩点,对缩完点的树形结构dp是很自然的
为了不重复的记录答案,我们dp时,只针对子树内的情况做讨论
我们就强制不选择 f a x ⇝ x fa_x \leadsto x fax⇝x 的这条边,在做转移的时候特别考虑
我们由于只对子树内做分析,所以我们默认子树外不选择任何军营
用 f x , 0 / 1 f_{x,0/1} fx,0/1 来表示 在 x x x 子树内(包括 x x x)存在 (没有 / 有)军营的情况(方案数)
故有转移:
f
x
,
0
←
f
x
,
0
×
2
×
f
y
,
0
(
×
2
是因为
x
⇝
y
的这条边可选可不选,共两种情况)
f
x
,
1
←
f
x
,
1
×
2
×
f
y
,
0
(同理)
f
x
,
1
←
f
x
,
0
×
f
y
,
1
(这里
x
⇝
y
的这个边变成必选了)
f
x
,
1
←
f
x
,
1
×
f
y
,
1
\begin{align*} f_{x,0} &\leftarrow f_{x,0} \times 2 \times f_{y,0} &(\times 2\ 是因为\ x \leadsto y\ 的这条边可选可不选,共两种情况)\\ f_{x,1} &\leftarrow f_{x,1} \times 2 \times f_{y,0} &(同理) \\ f_{x,1} &\leftarrow f_{x,0} \times f_{y,1} &(这里\ x \leadsto y\ 的这个边变成必选了) \\ f_{x,1} &\leftarrow f_{x,1} \times f_{y,1} \\ \end{align*}
fx,0fx,1fx,1fx,1←fx,0×2×fy,0←fx,1×2×fy,0←fx,0×fy,1←fx,1×fy,1(×2 是因为 x⇝y 的这条边可选可不选,共两种情况)(同理)(这里 x⇝y 的这个边变成必选了)
我们规定外面不造军营,但是道路我们想守就守。
我们子树内的军营建造完了,同时子树内的军营方案和道路看守方案也都完备了,
那么外面的道路,每个都有 2 2 2 的贡献。
我们考虑记录 x x x 子树内的边数,用总边数减去,就是外面的边数
记录表达式:
EdgeCnt
x
=
Edge
x
+
∑
y
∈
son
x
(
EdgeCnt
y
+
1
)
(
此处的
1
是
x
⇝
y
这条边
)
\operatorname{EdgeCnt}_x = \operatorname{Edge}_x + \sum_{y \in \operatorname{son}_x}(\operatorname{EdgeCnt}_y + 1) (此处的\ 1\ 是\ x \leadsto\ y\ 这条边)
EdgeCntx=Edgex+y∈sonx∑(EdgeCnty+1)(此处的 1 是 x⇝ y 这条边)
在 dp 的时候与
f
x
,
1
f_{x,1}
fx,1 计算入答案:
Ans
←
f
x
,
1
×
2
EdgeCnt
1
−
Edge
x
−
1
\operatorname{Ans} \leftarrow f_{x,1} \times 2^{\operatorname{EdgeCnt}_1 - \operatorname{Edge}_x - 1}
Ans←fx,1×2EdgeCnt1−Edgex−1
但是当我们到达根节点的时候,我们居然将答案除了个 2 2 2
这明显不对,因为答案肯定是随子树的增大而递增的
我们发现我们没有要处理的子树外的边,(因为 root \operatorname{root} root 没有父亲)
所以针对
root
\operatorname{root}
root:
Ans
←
f
root
,
1
\operatorname{Ans} \leftarrow f_{\operatorname{root},1}
Ans←froot,1
最后再谈一谈初始化:
f
x
,
0
=
2
Edge
x
(
无军营的方案数
)
f
x
,
1
=
2
Edge
x
+
Point
x
−
f
x
,
0
(
有军营的方案数
)
\begin{align*} f_{x,0} &= 2^{\operatorname{Edge}_x} &(无军营的方案数) \\ f_{x,1} &= 2^{\operatorname{Edge}_x + \operatorname{Point}_x} - f_{x,0} &(有军营的方案数) \end{align*}
fx,0fx,1=2Edgex=2Edgex+Pointx−fx,0(无军营的方案数)(有军营的方案数)
Over!
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e6+5,M = 5e6+5,mod = 1e9 + 7;
int n,m;
struct Edge{
int head[N],nxt[M<<1],to[M<<1],cnt;
Edge() {memset(head,-1,sizeof(head));cnt = 0;}
void add(int u,int v) {
nxt[cnt] = head[u];
to[cnt] = v;
head[u] = cnt++;
}
};
struct newEdge{
int head[N],nxt[M<<1],to[M<<1],cnt;
newEdge() {memset(head,-1,sizeof(head));cnt = 0;}
void add(int u,int v) {
nxt[cnt] = head[u];
to[cnt] = v;
head[u] = cnt++;
}
};
Edge g;
newEdge G;
int U[N],V[N],dfn[N],low[N],num,co[N],col,P[N],st[N],top,E[N],_edge[M<<1];
bool vis[M << 1];
void tarjan(int x) {
low[x] = dfn[x] = ++num;
st[++top] = x;
for(int i = g.head[x];~i;i = g.nxt[i]) {
int y = g.to[i];
if(vis[_edge[i]]) continue;
vis[_edge[i]] = true;
if(!dfn[y]){
tarjan(y);
low[x] = min(low[y],low[x]);
} else if(!co[y]) low[x] = min(low[x],dfn[y]);
}
if(low[x] == dfn[x]) {
int v;
++col;
do{
v = st[top--];
co[v] = col;
P[col]++;
}while(v ^ x);
}
}
int qpow(int x,int k) {
int r = 1;
while(k) {
if(k & 1) r = (r * x) % mod;
x = x * x % mod;
k >>= 1;
}
return r;
}
signed s[N];
int ans,f[N][2];
void dfs(int x,int fa) {
s[x] = E[x];
for(int i = G.head[x];~i;i = G.nxt[i]){
int y = G.to[i];
if(y ^ fa) {
dfs(y,x);
s[x] += s[y] + 1;
}
}
}
void dp(int x,int fa) {
for(int i = G.head[x];~i;i = G.nxt[i]) {
int y = G.to[i];
if(y ^ fa) {
dp(y,x);
f[x][1] = (f[x][1] * ((2 * f[y][0] % mod + f[y][1]) % mod) % mod + f[x][0] * f[y][1] % mod) % mod;
f[x][0] = (f[x][0] * 2 % mod * f[y][0]) % mod;
}
}
if(x == 1) ans = (ans + f[x][1]) % mod;
else ans = (ans + f[x][1] * qpow(2,s[1] - s[x] - 1) % mod) % mod;
}
signed main() {
int k,d;
scanf("%lld %lld",&k,&d);
n = k,m = d;
cout<<n<<' '<<m<<'\n';
for(int i = 1;i<=m;i++) {
cin>>U[i]>>V[i];
g.add(U[i],V[i]);_edge[g.cnt - 1] = i;
g.add(V[i],U[i]);_edge[g.cnt - 1] = i;
}
tarjan(1);
for(int i = 1;i<=m;i++)
if(co[U[i]] ^ co[V[i]])
G.add(co[U[i]],co[V[i]]),G.add(co[V[i]],co[U[i]]);
else
E[co[U[i]]]++;
for(int i = 1;i<=col;i++) {
f[i][0] = qpow(2,E[i]) % mod;
f[i][1] = (qpow(2,E[i] + P[i]) - f[i][0] + mod) % mod;
}
dfs(1,0);
dp(1,0);
cout<<ans;
return 0;
}
2024/08/12
梦熊模拟赛
T1 chess
题目描述
有一个棋盘, 行 列,每个位置有一个小写的英文字母。
最开始的时候,他们把他们的游戏角色——猫猫棋子,放在左上角坐标 的位置。
在每一回合,它们都必须把棋子往下或往右移动一格。在游戏结束时,猫猫必须到达坐标为 的棋盘右下角区域
flame注意到,猫猫通过的路径上的小写字母可以组成一个字符串,他想考考mybing:在猫猫所有的移动方案中,构成的字典序最小的字符串是什么呢?
mybing很快就想出来了,现在她希望用这个问题考考你
输入格式
输入的第一行包含两个正整数 ,表示棋盘大小。接下来的 行每行一个长度为 的字符串,表示棋盘内容。
输出格式
输出可能的字典序最小的单词
样例
样例输入 #1
4 5
ponoc
ohoho
hlepo
mirko
样例输出 #1
pohlepko
样例输入 #2
4 5
bbbbb
bbbbb
bbabb
bbbbb
样例输出 #2
bbbbabbb
数据范围与提示
本题采用subtask测试,分为两个subtask
对于其中 的数据,保证每一个位置下边和右边的位置中字母都不同。
对于其中 的数据,保证
我一开始写 bfs,捣鼓了半天
最后还是选择了刷表
行动只有向右、向下两种,所以到达右下角的步数是有限的
那么我们对每一个步数相同的点扫描一遍,看一看能否被选中
走个斜线就可以了
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2005;
char Map[N][N];
bool vis[N][N],pass[N][N];
int dx[4] = {-1,0};
int dy[4] = {0,-1};
string ans;
int n,m;
#define check(x,y) (1 <= x && x <= n && 1 <= y && y <= m)
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
cin>>n>>m;
for(int i = 1;i<=n;i++){
cin>>Map[i];
for(int j = m;j >= 1;j--)
Map[i][j] = Map[i][j - 1];
Map[i][0] = ' ';
}
vis[1][1] = true;
ans.push_back(Map[1][1]);
for(int i = 2;i<=m;i++) {
array<int,2> a;
char minn = 'z';
a[0] = 1,a[1] = i;
while(check(a[0],a[1])) {
for(int j = 0;j<2;j++) {
int kx = dx[j] + a[0],ky = dy[j] + a[1];
if(check(kx,ky) && vis[kx][ky]) {
minn = min(minn,Map[a[0]][a[1]]);
break;
}
}
a[0] = a[0] + 1,a[1] = a[1] - 1;
}
a[0] = 1,a[1] = i;
while(check(a[0],a[1])) {
pass[a[0]][a[1]] = true;
if(Map[a[0]][a[1]] == minn && (vis[a[0] - 1][a[1]] || vis[a[0]][a[1] - 1]))
vis[a[0]][a[1]] = true;
a[0] = a[0] + 1,a[1] = a[1] - 1;
}
ans.push_back(minn);
}
for(int i = 1;i<=m;i++) {
array<int,2> a;
a[0] = n,a[1] = i;
if(pass[a[0]][a[1]]) continue;
char minn = 'z';
while(check(a[0],a[1])) {
for(int j = 0;j<2;j++) {
int kx = a[0] + dx[j];
int ky = a[1] + dy[j];
if(check(kx,ky) && vis[kx][ky]) {
minn = min(minn,Map[a[0]][a[1]]);
break;
}
}
a[0] = a[0] - 1,a[1] = a[1] + 1;
}
a[0] = n,a[1] = i;
while(check(a[0],a[1])) {
pass[a[0]][a[1]] = true;
if(Map[a[0]][a[1]] == minn&& (vis[a[0] - 1][a[1]] || vis[a[0]][a[1] - 1]))
vis[a[0]][a[1]] = true;
a[0] = a[0] - 1,a[1] = a[1] + 1;
}
ans.push_back(minn);
}
cout<<ans;
return 0;
}