2024.8 - 做题记录与方法总结 - Part 4

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=1n(yij=ixixjxxj)

证明:我不会

我们 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)=yij=ixixjxxj

如果对于 第 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=ixixjxxj 干成 0 0 0

请记住ta!

P8867 [NOIP2022] 建造军营

缩点 + tarjan + 树形dp

很典的题

图上做dp肯定不好

那么用tarjan缩点,对缩完点的树形结构dp是很自然的

为了不重复的记录答案,我们dp时,只针对子树内的情况做讨论

我们就强制不选择 f a x ⇝ x fa_x \leadsto x faxx 的这条边,在做转移的时候特别考虑

我们由于只对子树内做分析,所以我们默认子树外不选择任何军营

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,1fx,0×2×fy,0fx,1×2×fy,0fx,0×fy,1fx,1×fy,1×2 是因为 xy 的这条边可选可不选,共两种情况)(同理)(这里 xy 的这个边变成必选了)

我们规定外面不造军营,但是道路我们想守就守。

我们子树内的军营建造完了,同时子树内的军营方案和道路看守方案也都完备了,

那么外面的道路,每个都有 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+ysonx(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} Ansfx,1×2EdgeCnt1Edgex1

但是当我们到达根节点的时候,我们居然将答案除了个 2 2 2

这明显不对,因为答案肯定是随子树的增大而递增的

我们发现我们没有要处理的子树外的边,(因为 root ⁡ \operatorname{root} root 没有父亲)

所以针对 root ⁡ \operatorname{root} root
Ans ⁡ ← f root ⁡ , 1 \operatorname{Ans} \leftarrow f_{\operatorname{root},1} Ansfroot,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+Pointxfx,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
题目描述

有一个棋盘, n m 列,每个位置有一个小写的英文字母。

最开始的时候,他们把他们的游戏角色——猫猫棋子,放在左上角坐标 (1,1) 的位置。

在每一回合,它们都必须把棋子往下或往右移动一格。在游戏结束时,猫猫必须到达坐标为 (N,M) 的棋盘右下角区域

flame注意到,猫猫通过的路径上的小写字母可以组成一个字符串,他想考考mybing:在猫猫所有的移动方案中,构成的字典序最小的字符串是什么呢?

mybing很快就想出来了,现在她希望用这个问题考考你

输入格式

输入的第一行包含两个正整数 N,M(1≤N,M≤2000) ,表示棋盘大小。接下来的 N 行每行一个长度为 M 的字符串,表示棋盘内容。

输出格式

输出可能的字典序最小的单词

样例
样例输入 #1
4 5
ponoc
ohoho
hlepo
mirko
样例输出 #1
pohlepko
样例输入 #2
4 5
bbbbb
bbbbb
bbabb
bbbbb
样例输出 #2
bbbbabbb
数据范围与提示

本题采用subtask测试,分为两个subtask

对于其中 40\% 的数据,保证每一个位置下边和右边的位置中字母都不同。

对于其中 100\% 的数据,保证 N,M(1≤N,M≤2000)

我一开始写 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;
}
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值