2025.5.27-2025.5.28做题记录

前言

不想写 dp ,于是随便找找题做了。

题目

例题A:旅游巴士

CSP-J2023 某人人生的第一场 OI,打的是依托。
似乎是没学 dp,没学图论,没有做题技巧或者思维。
就学了点搜索。果然学竞赛不能跟着大众校外机构(tctm)啊。
也算认识到不少人了吧,出去集训还认识了现在在 cdqz 的朋友,虽然他好像不学 OI 了。

观察到 \(k\le 100\),考虑直接分层图。
如果我们到达某个点的时候,还没开门,我们可以直接加上 \(k\) 的正整数倍,然后到达。
然后直接上 dijkstra。没了。

点击查看代码
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e4+10;
const int M=2e4+10;
const int inf=0x3f3f3f3f;
int n,m,k;
struct node{
	int v,a;
};
vector<node> vec[N];
int dis[N][110];
bool vis[N][110];
struct Dij{
	int id,i,val;
	bool operator <(const Dij x)const{
		return val>x.val;
	}
};
priority_queue<Dij> que;
void dij(){
	que.push({1,0,0});
	memset(dis,0x3f,sizeof(dis));
	dis[1][0]=0;
	while(!que.empty()){
		Dij q=que.top();que.pop();
		int u=q.id,i=q.i;
		if(vis[u][i]) continue;
		vis[u][i]=1;
		for(node to:vec[u]){
			int v=to.v,a=to.a;
			int t=dis[u][i],w=(i+1)%k;
			if(t<a) t+=(a-t+k-1)/k*k;
			if(dis[v][w]>t+1){
				dis[v][w]=t+1;
				que.push({v,w,t+1});
			}
		}
	}
}
int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=m;i++){
		int u,v,a;
		cin>>u>>v>>a;
		vec[u].push_back({v,a});
	}
	dij();
	if(dis[n][0]==inf) cout << -1;
	else cout << dis[n][0];
	return 0;
}

例题B:相连的边

注意到,题目中要求的三条边只能是菊花或者一条链!
我们直接考虑枚举花心,然后求出一个答案。
在枚举每条边的中间那条,再求出一个答案。
两个答案取 \(\max\) 就是最后答案。
难的可能是优美的代码实现:

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
struct node{
	int v;
    ll val;
};
bool cmp(node a,node b){
	return a.val>b.val;
}
vector<node> vec[N];
int n;
ll sum,ans;
int main(){
	cin>>n;
	for(int i=2;i<=n;i++){
		int v,val;
		cin>>v>>val;
		vec[i].push_back({v,val});
		vec[v].push_back({i,val});
	}
	for(int i=1;i<=n;i++){
		sort(vec[i].begin(),vec[i].end(),cmp);
		if(vec[i].size()<3) continue;
		sum=vec[i][0].val+vec[i][1].val+vec[i][2].val;
		ans=max(ans,sum);
	}
	for(int i=1;i<=n;i++){
		if(vec[i].size()==1) continue;
		for(node to:vec[i]){
			int j=to.v,val=to.val;
			if(vec[j].size()==1) continue;
			sum=val;
			sum+=(vec[j][0].v==i ? vec[j][1].val : vec[j][0].val);
			sum+=(vec[i][0].v==j ? vec[i][1].val : vec[i][0].val);
			ans=max(ans,sum);
		}
	}
	cout << ans;
	return 0;
}

例题C:小Z的袜子

莫队模板题,莫队就是暴力!
首先分块,把给出的序列分块,然后把询问按照分好的块左端点排序。
暴力求出第一个询问的答案,第二个询问答案直接通过移动指针完成。
时间复杂度被证明为 \(O(n\sqrt n)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e4+10;
int n,m,len;
int c[N];
ll sum;
int cnt[N];
ll ans1[N],ans2[N];
struct Query{
	int l,r,id;
}q[N];
bool cmp(Query a,Query b){
	if(a.l/len==b.l/len) return ((a.l/len)&1) ? a.r<b.r : a.r>b.r;
	return a.l<b.l;
}
//奇偶性排序优化
void add(int u){
	sum+=cnt[u];
	cnt[u]++;
}
void del(int u){
	cnt[u]--;
	sum-=cnt[u];
}
int main(){
	cin>>n>>m;
	len=sqrt(n);
	for(int i=1;i<=n;i++){
		cin>>c[i];
	}
	for(int i=1;i<=m;i++){
		cin>>q[i].l>>q[i].r;
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		if(q[i].l==q[i].r){
			ans1[q[i].id]=0;
			ans2[q[i].id]=1;
			continue;
		}
		while(l>q[i].l) add(c[--l]);
		while(r<q[i].r) add(c[++r]);
		while(l<q[i].l) del(c[l++]);
		while(r>q[i].r) del(c[r--]);
		ans1[q[i].id]=sum;
		ans2[q[i].id]=(ll)(r-l+1)*(r-l)/2;
	}
	for(int i=1;i<=m;i++){
		if(ans1[i]!=0){
			ll gcd=__gcd(ans1[i],ans2[i]);
			ans1[i]/=gcd,ans2[i]/=gcd;
		}else{
			ans2[i]=1;
		}
		cout << ans1[i]<<'/'<<ans2[i]<<'\n';
	}
	return 0;
}

例题D:狼人游戏

草这怎么是树形 dp 。

从题目的限制中可以得到最后的保护与指控关系构成一棵树。

然后我们把树建出来,指控边为 1,保护边为 2。

套用树形背包定义 \(f_{i,j,0/1}\) 表示以 \(i\) 为根的子树,里面有 \(j\) 个狼人,\(i\) 是/不是 狼人。

然后套用背包的转移。

点击查看代码
#include<iostream>
#include<vector>
#define ll long long
using namespace std;
const int N=210;
const int p=1e9+7;
struct node{
	int v,w;
};
vector<node> vec[N];
int f[N][N][2];
int n,w,m,in[N],siz[N];
void dfs(int u){
	f[u][0][0]=1;
	f[u][1][1]=1;
	siz[u]=1;
	for(auto to:vec[u]){
		int v=to.v,w=to.w;
		dfs(v);
		siz[u]+=siz[v];
		for(int i=siz[u];i>=0;i--){
			ll tmp0=0,tmp1=0;
			for(int j=min(i,siz[v]);j>=0;j--){	
				if(w){
					tmp0=(tmp0+1ll*f[u][i-j][0]*(f[v][j][1]+f[v][j][0])%p)%p;
					tmp1=(tmp1+1ll*f[u][i-j][1]*f[v][j][0])%p;
				}else{
					tmp0=(tmp0+1ll*f[u][i-j][0]*(f[v][j][1]+f[v][j][0])%p)%p;
					tmp1=(tmp1+1ll*f[u][i-j][1]*f[v][j][1])%p;
				}
			}	
			f[u][i][0]=tmp0;
			f[u][i][1]=tmp1;
		}
	}
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
	cin>>n>>w>>m;
	for(int i=1;i<=m;i++){
		char op;int a,b;
		cin>>op>>a>>b;
		if(op=='A'){
			vec[a].push_back({b,1});
		}else{
			vec[a].push_back({b,0});
		}
		in[b]++;
	}
	for(int i=1;i<=n;i++){
		if(in[i]==0){
			vec[0].push_back({i,1});
		}
	}
	dfs(0);
	cout << f[0][w][0];
	return 0;	
}

例题E:【模板】三维偏序(陌上花开)

好好听的名字。

三维偏序怎么能少得了我高贵(暴力)的树套树呢!
一维排序,剩下两维用树状数组套权值线段树维护二维前缀和。
关于树套树维护二维前缀和,可以把它们放到坐标轴上,树状数组维护了 \(x\) 轴,权值线段树维护了 \(y\) 轴。
由树状数组先钦定 \(x\) 轴的范围,再由权值线段树去统计 \(y\) 轴上有多少个点。如图(有点乱):

点击查看代码
#include<bits/stdc++.h>
#define lowbit(i) i&-i
using namespace std;
const int N=1e5+10;
const int K=2e5+10;
int n,k;
struct node{
	int a,b,c;
}p[K];
bool cmp(node a,node b){
	return a.a!=b.a ? a.a<b.a : (a.b!=b.b ? a.b<b.b : a.c<b.c);
}
struct Tree{
	int val;
	int ls,rs;
}tr[K<<6];
int tot,rt[K<<6];
void pushup(int u){
	tr[u].val=tr[tr[u].ls].val+tr[tr[u].rs].val;
}
void modify(int &u,int l,int r,int x,int val){
	if(!u) u=++tot;
	if(l==r){
		tr[u].val+=val;
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid) modify(tr[u].ls,l,mid,x,val);
	else modify(tr[u].rs,mid+1,r,x,val);
	pushup(u);
}
int query(int u,int l,int r,int x,int y){
	if(!u) return 0;
	if(l>=x && r<=y){
		return tr[u].val;
	}
	int res=0;
	int mid=(l+r)>>1;
	if(x<=mid) res+=query(tr[u].ls,l,mid,x,y);
	if(mid<y) res+=query(tr[u].rs,mid+1,r,x,y);
	return res;
}
void update(int x,int y,int val){
	if(x==0) return ;
	for(int i=x;i<=k;i+=lowbit(i)){
		modify(rt[i],1,k,y,val);
	}
}
int qry(int x,int y){
	int res=0;
	for(int i=x;i;i-=lowbit(i)){
		res+=query(rt[i],1,k,1,y);
	}
	return res;
}
int Ans[K<<1];
int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>p[i].a>>p[i].b>>p[i].c;
	}
	sort(p+1,p+1+n,cmp);
	int cnt=1;
	for(int i=1;i<=n;i++){
		if(p[i].a==p[i+1].a && p[i].b==p[i+1].b && p[i].c==p[i+1].c){
			cnt++;
			continue;
		}else{
			update(p[i].b,p[i].c,cnt);
			int ans=qry(p[i].b,p[i].c);
			Ans[ans]+=cnt;
			cnt=1;
		}
	}
	for(int i=1;i<=n;i++){
		cout << Ans[i] << '\n';
	}
	return 0;
}

例题F:最大异或和

可持久化 01 Trie 模板题

正常的最大异或和问题肯定就按位贪心就行了。
但是这个题是区间的,所以我们考虑像主席树一样可持久化一下。
树上扔一个 \(cnt\) 表示这个节点存不存在,剩下的事主席树模板。

点击查看代码
#include<bits/stdc++.h>

using namespace std;
const int N=6e5+10;
int n,m,a[N];
int rt[N],tr[N<<5];
int son[N<<5][2];
int tot=1;
void insert(int now,int pre,int t,int x){
	if(t<0) return ;
	int i=(x>>t)&1;
	son[now][!i]=son[pre][!i];
	son[now][i]=tot++;
	tr[son[now][i]]=tr[son[pre][i]]+1;
	insert(son[now][i],son[pre][i],t-1,x);
}
int query(int now,int pre,int t,int x){
	if(t<0) return 0;
	int i=(x>>t)&1;
	if(tr[son[pre][!i]]>tr[son[now][!i]]){
		return (1<<t)+query(son[now][!i],son[pre][!i],t-1,x);
	}else{
		return query(son[now][i],son[pre][i],t-1,x);
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	rt[0]=tot++;
	insert(rt[0],0,25,0);
	for(int i=1;i<=n;i++){
		int b;cin>>b;
		a[i]=a[i-1]^b;
		rt[i]=tot++;
		insert(rt[i],rt[i-1],25,a[i]);
	}
	for(int i=1;i<=m;i++){
		char op;
		cin>>op;
		if(op=='A'){
			int x;cin>>x;
			n++;
			a[n]=a[n-1]^x;
			rt[n]=tot++;
			insert(rt[n],rt[n-1],25,a[n]);
		}else{
			int l,r,x;
			cin>>l>>r>>x;
			l--,r--;
			if(l==0) cout << query(0,rt[r],25,x^a[n]) << '\n';
			else cout << query(rt[l-1],rt[r],25,x^a[n]) << '\n';
		}
	}
	return 0;
}
posted @ 2025-05-28 21:29  Tighnari  阅读(13)  评论(0)    收藏  举报