半AFO的笔记

笔记

LateX学习站

前缀和&差分

\[sum[i]=sum[i-1]+a[i] \]

\(sum[i]\) 表示到第 \(i\) 个位置的和。

\[sum[r]-sum[l-1]=\sum\limits_{i=l}^{r}a[i] \]

我们同样可以这样计算区间和。

\[cf[i]=cf[i-1]+(a[i]-a[i-1]) \]

\(cf[i]\) 表示到第 \(i\) 个位置和第 \(i-1\) 个位置的差。

同时差分通过修改端点来修改区间信息

给第 \(x-y\) 位置加 \(z\)

\[cf[x]=cf[x]+z,cf[y+1]=cf[y+1]-z \]

因为差分和前缀和是对称的操作,则有

\[\sum\limits_{i=1}^{k} cf[i]=a[k] \]

在实际的运用场景里,一维差分和一维前缀和往往是不够用的,于是我们有二维差分

给以 \((x_1,y_1)\)\((x_2,y_2)\) 的矩阵加值 \(z\)

cf[x_1][y_1]+=z;
cf[x_1][y_2+1]-=z;
cf[x_2+1][y_1]-=z;
cf[x_2+1][y_2+1]+=z

同样有二维前缀和

\[sum[i][j]=sum[i-1][j]+sum[i][j-1]+v[i][j]-sum[i-1][j-1] \]

自然也有

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}cf[i][j]=v[n][m] \]

模拟

模拟这个算法是个难度跨度非常大且极其考验思维细节的算法,我这里只会给出一些认为不错的习题。

例题1

#include<bits/stdc++.h>
#include<bits/extc++.h>
#define int long long
using namespace std;

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int T, M;
	cin >> T >> M;
	while (T--) {
		int a, b, c;
		cin >> a >> b >> c;
		int pbs = b*b - 4 * a*c;
		if (pbs < 0) {
			cout << "NO\n";
		} else {
			bool flag = 0;
			int tmp = 1;
			for (int i = 2; i <= sqrt(pbs); i++)
				while (pbs % (i * i) == 0) pbs /= i * i, tmp *= i;
			int fm = 2 * a, fz = -b;
			if (pbs == 1)
				fz += fm > 0 ? tmp : -tmp;
			if (fm < 0) fz = -fz, fm = -fm;
			int gcd1 = __gcd(abs(fz), abs(fm)), gcd2 = __gcd(abs(tmp), abs(fm));
			if (fz) flag = 1, cout << fz / gcd1;
			if (abs(fm / gcd1) != 1) cout << '/' << fm / gcd1;
			if (fz && pbs && pbs != 1) cout << '+';
			if (pbs && pbs != 1 && tmp / gcd2 != 1) cout << tmp / gcd2 << '*';
			if (pbs && pbs != 1) {
				flag = 1;
				cout << "sqrt(" << pbs << ')';
				if (abs(fm / gcd2) != 1) cout << '/' << fm / gcd2;
			}
			if (!flag) cout << 0;
			cout << '\n';
		}
	}
	return 0;
}

图论

这个是比较纠结的,毕竟可能太多,我也不会全讲。

记住,相当一部分问题可以在思维转换后变成图论题,这是个很重要的思维认知。

所以这里的图论,严格来说应该叫做图论建模。

拓扑排序

拓扑排序适用的图论模型是 DAG ,记住,我说的是图论模型,当出现先后顺序的限制时,就可以建立 DAG 模型。

代码

#include<bits/stdc++.h>
#include <climits>
using namespace std;
using ll=long long;
namespace IO {
	char buf[1<<21], *p1 = buf, *p2 = buf, buf1[1<<21];
	inline char gc() {
		if(p1 != p2) return *p1++;
		p1 = buf;
		p2 = p1 + fread(buf, 1, 1 << 21, stdin);
		return p1 == p2 ? EOF : *p1++;
	}
#define G gc

#ifndef ONLINE_JUDGE
#undef G
#define G getchar
#endif

	template<class I>
	inline void read(I &x) {
		x = 0;
		I f = 1;
		char c = G();
		while(c < '0' || c > '9') {
			if(c == '-') f = -1;
			c = G();
		}
		while(c >= '0' && c <= '9') {
			x = x * 10 + c - '0';
			c = G();
		}
		x *= f;
	}

	template<class I>
	inline void write(I x) {
		if(x == 0) {
			putchar('0');
			return;
		}
		I tmp = x > 0 ? x : -x;
		if(x < 0) putchar('-');
		int cnt = 0;
		while(tmp > 0) {
			buf1[cnt++] = tmp % 10 + '0';
			tmp /= 10;
		}
		while(cnt > 0) putchar(buf1[--cnt]);
	}
}
void mc() {
	freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
}
using namespace IO;
#define int long long
vector<int> edge[101000],val[101000];
int dis[101000],in[101000];
queue<int> q;
signed main() {
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		read(u),read(v),read(w);
		edge[u].push_back(v);
		val[u].push_back(w);
		in[v]++;//入度
	}
	for(int i=2;i<=n;i++)
	{
		dis[i]=-1e9;//默认每个到每个点的距离为INF
		if(!in[i]) q.push(i);
	}
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=0;i<edge[x].size();i++)
			if(!--in[edge[x][i]]) q.push(edge[x][i]);
	}
	q.push(1);
	while(q.size())
	{
		int x=q.front();
		q.pop();
		for(int i=0;i<edge[x].size();i++)
		{
			if(dis[edge[x][i]]<dis[x]+val[x][i]) dis[edge[x][i]]=dis[x]+val[x][i];
			if(!--in[edge[x][i]]) q.push(edge[x][i]);
		}
	}
	if(dis[n]==-1e9) cout<<-1;
	else cout<<dis[n];
	return 0;
}

模板题

进阶题

欧拉路径

没啥坑点,相对来说就是要去理解为什么要退栈的时候存点就没了。

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
const int N=1e5+10;
vector<int> E[N];
int n,m;
int rd[N],cd[N],del[N];
stack<int> stk;
void euler(int u)
{
    for(int i=del[u];i<E[u].size();i=del[u])
    {
        del[u]++;
        euler(E[u][i]);
    }
    stk.push(u);
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int u,v;
        cin>>u>>v;
        E[u].push_back(v);
        rd[v]++;
        cd[u]++;
    }
    for(int i=1;i<=n;i++) sort(E[i].begin(),E[i].end());
    int st=1,ed;
    bool flag=0;
    int cnt1=0,cnt2=0;
    for(int i=1;i<=n;i++)
    {
        if(rd[i]!=cd[i]){
            if(rd[i]-cd[i]==1)
            {
                ed=i;
                cnt2++;
            }
            else if(cd[i]-rd[i]==1)
            {
                st=i;
                cnt1++;
            }
            else flag=1;
        }
    }
    if(flag) 
    {
        cout<<"No";
        return 0;
    }
    euler(st);
    while(stk.size()) cout<<stk.top()<<' ',stk.pop();
    return 0;
}

例题

并查集

这是一个相对基础且十分好理解的结构,笔者的实力允许直接使用包含按秩合并和路径压缩的并查集。

种类并查集

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
const int N=5e4+10;
int n,k;
int f[N*3],dep[N*3];
int find(int x)
{
    return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int x,int y)
{
    int fx=find(x),fy=find(y);
    f[fx]=fy;
    // if(dep[fx]<dep[fy])
    // {
    //     f[fx]=fy;
    // }
    // else if(dep[fx]>dep[fy]){
    //     f[fy]=fx;
    // }
    // else{
    //     f[fx]=fy;
    //     dep[fx]++;
    // }
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>k;
    int ans=0;
    for(int i=1;i<=3*n;i++) f[i]=i;
    for(int i=1;i<=k;i++)
    {
        int op,x,y;
        cin>>op>>x>>y;
        if(x>n||y>n) 
        {
            ans++;
            continue;
        }
        int fx=find(x),fy=find(y),fxx=find(x+n),fyy=find(y+n),fxxx=find(x+2*n),fyyy=find(y+2*n);
        if(op==1)//我和你是同类
        {
            if(fxx==fy||fxxx==fy)//能吃我的是你的同类 我能吃你
            {
                ans++;
            }
            else{
                merge(x,y);
                merge(x+n,y+n);
                merge(x+2*n,y+2*n);//他俩能吃的绑定一下
            }
        }
        else{
            if(fx==fy||fxx==fy)//我和你是同类 能吃我的也能吃你
            {
                ans++;
            }
            else{
                merge(x+2*n,y);//吃
                merge(y+n,x);//能吃的
                merge(y+2*n,x+n);//你能吃的能吃我
            }
        }
    }
    cout<<ans;
    return 0;
}

例题

带权并查集

我觉得相对简单啊,就是一个回溯更新的过程,可能你要去理解并查集本身就是一个个集合,你每次merge之后,只不过是把孩子的父亲给改了,并没有去说实现一个菊花图的结构,也就是说,即使是路径压缩,也并非说高度最高两层,如果想实现一个菊花图的效果,可以在压缩之前去进行一个回溯的处理。

#include <cstdio>
#include <iostream>
using namespace std;
int sum[30001],fat[30001],toh[30001],t,ans;
int find(int x)
{
	if(fat[x]==x)
		return x;
	int t1=fat[x],t2=find(fat[x]);
	fat[x]=t2;
	toh[x]+=toh[t1];
	return t2;
}
void unionn(int r1,int r2)
{
	int f1=find(r1),f2=find(r2);
	fat[f2]=f1;
	toh[f2]=sum[f1];
	sum[f1]+=sum[f2];
	sum[f2]=0;
}
int abs(int op)
{
	return op<0?-op:op;
}
int main()
{
	char ds;
	int u,v;
	for(int i=1;i<=30000;i++)
	{
		fat[i]=i;
		sum[i]=1;
	}
	scanf("%d",&t);
	for(int i=1;i<=t;i++)
	{
		cin>>ds;scanf("%d %d",&u,&v);
		if(ds=='M')
		{
			unionn(v,u);
		}
		else
		{
			if(find(u)!=find(v))
				printf("-1\n");
			else
				printf("%d\n",abs(toh[u]-toh[v])-1);
		}
	}
}

例题

贪心

y1s1 ,我觉得看我博客的基础贪心应该都会吧,我这里相对讲一些提高级别的贪心,后续也会包括反悔贪心之类的。

线段覆盖

线段覆盖的本质其实很简单,就是用最少的线段去覆盖更多的区域,这个过程设计对端点的操作。

基础习题

作为我的学弟,你应该会

二分

最基础的就是对有序数组进行二分查找,复杂度 \(O(nlogn)\)

二分答案

当一道题的答案在某个区间内可行,而我们希望我们的取值更靠近我们希望的地方,我们就会用二分,在 \(O(logn*O(check))\) 的复杂度内解决问题。

题目中可能出现 最大值最小最小值最大 等字眼。

在二分的时候,如果不明白 \(l\)\(r\) 应该随着 \(mid\) 怎样变化,不妨每次循环都输出 \(l\) , \(mid\) , \(r\) 观察。

#include<bits/stdc++.h>
#include<bits/extc++.h>
#define int long long
using namespace std;
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	vector<pair<int,int>> vec(n);
	for(auto &[x,y]:vec) cin>>x>>y;
	sort(vec.begin(),vec.end(),[](pair<int,int> &a,pair<int,int> &b){return a.second==b.second?a.first<b.first:a.second<b.second;});
	auto check=[&n,&vec](int t)
	{
		for(auto &[x,y]:vec)
			if((t+x)>y) 
				return false;
			else t+=x;
		return true;
	};
	int l=0,r=2000000;
	int ans=-1;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(check(mid)) ans=mid,l=mid+1;
		else r=mid-1;
	}
	cout<<ans;
	return 0;
}

例题

数学

质数筛

高端人士起步直接欧拉筛。

#include<bits/stdc++.h>
#include<bits/extc++.h>
#define int long long
using namespace std;
bitset<100000010> bt;
int n,q,x;
vector<int> pri;
void get_prime()
{
	bt.set();
	bt[1]=0;
	for(int i=2;i<=n;i++)
	{
		if(bt[i]) 
		{
			pri.push_back(i);
		}
		for(int j=0;j<pri.size()&&i*pri[j]<=n;j++)
		{
			bt[i*pri[j]]=0;
			if(i%pri[j]==0) break;
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>q;
	get_prime();
	while(q--)
	{
		cin>>x;
		cout<<pri[x-1]<<'\n';
	}
	return 0;
}

例题

gcd&lcm

\[\operatorname{lcm}(a,b)=\frac{a*b}{gcd(a,b)} \Leftrightarrow a*b=\operatorname{lcm}(a,b)*gcd(a,b) \]

配套习题

唯一分解定理

任何一个正整数都可以被拆分成 \(a_1^{k1} \times a_2^{k2} \times a_3^{k3} \times a_4^{k4} \ldots\)

#include <bits/stdc++.h>
#define maxn 10005
typedef long long ll;
using namespace std;
int main()
{
 
    int n,i=0;
    int cnt=0;
    int a[maxn]={0};//存储其所有因子
    scanf("%d",&n);
    for(i=2;i*i<=n;i++)
    {
        while(n%i==0)
        {
            a[cnt++]=i;
            n/=i;
        }
    }
    if(n>1)
        a[cnt++]=n;
    for(i=0;i<cnt;i++)
    {
        if(i) printf(" ");
        printf("%d",a[i]);
    }
    printf("\n");
    return 0;
}

欧拉函数

筛法 - OI Wiki

例题

概要

给出一个定义式吧。

\[\varphi(n)=n*\prod_{i=1}^{s}{\frac{p_i-1}{p_i}} \]

Hash

\(2024.12.19:00:06\) 此时的作者只会普通的哈希判重。

单模哈希

int base=13131;
int mod=1e9+7;
unsigned long long hs1[1000010],hs2[1000010];
unsigned q[1000010];
void prepare_hash(int sz)
{
    q[0]=1;
    for(int i=1;i<=sz;i++) q[i]=q[i-1]*base%mod;
}
void get_hash(string a,unsigned long long *sz)
{
    for(int i=1;i<=a.length();i++)
    {
        sz[i]=(sz[i-1]*base+a[i-1])%mod;
    }
}
unsigned long long get_seg_hash(int l,int r,unsigned long long *sz)
{
    return (sz[r]-(sz[l-1]*q[r-l+1])%mod+mod)%mod;
}

DP

LIS

\(O(n^2)\)LIS ,优点就是好写,以及方便记录方案数。

\(O(n^2)\)DP 计算 LIS 长度以及方案数。

例题

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
int dp[100010];
int cnt[100010];
int a[100010];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T;
    cin>>T;
    while (T--)
    {
        int n,Cnt=1;
        cin>>n;
        fill(dp+1,dp+n+1,1);
        fill(cnt+1,cnt+n+1,1);
        fill(a+1,a+n+1,0);
        for(int i=1;i<=n;i++) cin>>a[i];
        int lmx=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<i;j++)
            {
                if(dp[j]+1>dp[i]&&a[j]<=a[i])
                {
                    dp[i]=dp[j]+1;
                    lmx=max(lmx,dp[i]);
                    cnt[i]=cnt[j];
                }
                else if(dp[j]+1==dp[i]&&a[j]<=a[i])
                {
                    cnt[i]+=cnt[j];
                }
            }
        }
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            if(dp[i]==lmx) ans+=cnt[i];
        }
        cout<<lmx<<' '<<ans<<endl;
    }
    return 0;
}

背包

01背包

模板

注意到 01背包 降维是很显然的,我们这里只打算讲 空间开维01背包

dp[j]=max(dp[j],dp[j-v[i]]+w[i]) 这是常规的一维转移,也可以根据题意适当加维。

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
int H,T;
int dp[410][410];
int v[410],h[410],w[410];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>H>>T;
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>v[i]>>h[i]>>w[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=H;j>=v[i];j--)
        {
            for(int k=T;k>=h[i];k--)
                dp[j][k]=max(dp[j][k],dp[j-v[i]][k-h[i]]+w[i]);
        } 
    }
    cout<<dp[H][T]<<endl;
    return 0;
}

例题 \(1\)

例题 \(2\)

完全背包

01背包 相比,只是修改了第二层循环而已。

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
int dp[10000010];
int v[10010],w[10010];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int h,n;
    cin>>h>>n;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++) for(int j=v[i];j<=h;j++) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    cout<<dp[h];
    return 0;
}

例题

多重背包

模板
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
bitset<1010> dp;
int cnt=0;
int v[1100],w[1100];
map<int,int> mp;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    vector<int> vec(6);
    mp[0]=1,mp[1]=2,mp[2]=3,mp[3]=5,mp[4]=10,mp[5]=20;
    int tot=0;
    for(auto &i:vec) cin>>i;
    for(int i=0;i<vec.size();i++) tot+=vec[i]*mp[i];
    dp[0]=1;
    for(int i=0;i<=5;i++)
    {
        for(int k=1;k<=vec[i];k++)
        {
            for(int j=tot;j>=mp[i];j--)
            {
                if(j-k*mp[i]<0) break;
                bool flag=dp[j];
                dp[j]=dp[j]|dp[j-k*mp[i]];
                cnt+=flag!=dp[j];
            }
        }
    }
    cout<<"Total="<<cnt;
    return 0;
}

例题

二进制拆分

这是完全背包的重要拓展

例题

最小消耗背包

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
int dp[100010];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int n;
    cin>>n;
    memset(dp,0x3f,sizeof(dp));
    dp[0]=0;
    for(int i=1;i<=17;i++)
    {
        int x=pow(i,4);
        for(int j=1;j<=n;j++)
        {
            if(j-x<0) continue;
            dp[j]=min(dp[j],dp[j-x]+1);
        }
    }
    cout<<dp[n];
    return 0;
}

例题

可达性背包

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
bool dp[110][10010];
int v[110],w[110];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int n;
    cin>>n;
    int sum=0;
    for(int i=1;i<=n;i++) cin>>w[i],sum+=w[i];
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=n/2;j>=1;j--)
        {
            for(int k=sum/2;k>=w[i];k--)
            {
                dp[j][k]|=dp[j-1][k-w[i]];
            }
        } 
    }
    for(int i=sum/2;i>=0;i--) 
    {
        if(dp[n/2][i]) return cout<<i,0;
    }
    return 0;
}

例题

方案统计背包

类似于 可达性背包

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
int n,m;
int dp[30010];
int v[110],w[30];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i];
    dp[0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=v[i];j--)
        {
            if(dp[j-v[i]]) dp[j]+=dp[j-v[i]];
        }
    }
    cout<<dp[m];
    return 0;
}

例题

背包计算具体方案

这里给出求字典序最小的 Code

#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int f[N][N], w[N], v[N];

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >> v[i] >> w[i];

    for (int i = n; i >= 1; i --)
        for (int j = 0; j <= m; j ++)
        {
            f[i][j] = f[i + 1][j];
            if (j >= v[i]) f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
        }

    for (int i = 1, j = m; i <= n; i ++)
        if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i])
        {
            cout << i << ' ';
            j -= v[i];
        }

    return 0;
}

杂项

棋盘问题&黑白染色法

这一类的问题往往存在某个差不变的性质。

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n1,m1,n2,m2,k,a,s1,s2,s3,s4;
bool flag;
int main(){
	scanf("%d%d%d%d%d",&n1,&m1,&n2,&m2,&k);
	for(int i=1;i<=n1;++i){
		for(int j=1;j<=m1;++j){
			scanf("%d",&a);
			if(a==999999){
				flag=(i%2==j%2);
				a=0;
			}
			if(i%2==j%2){
				s1+=k;
				s3+=a;
			}
			else{
				s2+=k;
				s4+=a;
			}
		}
	}
	for(int i=n1+1;i<=n1+n2;++i){
		for(int j=1;j<=m2;++j){
			scanf("%d",&a);
			if(a==999999){
				flag=(i%2==j%2);
				a=0;
			}
			if(i%2==j%2){
				s1+=k;
				s3+=a;
			}
			else{
				s2+=k;
				s4+=a;
			}
		}
	}
	if(flag){
		printf("%d\n",s1-s2+s4-s3);
	}
	else{
		printf("%d\n",s2-s1+s3-s4);
	}
	return 0;
}

例题

栈序列问题

这一类问题会出现在初赛中,将思维转换为人的思想,就是双指针。

#include<iostream>
#include<iomanip>
#include<queue>
#include<vector>
#include<stack> 
#include<list>
#include<cmath>
#include<string>
#include<algorithm>
#include <climits>
#include <array>
#include<map>
#include<set>
using namespace std;
#define int long long
#define ldb long double

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(nullptr),cout.tie(nullptr);
	int T;
	cin>>T;
	while(T--)
	{
		int sz;
		cin>>sz;
		stack<int> st1;
		vector<int> op1(sz),op2(sz);
		for(auto &i:op1) cin>>i;
		for(auto &i:op2) cin>>i;
		int pos=0;
		bool flag=0;
		for(int i=0;i<sz;i++)
		{
			while(st1.empty()||st1.top()!=op2[i]&&pos<=sz) st1.push(op1[pos++]);
			if(pos>=sz&&st1.top()!=op2[i]) 
			{
				flag=1;
				break;
			}
			st1.pop();
		}
		cout<<(flag==0?"Yes\n":"No\n");
	}
	return 0;
}

例题

无向图的定长有向路径计数

长度为 \(3\)

注意到长度为 \(3\) 的路径都具有中继点,我们预处理出中继点的入度,答案就是

\[\sum\limits_{i=1}^{n}(d_i-1)^2 \]

长度为 \(4\)

注意到长度为 \(4\) 的路径都具有中继边,我们预处理出每个点的入度。

注意到当且仅当边的两个端点皆有入边出边才会产生贡献,答案就是

\[\sum\limits_{i=1}^{m}(d_{u_i}-1)*(d_{v_i}-1) \]

树的遍历

  • 树的中序遍历是按照左子树,根,右子树的顺序访问节点;
  • 树的前序遍历是按照根,左子树,右子树的顺序访问节点;
  • 树的后序遍历是按照左子树,右子树,根的顺序访问节点。

处理这类问题时,常常会用到 分治 来解决问题。

中序前序求后序

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
string a,b;
void dfs(int x,int y,int p,int q)
{
    if(x>y||p>q) return ;
    int pos=a.find(b[x]);
    dfs(x+1,x+pos-p,p,pos-1);
    dfs(x+pos-p+1,y,pos+1,q);
    cout<<b[x];
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>a>>b;
    int len=a.length()-1;
    dfs(0,len,0,len);
    return 0;
}

给出中序后序求前序

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
void preord(string mid,string suf){
    if (mid.size()>0){
        char ch=suf[suf.size()-1];
        cout<<ch;
        int k=mid.find(ch);
        preord(mid.substr(0,k),suf.substr(0,k));
        preord(mid.substr(k+1),suf.substr(k,mid.size()-k-1));
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    string midord,suford;
    cin>>midord;
    cin>>suford;
    preord(midord,suford);
    return 0;
}

给出父子节点表示求最大深度

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
int arr[1000010][2];
int n;
int mx=0;
void dfs(int x,int dep)
{
    if(x==0) {
        mx=max(mx,dep);
        return ;
    }
    dfs(arr[x][0],dep+1);
    dfs(arr[x][1],dep+1);
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>arr[i][0]>>arr[i][1];
    dfs(1,0);
    cout<<mx;
    return 0;
}

给出树父子节点表示求前中后

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)
int n;
array<array<int,2>,1000010> tr;
void preorder(int x)
{
    if(!x) return ;
    cout<<x<<' ';
    preorder(tr[x][0]);
    preorder(tr[x][1]);
}
void midorder(int x)
{
    if(!x) return ;
    midorder(tr[x][0]);
    cout<<x<<' ';
    midorder(tr[x][1]);
}
void suforder(int x)
{
    if(!x) return ;
    suforder(tr[x][0]);
    suforder(tr[x][1]);
    cout<<x<<' ';
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>tr[i][0]>>tr[i][1];
    preorder(1);
    cout<<'\n';
    midorder(1);
    cout<<'\n';
    suforder(1);
    cout<<'\n';
    return 0;
}

Trick

滑动窗口

滑动窗口可以在 \(O(n)\) 的复杂度内处理出每一个区间的最大值和最小值等信息,对降低复杂度有奇效。

#include<iostream>
#include<iomanip>
#include<queue>
#include<vector>
#include<stack> 
#include<list>
#include<cmath>
#include<string>
#include<algorithm>
#include <climits>
#include <array>
#include<map>
#include<set>
using namespace std;
#define int long long
#define ldb long double
int n,k;
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(nullptr),cout.tie(nullptr);
	deque<pair<int,int>> dq1,dq2;
	cin>>n>>k;
	vector<int> mn,mx;
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x;
		while(dq1.size()&&i-dq1.front().first>=k) dq1.pop_front();
		while (dq2.size()&&i-dq2.front().first>=k) dq2.pop_front(); 
		while(dq1.size()&&x<dq1.back().second) dq1.pop_back();
		while(dq2.size()&&x>dq2.back().second) dq2.pop_back();
		dq1.push_back({i,x});
		dq2.push_back({i,x});
		if(i>=k) mn.push_back(dq1.front().second),mx.push_back(dq2.front().second);
	}
	for(auto &x:mn) cout<<x<<' ';
	cout<<'\n';
	for(auto &x:mx) cout<<x<<' ';
	return 0;
}

例题

单调栈

一种特殊的栈,栈内的元素维持某种信息的单调性,可以在 \(O(n)\) 的时间内预处理出每一个元素左侧 or 右侧第一个大于或者小于的值等信息。

#include<bits/stdc++.h>
#include<bits/extc++.h>
#define int long long
using namespace std;
int v[1000010];
int mx=0;
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	vector<pair<int,int>> vec(n+2);
	for(int i=1;i<=n;i++)
	{
		auto &[x,y]=vec[i];
		cin>>x>>y;
	}
	stack<int> st;//两个单调栈 存的是编号
	for(int i=1;i<=n;i++)
	{
		auto &[x,y]=vec[i];//H V
		while(st.size()&&x>vec[st.top()].first) v[i]+=vec[st.top()].second,st.pop();
		st.push(i);
	}
	while(st.size()) st.pop();
	for(int i=n;i>=1;i--)
	{
		auto &[x,y]=vec[i];//H V
		while(st.size()&&x>vec[st.top()].first) v[i]+=vec[st.top()].second,st.pop();
		st.push(i);
	}
	cout<<(*max_element(v+1,v+n+1));
	return 0;
}

例题

边表删边

将起点的出边和终点的对应边到达点都设置为 \(-1\)dfs 时注意判断当前边对应点编号是否大于 \(1\) 即可。

栈内维护最值

P1165 日志分析 - 洛谷 | 计算机科学教育新生态

一般用手写栈,或者巧用 \(dfs\) 序。

注意到栈里的信息不存在回溯,且自下向上传递。

不难想到可以维护栈内前 \(i\) 个元素的最值

\[mx[i]=max(mx[i-1],v[i]) \]

退的时候也不用管他,因为栈的操作对先入栈的元素是没有影响的。

std::Bitset 的使用

bitset - OI Wiki

可以把 \(O(n)\) 优化到 \(O(\frac{n}{w})\)\(w\) 为计算机位数,通常 \(32\) or \(64\)

用字符串初始化的时候记住会倒序存储。

函数转换

很多时候我们推出的式子是一个函数,满足某一些特定的性质,可以巧利用。

集合去重

集合有一个特点就是 无序性 ,所以判定集合或者多重集合是否相等的时候完全可以先排序再比较。

预处理

很多时候题目给出多组询问,但是一部分处理的数据始终不变,我们就可以对这部分数据进行预处理降低复杂度。

精度处理

一般来说,如果题目让我们保留一两位小数,在数据允许的范围内,我们完全可以通过输入乘相同的倍数解决。

卡常

循环展开

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
#define int long long
#define fc freopen("in.in","r",stdin),freopen("out.out","w",stdout)

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    for(int i=1;i<=n;i+=8)
    {
        //i
        //i+1
        //i+2
        //i+3
        //i+4
        //i+5
        //i+6
        //i+7
    }
    for(int i=1;i<=n%8;i++)
    {
        //i
    }
    return 0;
}

离线

当题目不要求我们强制在线的时候,我们可以观察题目的性质,如果我们可以通过排序离散化等方式重置询问的顺序来降低我们计算答案的次数,那再好不过。

踩过的坑

  • -INT_MAX 不等于 INT_MIN
  • 容器能传实参就不要传形参,不然复杂度多个 \(n\)
  • 欧拉筛的 for 要循环到 \(n\)
  • 读题的时候对样例要考虑全面!
  • unqiue 前记得 sort
  • bool 数组尽量用 Bitset 代替。
  • 不要抱有数组不多开一位也能 AC 的侥幸心理。
  • 注意求得是 还是 面积
  • 二分的答案不是 \(l\) 也不是 \(r\) ,而是 check 成功的 \(mid\)
  • 找不到性质的时候,记得打表!
  • 观察数据在操作前后是否有始终不变的地方,包括和差奇商最大公约数最小公倍数同余等等。
  • 欧拉路径退栈的时候再存点
posted @ 2024-12-15 01:29  Luo_Saisei  阅读(54)  评论(0)    收藏  举报