一些并不怎么重要的题目

P1111 修复公路

肖邦 g 小调第一叙事曲

并查集 + \(krustra\) 板子题
code:

#include<bits/stdc++.h>

using namespace std;

const int maxn=1010;
const int maxm=100100; 

int n,m;
int en=0;
int ans=-1;
int to[maxn];

inline int read()
{
	int f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-48;
		ch=getchar();
	}
	return f*x;
}

struct edge{
	int p1,p2,d;
}ed[maxm];

bool operator<(const edge &a,const edge &b)
{
	return a.d<b.d;
}

int go(int p)
{
	if(p==to[p])return to[p];
	else return to[p]=go(to[p]); 
}

int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		to[i]=i;
	}
	for(int i=1;i<=m;i++)
	{
		ed[i].p1=read(),ed[i].p2=read(),ed[i].d=read();
	}
	sort(ed+1,ed+1+m);
	for(int i=1;i<=m;i++)
	{
		int p1=ed[i].p1,p2=ed[i].p2;
		if(go(p1)!=go(p2))
		{
			to[go(p1)]=to[go(p2)];
			en++;
			ans=max(ed[i].d,ans); 
		}
	}
	if(en==n-1)cout<<ans<<endl;
	else cout<<-1<<endl;
	return 0;
 } //第一次 m、n 写反喜提 10 分

P1123 取数游戏

(秀一下我新学的 \(LaTeX\) 不过分吧 qwq)

これから、我が剣は汝と共にあり、汝の運命は我とともに存続する。

题意:

\(T\) 组数据;
每组数据给你一个 \(N \times M\) 的矩阵,选择若干个不相邻的元素使它们的和最大,求出这个最大的和。
\(1 \le N\)\(M \le 6\)\(T \le 20\)
不相邻是指 左、右、上、下、左上、左下、右上、右下 八个方向不相邻。

思路:

一看到 \(N\)\(M\) 的取值范围,就知道这个题肯定是 爆搜!
那怎么搜索呢?
对于每一个数,我们有选择跟不选两种情况;
如果不选,可以继续搜索其他合法数。
如果选,那么我们就不能再选择它周围的数了,标记一下。
code:

#include<bits/stdc++.h>

using namespace std;

const int maxn=10;
const int maxm=10; //好习惯

int t;
int m,n;
int ans=0;
int mp[maxn][maxm];
int dx[]={0,1,-1,0,0,1,-1,1,-1};
int dy[]={0,-1,1,-1,1,1,-1,0,0}; //方向坐标 
int mark[maxn][maxm];

void dfs(int x,int y,int sum)
{
	if(y>m){
		dfs(x+1,1,sum);
		return;
	}
	if(x>n){
		ans=max(ans,sum);
		return;
	}
	//两种情况 
	dfs(x,y+1,sum); //不选的时候 
	if(!mark[x][y]) //选择的时候 
	{
		for(int i=1;i<=8;i++)
		{
			int xx=x+dx[i],yy=y+dy[i];
			mark[xx][yy]+=1; //将周围数的 mark +1
			//这个地方不能用 vis 判断是否遍历过,因为它也可能是其他被选择的数的相邻数 
		}
		dfs(x,y+1,sum+mp[x][y]); //sum 的值加上 选择的 这个数 
		for(int i=1;i<=8;i++)
		{
			int xx=x+dx[i],yy=y+dy[i]; //回溯 
			mark[xx][yy]-=1;
		}
	 } 
}

int main()
{
	cin>>t;
	while(t--)
	{
		ans=0;
		memset(mp,0,sizeof(mp));
		memset(mark,0,sizeof(mark)); //每次一定要清空 
		cin>>n>>m;
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				cin>>mp[i][j];
			}
		}
		dfs(1,1,0);
		cout<<ans<<endl;
	}
	return 0; 
}(参考题解过的TAT,我太菜了)

传送门:P1160 队列安排

为你,千千万万遍。

题目意思:

n 个同学排成一行,给你编号 2~n 的同学的位置信息 k、 p ,然后再给你 m 个删除操作,求最后的排列。

  • 当 p = 0 时,表示将第 i 个同学插入第 k 个同学的左边;
  • 当 p = 1 时,表示将第 i 个同学插入第 k 个同学的右边。

思路:

链表(以为自己不会链表,结果就是个链前存图 qwq)
不同的就是要同时存 左边和右边,每两个学生之间有且只有一条路径
code:

#include<bits/stdc++.h>

using namespace std;

int n,m;
bool vis[100010];

struct people{
	int left,right;
	int vis; 
}pe[100010];

void add_edge(int u,int v,int p)
{
	if(p==1){//u = k
	//i = v
		pe[u].left=v;
		pe[u].right=pe[v].right;
		pe[pe[v].right].left=u;
		pe[v].right=u;
	}
	else{
		pe[u].right=v;
		pe[u].left=pe[v].left;
		pe[pe[v].left].right=u;
		pe[v].left=u;
	}
}

int main()
{
	cin>>n;
	pe[0].left=0,pe[0].right=0; //这个地方要从 0 开始
	add_edge(1,0,1);
	for(int i=2;i<=n;i++)
	{
		int k,p;
		cin>>k>>p;
		add_edge(i,k,p);
	}
	cin>>m;
	for(int i=1;i<=m;i++)
	{
		int k;
		cin>>k;
		pe[k].vis=1;
	}
	for(int i=pe[0].right;i;i=pe[i].right)
	{
		if(pe[i].vis==0)cout<<i<<" ";
	}
	return 0;
 } 

P1510 精卫填海

背包 DP 复习

  • 01 背包
    顾名思义,一个东西只有选和不选两种选择。
    求体积一定的包里能放的最大质量。
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=w[i];j--)//w[i]表示物品 i 的体积
		{
			f[j]=max(f[j],f[j-w[i]]+v[i]);//v[i]表示物品 i 的质量
            //f[j]表示 背包容量为 j 时最多能放的质量
		}
	}

为什么要逆序

首先,通过上一个问题,我们确认了我们目前一维的dp数组,保存的是确认过的最新一层的数据,即上一层的数据。
当我们计算当前层时,对于二维时的状态转移方程有
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
可以看到,dp[i - 1][j - v[i]] + w[i] 使用的上一层的原始数据(dp[i - 1]),而我们使用一维的状态转移方程时有
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
当我们从小到大更新是, 因为j - v[i] 是严格小于j 的,所以我们可以举个例子 dp[3] = max(dp[3], dp[2] + 1); 因为我们是从小到大更新的,所以当更新到dp[3]的时候,dp[2]已经更新过了,已经不是上一层的dp[2]。
而当我们逆序更新时有,举例 dp[8] = max(dp[8], dp[6] + 2)当更新dp[8]时,dp[6]还没有被更新,还是上一层的数据,这样才能保证没有读入脏数据。

以上来自 AcWing

  • 完全背包
    与 01 背包的区别就是:同一个物品可以被放多次
for(int i=1;i<=n;i++)
	{
		for(int j=w[i];j<=m;j++)//这里的枚举顺序要改变一下
		{
			f[j]=max(f[j],f[j-w[i]]+v[i]);
            //f[j]表示总体积是 j 时最大价值是多少
		}
	}

枚举顺序改变的原因:

每次再使用 f[j] 的时候状态就是已经更新过的

  • 多重背包
    在完全背包的基础上,每个物品是有限定数量的。
    (1) 可以把每种物品看成 cnt 个物品,只不过他们的质量体积都相同,这样就能转化为 01 背包问题。
    (2) 每个物品有 不选选一个选两个、………… 选 cnt 个,加一重循环就能实现。
    好像两个运行时间差不多?
    代码(方法 2 )
for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>v[i]>>cnt[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=w[i];j<=m;j++)
		{
			for(int k=0;k<=cnt[i];k++)
			{
				f[j]=max(f[j],f[j-k*w[i]]+k*v[i]);
			}
		}
	}

回到这道题
明显的 01 背包
求的是剩下的最大体力,就是求消耗的最小体力。将体力看做背包 将石子看做物品。
\(f[ j ]=max( f[ j ],f[ j-w[ i ] ]+v[ i ])\)
求一边后再从小到大搜 j ,找到一个石子 >= 要求的最小解,再输出 总体力 - 最小解。
code:

#include<bits/stdc++.h>

using namespace std;

int V,n,c;
int w[10010];
int v[10010];
int f[100010];

int main()
{
	cin>>V>>n>>c;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>v[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=c;j>=v[i];j--)
		{
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	}
	for(int j=0;j<=c;j++)
	{
		if(f[j]>=V){
			cout<<c-j<<endl;
			return 0;
		}
	}
	cout<<"Impossible"<<endl;
	return 0; 
}

传送门:P10995 【MX-J3-T2】Substring

本来是一道黄题,结果它橙了。。。(流出做不出橙题的泪)

题目意思:

给你一个数列,数列中每个数只出现了一次,求字典序为第 k 个的子串的左右端点。

思路

贪心、前缀和、二分。
由于查询的是子串,那么串一定连续。这时候子串排序的结果一定是按照 \(1\) 开头,\(2\) 开头,...,\(n\) 开头这样的顺序排的。
设这个数字为 \(a_i\),则 \(a_i\) 开头的子串数量就是 \(n-i+1\)
知道了这一点后,我们就可以先记录每个数字的位置 \(id_{a_i}\),再把数字排序,计算出每个数开头的子串数量,最后计算前缀和。
每次查询的时候找第一个前缀和 \(\ge k≥k\) 的位置 \(p\),说明第 \(k\) 个子串肯定在 \(a_p\) 开头的范围内。
现在我们知道左端点为 \(id_p\),接着计算右端点。
此时 \(sum_p\) 为加上 \(a_p\) 开头的所有数之后的前缀和,所以 \(k-sum_{p-1}\)即为要往后延长的数量。
但是注意这样查的范围是右界 \(+1\),所以最后还要 \(−1\),即右端点为 \(id_p+k-sum_{p-1}-1\)
子串数最大为 \(\Large\frac{n(n+1)}{2}\)\(1\le n\le 3\times 10^5\)
记得开 long long。

代码

#include<bits/stdc++.h>

using namespace std;

#define int long long

int n,q;

struct number{
	int a,pos;
}a[300100];

int rr[300100];
int cnt[300100];

bool cmp(const number &a,const number &b)
{
	return a.a<b.a;
}

bool check(int m,int p)
{
	if(cnt[m]>=p)return true;
	else return false;
}

signed main()
{
	cin>>n>>q;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].a;
		a[i].pos=i;
		rr[a[i].a]=i;
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++)
	{
		cnt[i]=n-a[i].pos+1+cnt[i-1];
	}
	for(int i=1;i<=q;i++)
	{
		int p;
		cin>>p;
		int l=1,r=n;
		while(l<r){
			int mid=l+r>>1;
			if(check(mid,p))r=mid;
			else l=mid+1;
		}
		cout<<a[l].pos<<" "<<a[l].pos-1+p-cnt[l-1];
		cout<<endl;
	}
	return 0;
}

P3398 仓鼠找 sugar

题目意思:

给你一棵树,有 \(q\) 次询问
给你两对 \(u\)\(v\),求它们的最短路径是否有重合的点。
对于每个询问,如果有公共点,输出大写字母 Y;否则输出 N

思路

树上最短路径很容易想到是 \(u\to lca(u,v)\to v\)
观察下面的图:

假设我们此时要查询的两组 \(u\)\(v\) 分别为 \(2\)\(7\)\(4\)\(8\)
\(lca(2,7)\) 的值为 \(1\),路径 1 为 \(2\to1\to4\to5\to7\)
\(lca(4,8)\) 的值为 4,路径 2 为 \(4\to5\to8\)
很容易看出如果两条路径有重点,那么一定有一条路径的lca在另一条路径上,并且这个lca是深度比较大的那一个
并且当 x 为 u 到 v 的路径上的点时:

  • \(depth_x>=depth_{LCA(u,v)}\)
  • \(LCA(u,x)=x\)\(LCA(v,x)=x;\)

粗略的证明:
\(depth_x<depth_{lca(u,v)}\) 时,说明一定不可能在路径上。
如果上一个条件满足,当 \(lca(u,x)=x\)\(lca(v,x)=x\) 时,说明从 \(u\)\(v\) 走到 \(x\) 不需要有拐点,并且前面已知从 \(u\) 走到 \(v\) 的拐点为 \(lca(u,v)\)\(x\)的深度等于 lca 或比 lca 大,说明 \(u\) 到 lca 或 \(v\) 到 lca 会经过 \(x\)

代码:

#include<bits/stdc++.h>

using namespace std;

const int maxn=100010;
const int maxm=100100;

int n,q;
int en;
int fir[maxn];
int f[maxn][25];

struct edge{
	int v,next;
}ed[maxm*2];

void add_edge(int u,int v)
{
	en++;
	ed[en].v=v;
	ed[en].next=fir[u];
	fir[u]=en; 
 } 
 
int depth[maxn];
 
void dfs(int now,int fa)
{
	depth[now]=depth[fa]+1;
	f[now][0]=fa;
	for(int i=1;i<=20;i++)
	{
		f[now][i]=f[f[now][i-1]][i-1];
	}
	for(int i=fir[now];i;i=ed[i].next)
	{
		if(ed[i].v!=fa) dfs(ed[i].v,now);
	}
}

int get_lca(int a,int b)
{
	if(depth[a]<depth[b]) swap(a,b);
	for(int i=20;i>=0;i--)
	{
		if(depth[f[a][i]]>=depth[b])
			a=f[a][i];
	}
	if(a==b) return a;
	for(int i=20;i>=0;i--)
	{
		if(f[a][i]!=f[b][i])
			a=f[a][i],b=f[b][i];
	}
	return f[a][0];
}

int main()
{
	cin>>n>>q;
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	for(int i=1;i<=q;i++)
	{
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		int lca1=get_lca(a,b);
		int lca2=get_lca(c,d);
		if(depth[lca1]>=depth[lca2]) //查找c到d的路径上有没有lca1 
		{
			if(get_lca(c,lca1)==lca1||get_lca(d,lca1)==lca1)cout<<"Y"<<endl;
			else cout<<"N"<<endl;
		}
		else{
			if(get_lca(a,lca2)==lca2||get_lca(b,lca2)==lca2)cout<<"Y"<<endl;
			else cout<<"N"<<endl;
		}
	}
	return 0;
}

P5542 [USACO19FEB] Painting The Barn S

思路:

二维差分
定义数组差分数组 \(cf_{i,j}\)
很容易想到差分数组的求法:\(cf_{i,j}=a_{i,j}-cf_{i-1,j}-cf_{i,j-1}+cf_{i-1,j-1}\);
最后统计数据的方法就是:\(cf_{i,j}+=cf_{i-1,j}+cf_{i,j-1}-cf_{i-1,j-1}\)
将点 x1,y1 到 x2,y2 更改的方法:

cf[x1][y1] += c; 
cf[x1][y2+1] -=c; 
cf[x2+1][y1] -=c; 
cf[x2+1][y2+1] += c;

注意:

1。目给的是二维坐标系的左下角和右上角,实际上就是我们平常使用差分或前缀和时,用行列表示的左上角和右下角(相当于把坐标系转一下),所以直接拿来用就好了,不需要进行转化
2。题目告知的是点的坐标,而差分和前缀和都是在格子上实现的,所以要把点转化成格子,即左上角的点横坐标和纵坐标都+1(不会的话可以画个图模拟下)

代码:

#include<bits/stdc++.h>

using namespace std;

int n,k;
int cf[10010][10010];
int ans=0;

int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		a++,b++;
		cf[a][b]++;
		cf[c+1][d+1]++;
		cf[a][d+1]--;
		cf[c+1][b]--;
	}
	for(int i=1;i<=1000;i++)
	{
		for(int j=1;j<=1000;j++)
		{
			cf[i][j]+=cf[i-1][j]+cf[i][j-1]-cf[i-1][j-1];
			if(cf[i][j]==k) ans++;		}
	}
	cout<<ans<<endl;
	return 0;
 } 
posted @ 2024-08-21 16:34  lazy_ZJY  阅读(13)  评论(0编辑  收藏  举报