一些并不怎么重要的题目
P1111 修复公路
肖邦 g 小调第一叙事曲
并查集 +
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 取数游戏
(秀一下我新学的
これから、我が剣は汝と共にあり、汝の運命は我とともに存続する。
题意:
有
每组数据给你一个
不相邻是指 左、右、上、下、左上、左下、右上、右下 八个方向不相邻。
思路:
一看到
那怎么搜索呢?
对于每一个数,我们有选择跟不选两种情况;
如果不选,可以继续搜索其他合法数。
如果选,那么我们就不能再选择它周围的数了,标记一下。
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 背包
求的是剩下的最大体力,就是求消耗的最小体力。将体力看做背包 将石子看做物品。
求一边后再从小到大搜 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 个的子串的左右端点。
思路
贪心、前缀和、二分。
由于查询的是子串,那么串一定连续。这时候子串排序的结果一定是按照
设这个数字为
知道了这一点后,我们就可以先记录每个数字的位置
每次查询的时候找第一个前缀和
现在我们知道左端点为
此时
但是注意这样查的范围是右界
子串数最大为
记得开 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
题目意思:
给你一棵树,有
给你两对
对于每个询问,如果有公共点,输出大写字母 Y;否则输出 N
思路
树上最短路径很容易想到是
观察下面的图:
假设我们此时要查询的两组
很容易看出如果两条路径有重点,那么一定有一条路径的lca在另一条路径上,并且这个lca是深度比较大的那一个
并且当 x 为 u 到 v 的路径上的点时:
或
粗略的证明:
当
如果上一个条件满足,当
代码:
#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
思路:
二维差分
定义数组差分数组
很容易想到差分数组的求法:
最后统计数据的方法就是:
将点 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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!