一些并不怎么重要的题目

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×M 的矩阵,选择若干个不相邻的元素使它们的和最大,求出这个最大的和。
1NM6T20
不相邻是指 左、右、上、下、左上、左下、右上、右下 八个方向不相邻。

思路:

一看到 NM 的取值范围,就知道这个题肯定是 爆搜!
那怎么搜索呢?
对于每一个数,我们有选择跟不选两种情况;
如果不选,可以继续搜索其他合法数。
如果选,那么我们就不能再选择它周围的数了,标记一下。
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[jw[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 开头这样的顺序排的。
设这个数字为 ai,则 ai 开头的子串数量就是 ni+1
知道了这一点后,我们就可以先记录每个数字的位置 idai,再把数字排序,计算出每个数开头的子串数量,最后计算前缀和。
每次查询的时候找第一个前缀和 kk 的位置 p,说明第 k 个子串肯定在 ap 开头的范围内。
现在我们知道左端点为 idp,接着计算右端点。
此时 sump 为加上 ap 开头的所有数之后的前缀和,所以 ksump1即为要往后延长的数量。
但是注意这样查的范围是右界 +1,所以最后还要 1,即右端点为 idp+ksump11
子串数最大为 n(n+1)21n3×105
记得开 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 次询问
给你两对 uv,求它们的最短路径是否有重合的点。
对于每个询问,如果有公共点,输出大写字母 Y;否则输出 N

思路

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

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

  • depthx>=depthLCA(u,v)
  • LCA(u,x)=xLCA(v,x)=x;

粗略的证明:
depthx<depthlca(u,v) 时,说明一定不可能在路径上。
如果上一个条件满足,当 lca(u,x)=xlca(v,x)=x 时,说明从 uv 走到 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

思路:

二维差分
定义数组差分数组 cfij
很容易想到差分数组的求法:cfi,j=ai,jcfi1,jcfi,j1+cfi1,j1;
最后统计数据的方法就是:cfi,j+=cfi1,j+cfi,j1cfi1,j1
将点 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 @   lazy_ZJY  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示