2025国庆Day6

模拟赛

T1

枚举每个点

直接对每个ai%r

再考虑区间减

判断是否有剩余即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
#define lson x<<1
#define rson x<<1|1
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;	
}
inline int read()
{
	int x=0,f=1;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 x*f;
}
#define lowbit(x) x&(-x)
int a[100005];
const int MAXN=1e5+5;
void modify(int l,int r,int c){
	for(int i=l;i<=MAXN;i+=lowbit(i)) a[i]+=c;
	for(int i=r+1;i<=MAXN;i+=lowbit(i)) a[i]-=c;
}
int query(int x){
	int ans=0;
	for(int i=x;i;i-=lowbit(i)) ans+=a[i];
	return ans;
}
signed main()
{
	freopen("dskfj.in", "r", stdin);
	freopen("dskfj.out", "w", stdout);
	int n=read(),k=read(),r=read();
	for(int i=1;i<=n;i++){
		int x=read();
		modify(i,i,x);
	}
	for(int i=1;i<=n-k+1;i++){
		int zh=query(i)%r;
		if(!zh) continue;
		int l=i,r=i+k-1;
		modify(l,r,-zh);
	}
	for(int i=1;i<=n;i++){
		int zh=query(i);
		if(zh%r!=0){
			cout<<"No"<<'\n';
			return 0;
		}
	}
	cout<<"Yes"<<'\n';
	return 0;
}

T2

树形dp

一棵子树内共四种情况

空、一条链、两条链、一个倒Y

记录倒Y的数量(a)及链的数量(b)

发现3个链自己可以组成三叉

一个倒Y也可组成三叉

一个链和一个倒Y也可组成三叉

先三个链自己配

再配倒Y

于是记录min(a,2)和b%3的值

发现可能会对父亲贡献链和倒Y

因此倒Y要记录min(a,3)

链记录0~4,注意0,3 、1,4不同,区别在于能否对父亲贡献2条链

然后树上背包就行

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;	
}
inline int read()
{
	int x=0,f=1;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 x*f;
}
int dp[100005][4],f[100005][5][4],g[5][4];
vector<pair<int,int> > tu[100005];
void dfs(int x,int fa){
	f[x][0][0]=0;
	for(auto [ed,w]:tu[x]){
		if(ed==fa) continue;
		dfs(ed,x);
		for(int i=0;i<5;i++){
			for(int j=0;j<4;j++) g[i][j]=-1e18;
		}
		for(int i=0;i<5;i++){
			for(int j=0;j<4;j++){
				g[i][j]=max(g[i][j],f[x][i][j]+dp[ed][0]);
				int nxt=j+1;
				if(nxt>3) nxt=3;
				g[i][nxt]=max(g[i][nxt],f[x][i][j]+w+max(dp[ed][2],dp[ed][3]));
				nxt=i+1;
				if(nxt==5) nxt=2;
				g[nxt][j]=max(g[nxt][j],f[x][i][j]+w+max(dp[ed][0],dp[ed][1]));
			}
		}
		for(int i=0;i<5;i++){
			for(int j=0;j<4;j++){
				f[x][i][j]=g[i][j];
			}
		}
	}
	for(int q=0;q<3;q++){
		for(int i=0;i<5-q;i++){
			int st=i%3;
			for(int j=st;j<4;j++){
				dp[x][q]=max(dp[x][q],f[x][i+q][j]);
			}
		}
	}
	for(int i=0;i<5;i++){
		int st=i%3;
		for(int j=st;j<3;j++){
			dp[x][3]=max(dp[x][3],f[x][i][j+1]);
		}
	}
}
signed main()
{
	freopen("ternary.in", "r", stdin);
	freopen("ternary.out", "w", stdout);
	int T=read();
	while(T--){
		int n=read();
		for(int i=1;i<=n;i++) tu[i].clear();
		for(int i=1;i<=n;i++){
			for(int j=0;j<5;j++){
				for(int k=0;k<4;k++){
					dp[i][k]=f[i][j][k]=g[j][k]=-1e18;
				}
			}
		}
		for(int i=1;i<n;i++){
			int u=read(),v=read(),w=read();
			tu[u].push_back({v,w});
			tu[v].push_back({u,w});
		}
		dfs(1,0);
		cout<<dp[1][0]<<'\n';
	}
	return 0;
}

T3

image

另一种思路

前半部分是一样的

分 <=e/2 和 >e/2

第一组内部的相对顺序不能改变

从小到大加入第二组中的所有数

设当前加入了 x

称一个点 a[i] 是断点,当:

  1. 它属于第一组,同时 a[i]+x<=e
  2. 它属于第二组,同时已经被加入了

第一种称为第一类断点

整个序列被第一类断点分割成若干段

找到 x 所在的那一段 [l,r]

那么,x 可以被放到 [l,r] 中的任意一个位置

同时,不能被放到 [l,r] 以外,因为边界处是不可交换的断点

考虑所有 [l,r] 中的断点,一个计数经典技巧是,只考虑 x 后面第一个断点是什么

将答案乘上为 x 选取后面第一个断点的方案数

然后标记 x 为断点

整个过程都可以 set 维护断点分割的连续段

就是钦定断点之间的相对位置不能被改变(无论第一类还是第二类)

那么,x 可以被插入到两个断点之间

这个方案数就是 (l,r] 中的断点数量

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;	
}
inline int read()
{
	int x=0,f=1;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 x*f;
}
const int MOD=1e9+7;
int a[200005],cnmin,cnmax;
pair<int,int> minn[200005],maxx[200005];
set<pair<int,int> > p; 
signed main()
{
	freopen("str.in", "r", stdin);
	freopen("str.out", "w", stdout);
	int n=read(),E=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	} 
	for(int i=1;i<=n;i++){
		if(a[i]<=E/2){
			minn[++cnmin]={a[i],i};
			p.insert({i,1});
		} 
		else maxx[++cnmax]={a[i],i};
	}
	p.insert({n+1,1});
	sort(minn+1,minn+cnmin+1);
	reverse(minn+1,minn+cnmin+1);
	sort(maxx+1,maxx+cnmax+1);
//	for(int i=1;i<=cnmin;i++) cout<<minn[i].first<<' ';
//	cout<<'\n';
	int ans=1;
	int l=1;
	for(int i=1;i<=cnmax;i++){
		while(minn[l].first+maxx[i].first>E&&l<=cnmin){
			auto pp=p.lower_bound({minn[l].second,-1});
			pair<int,int> px=*pp;
			pp++;
			pair<int,int> py=*pp;
			int id=py.first,sum=px.second+py.second;
//			cout<<id<<' '<<sum<<'\n';
			p.insert({id,sum});
			p.erase(px);
			p.erase(py);
			l++;
		}
		auto pp=p.lower_bound({maxx[i].second,-1});
		pair<int,int> px=*pp;
		ans*=px.second;
		ans%=MOD;
		p.erase(px);
		p.insert({px.first,px.second+1});
	}
	cout<<ans<<'\n';
	return 0;
}

T4

预处理极小mex区间

变成三维偏序

观察性质

image

变成二位偏序

贪心模拟二分

1.https://www.luogu.com.cn/problem/AT_kupc2018_k

容易发现不好点一定是独立集(否则有边一定有一个点是好点)

发现K没用,直接变成K=2即可

然后将平均值拆开

check(m)是否可行

等价于b1-m + b2-m + …… + bq-m >=0

变成求权值和最大的点覆盖

(点覆盖就是独立集的补集)

转化成求独立集的最小值

可以dp

dpr表示右边选r这个点的最小独立集

枚举上一个选的点l,计算区间贡献K_l,r

每次从r到r+1时,对1~l-1的区间的K_p,r+1进行一个w_l,r的区间加,同时修改dp_r+1

需要线段树维护区间加,全局min,单点修

2.https://www.luogu.com.cn/problem/P9695

对每个x二分一个左端点l右端点r

数据结构check是否可行(s_a1+s_a2+……+s_ak是否=r-l+1)

3.

image

考虑贪心

让每个ai尽量大,此基础上让bi尽量大

但不一定优

考虑对ai做前缀减(相当于区间减)

假设减去h

于是我们就有了h次区间加的操作

贪心加区间长的即可

4.https://www.luogu.com.cn/problem/CF405E

对于一棵树从下往上贪心

找到一个父亲节点,使得所有儿子都是叶子

若儿子为偶数,配对消除

否则,剩下的一条边与fa-fa[fa]这条边配对,消除一棵子树

可以自然转化成图

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;	
}
inline int read()
{
	int x=0,f=1;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 x*f;
}
vector<pair<int,int> > tu[100005];
int vis[100005];
int dfs(int x){
	queue<int> q;
	for(auto [ed,id]:tu[x]){
		if(vis[id]) continue;
		vis[id]=1;
		int f=dfs(ed);
		if(f) cout<<x<<' '<<ed<<' '<<f<<'\n';
		else q.push(ed);
	}
//	cout<<q.size()<<'\n';
	while(q.size()>=2){
		cout<<q.front()<<' ';
		cout<<x<<' ';
		q.pop();
		cout<<q.front()<<'\n';
		q.pop();
	}
	if(q.size()) return q.front();
	return 0;
}
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	int n=read(),m=read();
	if(m%2==1){
		cout<<"No solution"<<'\n';
		return 0;
	}
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		tu[u].push_back({v,i});
		tu[v].push_back({u,i});
	}
	dfs(1);
	return 0;
}

5.https://www.luogu.com.cn/problem/P11189

先进行操作2

最后一定使得ap<=0且ap<=afa才能通过操作1满足要求

于是每个叶子节点的操作次数区间[li,ri]易求

考虑如何快速求父亲的[li,ri]

li就是所有儿子的li的max

ri发现满足单调性,二分check

6.https://www.luogu.com.cn/problem/P13552

容易发现最后进行加操作一定是优的

问题转化成将一堆数划分成k个集合,使得每个集合按位与结果最大

假设有m个数最高位=1

首先有一些性质

二进制下A 1 B 0 C 0

则 (a&b)+c<a+(b&c)

于是,若k>=m+1,则每个高位1单独一个集合,剩余的0划分成k-m个集合

否则,将所有的0按位与进行合并,递归考虑次高位的情况

posted @ 2025-10-07 18:52  gbrrain  阅读(10)  评论(0)    收藏  举报